diff options
Diffstat (limited to 'win8/metro_driver/file_picker.cc')
-rw-r--r-- | win8/metro_driver/file_picker.cc | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/win8/metro_driver/file_picker.cc b/win8/metro_driver/file_picker.cc new file mode 100644 index 0000000..4977be6 --- /dev/null +++ b/win8/metro_driver/file_picker.cc @@ -0,0 +1,618 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "stdafx.h" +#include "win8/metro_driver/file_picker.h" + +#include <windows.storage.pickers.h> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/win/scoped_comptr.h" +#include "base/win/metro.h" +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/winrt_utils.h" + +namespace { + +namespace winstorage = ABI::Windows::Storage; +typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf; + +// TODO(siggi): Complete this implementation and move it to a common place. +class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> { + public: + ~StringVectorImpl() { + std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString); + } + + HRESULT RuntimeClassInitialize(const std::vector<string16>& list) { + for (size_t i = 0; i < list.size(); ++i) + strings_.push_back(MakeHString(list[i])); + + return S_OK; + } + + // IVector<HSTRING> implementation. + STDMETHOD(GetAt)(unsigned index, HSTRING* item) { + if (index >= strings_.size()) + return E_INVALIDARG; + + return ::WindowsDuplicateString(strings_[index], item); + } + STDMETHOD(get_Size)(unsigned *size) { + *size = strings_.size(); + return S_OK; + } + STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) { + return E_NOTIMPL; + } + STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) { + return E_NOTIMPL; + } + + // write methods + STDMETHOD(SetAt)(unsigned index, HSTRING item) { + return E_NOTIMPL; + } + STDMETHOD(InsertAt)(unsigned index, HSTRING item) { + return E_NOTIMPL; + } + STDMETHOD(RemoveAt)(unsigned index) { + return E_NOTIMPL; + } + STDMETHOD(Append)(HSTRING item) { + return E_NOTIMPL; + } + STDMETHOD(RemoveAtEnd)() { + return E_NOTIMPL; + } + STDMETHOD(Clear)() { + return E_NOTIMPL; + } + + private: + std::vector<HSTRING> strings_; +}; + +class FilePickerSessionBase { + public: + // Creates a file picker for open_file_name. + explicit FilePickerSessionBase(OPENFILENAME* open_file_name); + + // Runs the picker, returns true on success. + bool Run(); + + protected: + // Creates, configures and starts a file picker. + // If the HRESULT returned is a failure code the file picker has not started, + // so no callbacks should be expected. + virtual HRESULT StartFilePicker() = 0; + + // The parameters to our picker. + OPENFILENAME* open_file_name_; + // The event Run waits on. + base::WaitableEvent event_; + // True iff a file picker has successfully finished. + bool success_; + + private: + // Initiate a file picker, must be called on the metro dispatcher's thread. + void DoFilePicker(); + + DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase); +}; + +class OpenFilePickerSession : public FilePickerSessionBase { + public: + explicit OpenFilePickerSession(OPENFILENAME* open_file_name); + + private: + virtual HRESULT StartFilePicker() OVERRIDE; + + typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*> + SingleFileAsyncOp; + typedef winfoundtn::Collections::IVectorView< + winstorage::StorageFile*> StorageFileVectorCollection; + typedef winfoundtn::IAsyncOperation<StorageFileVectorCollection*> + MultiFileAsyncOp; + + // Called asynchronously when a single file picker is done. + HRESULT SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status); + + // Called asynchronously when a multi file picker is done. + HRESULT MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status); + + // Composes a multi-file result string suitable for returning to a + // from a storage file collection. + static HRESULT ComposeMultiFileResult(StorageFileVectorCollection* files, + string16* result); + private: + DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession); +}; + +class SaveFilePickerSession : public FilePickerSessionBase { + public: + explicit SaveFilePickerSession(OPENFILENAME* open_file_name); + + private: + virtual HRESULT StartFilePicker() OVERRIDE; + + typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*> + SaveFileAsyncOp; + + // Called asynchronously when the save file picker is done. + HRESULT FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status); +}; + +FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name) + : open_file_name_(open_file_name), + event_(true, false), + success_(false) { +} + +bool FilePickerSessionBase::Run() { + DCHECK(globals.appview_msg_loop != NULL); + + // Post the picker request over to the metro thread. + bool posted = globals.appview_msg_loop->PostTask(FROM_HERE, + base::Bind(&FilePickerSessionBase::DoFilePicker, base::Unretained(this))); + if (!posted) + return false; + + // Wait for the file picker to complete. + event_.Wait(); + + return success_; +} + +void FilePickerSessionBase::DoFilePicker() { + // The file picker will fail if spawned from a snapped application, + // so let's attempt to unsnap first if we're in that state. + HRESULT hr = ChromeAppView::Unsnap(); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr; + } + + if (SUCCEEDED(hr)) + hr = StartFilePicker(); + + if (FAILED(hr)) { + LOG(ERROR) << "Failed to start file picker, error 0x" + << std::hex << hr; + + event_.Signal(); + } +} + +OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name) + : FilePickerSessionBase(open_file_name) { +} + +HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<winstorage::IStorageFile> file; + HRESULT hr = async->GetResults(file.GetAddressOf()); + + if (file) { + mswr::ComPtr<winstorage::IStorageItem> storage_item; + if (SUCCEEDED(hr)) + hr = file.As(&storage_item); + + mswrw::HString file_path; + if (SUCCEEDED(hr)) + hr = storage_item->get_Path(file_path.GetAddressOf()); + + if (SUCCEEDED(hr)) { + size_t path_len = 0; + const wchar_t* path_str = + ::WindowsGetStringRawBuffer(file_path.Get(), &path_len); + + // If the selected file name is longer than the supplied buffer, + // we return false as per GetOpenFileName documentation. + if (path_len < open_file_name_->nMaxFile) { + base::wcslcpy(open_file_name_->lpstrFile, + path_str, + open_file_name_->nMaxFile); + success_ = true; + } + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << status; + } + + event_.Signal(); + + return S_OK; +} + +HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<StorageFileVectorCollection> files; + HRESULT hr = async->GetResults(files.GetAddressOf()); + + if (files) { + string16 result; + if (SUCCEEDED(hr)) + hr = ComposeMultiFileResult(files.Get(), &result); + + if (SUCCEEDED(hr)) { + if (result.size() + 1 < open_file_name_->nMaxFile) { + // Because the result has embedded nulls, we must memcpy. + memcpy(open_file_name_->lpstrFile, + result.c_str(), + (result.size() + 1) * sizeof(result[0])); + success_ = true; + } + } + } else { + LOG(ERROR) << "NULL StorageFileVectorCollection"; + } + } else { + LOG(ERROR) << "Unexpected async status " << status; + } + + event_.Signal(); + + return S_OK; +} + +HRESULT OpenFilePickerSession::StartFilePicker() { + DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); + DCHECK(open_file_name_ != NULL); + + mswrw::HStringReference class_name( + RuntimeClass_Windows_Storage_Pickers_FileOpenPicker); + + // Create the file picker. + mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker; + HRESULT hr = ::Windows::Foundation::ActivateInstance( + class_name.Get(), picker.GetAddressOf()); + CheckHR(hr); + + // Set the file type filter + mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter; + hr = picker->get_FileTypeFilter(filter.GetAddressOf()); + if (FAILED(hr)) + return hr; + + if (open_file_name_->lpstrFilter == NULL) { + hr = filter->Append(mswrw::HStringReference(L"*").Get()); + if (FAILED(hr)) + return hr; + } else { + // The filter is a concatenation of zero terminated string pairs, + // where each pair is {description, extension}. The concatenation ends + // with a zero length string - e.g. a double zero terminator. + const wchar_t* walk = open_file_name_->lpstrFilter; + while (*walk != L'\0') { + // Walk past the description. + walk += wcslen(walk) + 1; + + // We should have an extension, but bail on malformed filters. + if (*walk == L'\0') + break; + + // There can be a single extension, or a list of semicolon-separated ones. + std::vector<string16> extensions_win32_style; + size_t extension_count = Tokenize(walk, L";", &extensions_win32_style); + DCHECK_EQ(extension_count, extensions_win32_style.size()); + + // Metro wants suffixes only, not patterns. + mswrw::HString extension; + std::vector<string16> extensions; + for (size_t i = 0; i < extensions_win32_style.size(); ++i) { + if (extensions_win32_style[i] == L"*.*") { + // The wildcard filter is "*" for Metro. The string "*.*" produces + // an "invalid parameter" error. + hr = extension.Set(L"*"); + } else { + // Metro wants suffixes only, not patterns. + string16 ext = FilePath(extensions_win32_style[i]).Extension(); + if ((ext.size() < 2) || + (ext.find_first_of(L"*?") != string16::npos)) { + continue; + } + hr = extension.Set(ext.c_str()); + } + if (SUCCEEDED(hr)) + hr = filter->Append(extension.Get()); + if (FAILED(hr)) + return hr; + } + + // Walk past the extension. + walk += wcslen(walk) + 1; + } + } + + // Spin up a single or multi picker as appropriate. + if (open_file_name_->Flags & OFN_ALLOWMULTISELECT) { + mswr::ComPtr<MultiFileAsyncOp> completion; + hr = picker->PickMultipleFilesAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + StorageFileVectorCollection*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &OpenFilePickerSession::MultiPickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + + return hr; + } else { + mswr::ComPtr<SingleFileAsyncOp> completion; + hr = picker->PickSingleFileAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + winstorage::StorageFile*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &OpenFilePickerSession::SinglePickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + + return hr; + } +} + +HRESULT OpenFilePickerSession::ComposeMultiFileResult( + StorageFileVectorCollection* files, string16* result) { + DCHECK(files != NULL); + DCHECK(result != NULL); + + // Empty the output string. + result->clear(); + + size_t num_files = 0; + HRESULT hr = files->get_Size(&num_files); + if (FAILED(hr)) + return hr; + + // Make sure we return an error on an empty collection. + if (num_files == 0) { + DLOG(ERROR) << "Empty collection on input."; + return E_UNEXPECTED; + } + + // This stores the base path that should be the parent of all the files. + FilePath base_path; + + // Iterate through the collection and append the file paths to the result. + for (size_t i = 0; i < num_files; ++i) { + mswr::ComPtr<winstorage::IStorageFile> file; + hr = files->GetAt(i, file.GetAddressOf()); + if (FAILED(hr)) + return hr; + + mswr::ComPtr<winstorage::IStorageItem> storage_item; + hr = file.As(&storage_item); + if (FAILED(hr)) + return hr; + + mswrw::HString file_path_str; + hr = storage_item->get_Path(file_path_str.GetAddressOf()); + if (FAILED(hr)) + return hr; + + FilePath file_path(MakeStdWString(file_path_str.Get())); + if (base_path.empty()) { + DCHECK(result->empty()); + base_path = file_path.DirName(); + + // Append the path, including the terminating zero. + // We do this only for the first file. + result->append(base_path.value().c_str(), base_path.value().size() + 1); + } + DCHECK(!result->empty()); + DCHECK(!base_path.empty()); + DCHECK(base_path == file_path.DirName()); + + // Append the base name, including the terminating zero. + FilePath base_name = file_path.BaseName(); + result->append(base_name.value().c_str(), base_name.value().size() + 1); + } + + DCHECK(!result->empty()); + + return S_OK; +} + +SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name) + : FilePickerSessionBase(open_file_name) { +} + +HRESULT SaveFilePickerSession::StartFilePicker() { + DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); + DCHECK(open_file_name_ != NULL); + + mswrw::HStringReference class_name( + RuntimeClass_Windows_Storage_Pickers_FileSavePicker); + + // Create the file picker. + mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker; + HRESULT hr = ::Windows::Foundation::ActivateInstance( + class_name.Get(), picker.GetAddressOf()); + CheckHR(hr); + + typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*> + StringVectorMap; + mswr::ComPtr<StringVectorMap> choices; + hr = picker->get_FileTypeChoices(choices.GetAddressOf()); + if (FAILED(hr)) + return hr; + + if (open_file_name_->lpstrFilter) { + // The filter is a concatenation of zero terminated string pairs, + // where each pair is {description, extension list}. The concatenation ends + // with a zero length string - e.g. a double zero terminator. + const wchar_t* walk = open_file_name_->lpstrFilter; + while (*walk != L'\0') { + mswrw::HString description; + hr = description.Set(walk); + if (FAILED(hr)) + return hr; + + // Walk past the description. + walk += wcslen(walk) + 1; + + // We should have an extension, but bail on malformed filters. + if (*walk == L'\0') + break; + + // There can be a single extension, or a list of semicolon-separated ones. + std::vector<string16> extensions_win32_style; + size_t extension_count = Tokenize(walk, L";", &extensions_win32_style); + DCHECK_EQ(extension_count, extensions_win32_style.size()); + + // Metro wants suffixes only, not patterns. Also, metro does not support + // the all files ("*") pattern in the save picker. + std::vector<string16> extensions; + for (size_t i = 0; i < extensions_win32_style.size(); ++i) { + string16 ext = FilePath(extensions_win32_style[i]).Extension(); + if ((ext.size() < 2) || + (ext.find_first_of(L"*?") != string16::npos)) + continue; + extensions.push_back(ext); + } + + if (!extensions.empty()) { + // Convert to a Metro collection class. + mswr::ComPtr<StringVectorItf> list; + hr = mswr::MakeAndInitialize<StringVectorImpl>( + list.GetAddressOf(), extensions); + if (FAILED(hr)) + return hr; + + // Finally set the filter. + boolean replaced = FALSE; + hr = choices->Insert(description.Get(), list.Get(), &replaced); + if (FAILED(hr)) + return hr; + DCHECK_EQ(FALSE, replaced); + } + + // Walk past the extension(s). + walk += wcslen(walk) + 1; + } + } + + // The save picker requires at least one choice. Callers are strongly advised + // to provide sensible choices. If none were given, fallback to .dat. + uint32 num_choices = 0; + hr = choices->get_Size(&num_choices); + if (FAILED(hr)) + return hr; + + if (num_choices == 0) { + mswrw::HString description; + // TODO(grt): Get a properly translated string. This can't be done from + // within metro_driver. Consider preprocessing the filter list in Chrome + // land to ensure it has this entry if all others are patterns. In that + // case, this whole block of code can be removed. + hr = description.Set(L"Data File"); + if (FAILED(hr)) + return hr; + + mswr::ComPtr<StringVectorItf> list; + hr = mswr::MakeAndInitialize<StringVectorImpl>( + list.GetAddressOf(), std::vector<string16>(1, L".dat")); + if (FAILED(hr)) + return hr; + + boolean replaced = FALSE; + hr = choices->Insert(description.Get(), list.Get(), &replaced); + if (FAILED(hr)) + return hr; + DCHECK_EQ(FALSE, replaced); + } + + if (open_file_name_->lpstrFile != NULL) { + hr = picker->put_SuggestedFileName( + mswrw::HStringReference( + const_cast<const wchar_t*>(open_file_name_->lpstrFile)).Get()); + if (FAILED(hr)) + return hr; + } + + mswr::ComPtr<SaveFileAsyncOp> completion; + hr = picker->PickSaveFileAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + winstorage::StorageFile*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &SaveFilePickerSession::FilePickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + + return hr; +} + +HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<winstorage::IStorageFile> file; + HRESULT hr = async->GetResults(file.GetAddressOf()); + + if (file) { + mswr::ComPtr<winstorage::IStorageItem> storage_item; + if (SUCCEEDED(hr)) + hr = file.As(&storage_item); + + mswrw::HString file_path; + if (SUCCEEDED(hr)) + hr = storage_item->get_Path(file_path.GetAddressOf()); + + if (SUCCEEDED(hr)) { + string16 path_str = MakeStdWString(file_path.Get()); + + // If the selected file name is longer than the supplied buffer, + // we return false as per GetOpenFileName documentation. + if (path_str.size() < open_file_name_->nMaxFile) { + base::wcslcpy(open_file_name_->lpstrFile, + path_str.c_str(), + open_file_name_->nMaxFile); + success_ = true; + } + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << status; + } + + event_.Signal(); + + return S_OK; +} + +} // namespace + +BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) { + OpenFilePickerSession session(open_file_name); + + return session.Run(); +} + +BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) { + SaveFilePickerSession session(open_file_name); + + return session.Run(); +} |