Fixing an off by one error in screenshot manager code.
[chromium-blink-merge.git] / win8 / metro_driver / file_picker_ash.cc
blob902ee782d686b75266036521339b61dbad75b2b6
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "stdafx.h"
6 #include "win8/metro_driver/file_picker_ash.h"
8 #include "base/bind.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/win/metro.h"
14 #include "base/win/scoped_comptr.h"
15 #include "ui/metro_viewer/metro_viewer_messages.h"
16 #include "win8/metro_driver/chrome_app_view_ash.h"
17 #include "win8/metro_driver/winrt_utils.h"
19 namespace {
21 namespace winstorage = ABI::Windows::Storage;
22 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
24 // TODO(siggi): Complete this implementation and move it to a common place.
25 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
26 public:
27 ~StringVectorImpl() {
28 std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
31 HRESULT RuntimeClassInitialize(const std::vector<base::string16>& list) {
32 for (size_t i = 0; i < list.size(); ++i)
33 strings_.push_back(MakeHString(list[i]));
35 return S_OK;
38 // IVector<HSTRING> implementation.
39 STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
40 if (index >= strings_.size())
41 return E_INVALIDARG;
43 return ::WindowsDuplicateString(strings_[index], item);
45 STDMETHOD(get_Size)(unsigned *size) {
46 if (strings_.size() > UINT_MAX)
47 return E_UNEXPECTED;
48 *size = static_cast<unsigned>(strings_.size());
49 return S_OK;
51 STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
52 return E_NOTIMPL;
54 STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
55 return E_NOTIMPL;
58 // write methods
59 STDMETHOD(SetAt)(unsigned index, HSTRING item) {
60 return E_NOTIMPL;
62 STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
63 return E_NOTIMPL;
65 STDMETHOD(RemoveAt)(unsigned index) {
66 return E_NOTIMPL;
68 STDMETHOD(Append)(HSTRING item) {
69 return E_NOTIMPL;
71 STDMETHOD(RemoveAtEnd)() {
72 return E_NOTIMPL;
74 STDMETHOD(Clear)() {
75 return E_NOTIMPL;
78 private:
79 std::vector<HSTRING> strings_;
82 } // namespace
84 FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh* app_view,
85 const base::string16& title,
86 const base::string16& filter,
87 const base::FilePath& default_path)
88 : app_view_(app_view),
89 title_(title),
90 filter_(filter),
91 default_path_(default_path),
92 success_(false) {
95 bool FilePickerSessionBase::Run() {
96 if (!DoFilePicker())
97 return false;
98 return success_;
101 bool FilePickerSessionBase::DoFilePicker() {
102 // The file picker will fail if spawned from a snapped application,
103 // so let's attempt to unsnap first if we're in that state.
104 HRESULT hr = ChromeAppViewAsh::Unsnap();
105 if (FAILED(hr)) {
106 LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr;
107 return false;
109 hr = StartFilePicker();
110 if (FAILED(hr)) {
111 LOG(ERROR) << "Failed to start file picker, error 0x"
112 << std::hex << hr;
113 return false;
115 return true;
118 OpenFilePickerSession::OpenFilePickerSession(
119 ChromeAppViewAsh* app_view,
120 const base::string16& title,
121 const base::string16& filter,
122 const base::FilePath& default_path,
123 bool allow_multi_select)
124 : FilePickerSessionBase(app_view, title, filter, default_path),
125 allow_multi_select_(allow_multi_select) {
128 HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async,
129 AsyncStatus status) {
130 if (status == Completed) {
131 mswr::ComPtr<winstorage::IStorageFile> file;
132 HRESULT hr = async->GetResults(file.GetAddressOf());
134 if (file) {
135 mswr::ComPtr<winstorage::IStorageItem> storage_item;
136 if (SUCCEEDED(hr))
137 hr = file.As(&storage_item);
139 mswrw::HString file_path;
140 if (SUCCEEDED(hr))
141 hr = storage_item->get_Path(file_path.GetAddressOf());
143 if (SUCCEEDED(hr)) {
144 UINT32 path_len = 0;
145 const wchar_t* path_str =
146 ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
148 result_ = path_str;
149 success_ = true;
151 } else {
152 LOG(ERROR) << "NULL IStorageItem";
154 } else {
155 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
157 app_view_->OnOpenFileCompleted(this, success_);
158 return S_OK;
161 HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async,
162 AsyncStatus status) {
163 if (status == Completed) {
164 mswr::ComPtr<StorageFileVectorCollection> files;
165 HRESULT hr = async->GetResults(files.GetAddressOf());
167 if (files) {
168 base::string16 result;
169 if (SUCCEEDED(hr))
170 hr = ComposeMultiFileResult(files.Get(), &result);
172 if (SUCCEEDED(hr)) {
173 success_ = true;
174 // The code below has been copied from the
175 // SelectFileDialogImpl::RunOpenMultiFileDialog function in
176 // select_file_dialog_win.cc.
177 // TODO(ananta)
178 // Consolidate this into a common place.
179 const wchar_t* selection = result.c_str();
180 std::vector<base::FilePath> files;
182 while (*selection) { // Empty string indicates end of list.
183 files.push_back(base::FilePath(selection));
184 // Skip over filename and null-terminator.
185 selection += files.back().value().length() + 1;
187 if (files.empty()) {
188 success_ = false;
189 } else if (files.size() == 1) {
190 // When there is one file, it contains the path and filename.
191 filenames_ = files;
192 } else if (files.size() > 1) {
193 // Otherwise, the first string is the path, and the remainder are
194 // filenames.
195 std::vector<base::FilePath>::iterator path = files.begin();
196 for (std::vector<base::FilePath>::iterator file = path + 1;
197 file != files.end(); ++file) {
198 filenames_.push_back(path->Append(*file));
202 } else {
203 LOG(ERROR) << "NULL StorageFileVectorCollection";
205 } else {
206 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
208 app_view_->OnOpenFileCompleted(this, success_);
209 return S_OK;
212 HRESULT OpenFilePickerSession::StartFilePicker() {
213 mswrw::HStringReference class_name(
214 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
216 // Create the file picker.
217 mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker;
218 HRESULT hr = ::Windows::Foundation::ActivateInstance(
219 class_name.Get(), picker.GetAddressOf());
220 CheckHR(hr);
222 // Set the file type filter
223 mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
224 hr = picker->get_FileTypeFilter(filter.GetAddressOf());
225 if (FAILED(hr))
226 return hr;
228 if (filter_.empty()) {
229 hr = filter->Append(mswrw::HStringReference(L"*").Get());
230 if (FAILED(hr))
231 return hr;
232 } else {
233 // The filter is a concatenation of zero terminated string pairs,
234 // where each pair is {description, extension}. The concatenation ends
235 // with a zero length string - e.g. a double zero terminator.
236 const wchar_t* walk = filter_.c_str();
237 while (*walk != L'\0') {
238 // Walk past the description.
239 walk += wcslen(walk) + 1;
241 // We should have an extension, but bail on malformed filters.
242 if (*walk == L'\0')
243 break;
245 // There can be a single extension, or a list of semicolon-separated ones.
246 std::vector<base::string16> extensions_win32_style;
247 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
248 DCHECK_EQ(extension_count, extensions_win32_style.size());
250 // Metro wants suffixes only, not patterns.
251 mswrw::HString extension;
252 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
253 if (extensions_win32_style[i] == L"*.*") {
254 // The wildcard filter is "*" for Metro. The string "*.*" produces
255 // an "invalid parameter" error.
256 hr = extension.Set(L"*");
257 } else {
258 // Metro wants suffixes only, not patterns.
259 base::string16 ext =
260 base::FilePath(extensions_win32_style[i]).Extension();
261 if ((ext.size() < 2) ||
262 (ext.find_first_of(L"*?") != base::string16::npos)) {
263 continue;
265 hr = extension.Set(ext.c_str());
267 if (SUCCEEDED(hr))
268 hr = filter->Append(extension.Get());
269 if (FAILED(hr))
270 return hr;
273 // Walk past the extension.
274 walk += wcslen(walk) + 1;
278 // Spin up a single or multi picker as appropriate.
279 if (allow_multi_select_) {
280 mswr::ComPtr<MultiFileAsyncOp> completion;
281 hr = picker->PickMultipleFilesAsync(&completion);
282 if (FAILED(hr))
283 return hr;
285 // Create the callback method.
286 typedef winfoundtn::IAsyncOperationCompletedHandler<
287 StorageFileVectorCollection*> HandlerDoneType;
288 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
289 this, &OpenFilePickerSession::MultiPickerDone));
290 DCHECK(handler.Get() != NULL);
291 hr = completion->put_Completed(handler.Get());
293 return hr;
294 } else {
295 mswr::ComPtr<SingleFileAsyncOp> completion;
296 hr = picker->PickSingleFileAsync(&completion);
297 if (FAILED(hr))
298 return hr;
300 // Create the callback method.
301 typedef winfoundtn::IAsyncOperationCompletedHandler<
302 winstorage::StorageFile*> HandlerDoneType;
303 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
304 this, &OpenFilePickerSession::SinglePickerDone));
305 DCHECK(handler.Get() != NULL);
306 hr = completion->put_Completed(handler.Get());
308 return hr;
312 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
313 StorageFileVectorCollection* files, base::string16* result) {
314 DCHECK(files != NULL);
315 DCHECK(result != NULL);
317 // Empty the output string.
318 result->clear();
320 unsigned int num_files = 0;
321 HRESULT hr = files->get_Size(&num_files);
322 if (FAILED(hr))
323 return hr;
325 // Make sure we return an error on an empty collection.
326 if (num_files == 0) {
327 DLOG(ERROR) << "Empty collection on input.";
328 return E_UNEXPECTED;
331 // This stores the base path that should be the parent of all the files.
332 base::FilePath base_path;
334 // Iterate through the collection and append the file paths to the result.
335 for (unsigned int i = 0; i < num_files; ++i) {
336 mswr::ComPtr<winstorage::IStorageFile> file;
337 hr = files->GetAt(i, file.GetAddressOf());
338 if (FAILED(hr))
339 return hr;
341 mswr::ComPtr<winstorage::IStorageItem> storage_item;
342 hr = file.As(&storage_item);
343 if (FAILED(hr))
344 return hr;
346 mswrw::HString file_path_str;
347 hr = storage_item->get_Path(file_path_str.GetAddressOf());
348 if (FAILED(hr))
349 return hr;
351 base::FilePath file_path(MakeStdWString(file_path_str.Get()));
352 if (base_path.empty()) {
353 DCHECK(result->empty());
354 base_path = file_path.DirName();
356 // Append the path, including the terminating zero.
357 // We do this only for the first file.
358 result->append(base_path.value().c_str(), base_path.value().size() + 1);
360 DCHECK(!result->empty());
361 DCHECK(!base_path.empty());
362 DCHECK(base_path == file_path.DirName());
364 // Append the base name, including the terminating zero.
365 base::FilePath base_name = file_path.BaseName();
366 result->append(base_name.value().c_str(), base_name.value().size() + 1);
369 DCHECK(!result->empty());
371 return S_OK;
374 SaveFilePickerSession::SaveFilePickerSession(
375 ChromeAppViewAsh* app_view,
376 const MetroViewerHostMsg_SaveAsDialogParams& params)
377 : FilePickerSessionBase(app_view,
378 params.title,
379 params.filter,
380 params.suggested_name),
381 filter_index_(params.filter_index) {
384 int SaveFilePickerSession::filter_index() const {
385 // TODO(ananta)
386 // Add support for returning the correct filter index. This does not work in
387 // regular Chrome metro on trunk as well.
388 // BUG: https://code.google.com/p/chromium/issues/detail?id=172704
389 return filter_index_;
392 HRESULT SaveFilePickerSession::StartFilePicker() {
393 mswrw::HStringReference class_name(
394 RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
396 // Create the file picker.
397 mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker;
398 HRESULT hr = ::Windows::Foundation::ActivateInstance(
399 class_name.Get(), picker.GetAddressOf());
400 CheckHR(hr);
402 typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*>
403 StringVectorMap;
404 mswr::ComPtr<StringVectorMap> choices;
405 hr = picker->get_FileTypeChoices(choices.GetAddressOf());
406 if (FAILED(hr))
407 return hr;
409 if (!filter_.empty()) {
410 // The filter is a concatenation of zero terminated string pairs,
411 // where each pair is {description, extension list}. The concatenation ends
412 // with a zero length string - e.g. a double zero terminator.
413 const wchar_t* walk = filter_.c_str();
414 while (*walk != L'\0') {
415 mswrw::HString description;
416 hr = description.Set(walk);
417 if (FAILED(hr))
418 return hr;
420 // Walk past the description.
421 walk += wcslen(walk) + 1;
423 // We should have an extension, but bail on malformed filters.
424 if (*walk == L'\0')
425 break;
427 // There can be a single extension, or a list of semicolon-separated ones.
428 std::vector<base::string16> extensions_win32_style;
429 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
430 DCHECK_EQ(extension_count, extensions_win32_style.size());
432 // Metro wants suffixes only, not patterns. Also, metro does not support
433 // the all files ("*") pattern in the save picker.
434 std::vector<base::string16> extensions;
435 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
436 base::string16 ext =
437 base::FilePath(extensions_win32_style[i]).Extension();
438 if ((ext.size() < 2) ||
439 (ext.find_first_of(L"*?") != base::string16::npos))
440 continue;
441 extensions.push_back(ext);
444 if (!extensions.empty()) {
445 // Convert to a Metro collection class.
446 mswr::ComPtr<StringVectorItf> list;
447 hr = mswr::MakeAndInitialize<StringVectorImpl>(
448 list.GetAddressOf(), extensions);
449 if (FAILED(hr))
450 return hr;
452 // Finally set the filter.
453 boolean replaced = FALSE;
454 hr = choices->Insert(description.Get(), list.Get(), &replaced);
455 if (FAILED(hr))
456 return hr;
457 DCHECK_EQ(FALSE, replaced);
460 // Walk past the extension(s).
461 walk += wcslen(walk) + 1;
465 // The save picker requires at least one choice. Callers are strongly advised
466 // to provide sensible choices. If none were given, fallback to .dat.
467 uint32 num_choices = 0;
468 hr = choices->get_Size(&num_choices);
469 if (FAILED(hr))
470 return hr;
472 if (num_choices == 0) {
473 mswrw::HString description;
474 // TODO(grt): Get a properly translated string. This can't be done from
475 // within metro_driver. Consider preprocessing the filter list in Chrome
476 // land to ensure it has this entry if all others are patterns. In that
477 // case, this whole block of code can be removed.
478 hr = description.Set(L"Data File");
479 if (FAILED(hr))
480 return hr;
482 mswr::ComPtr<StringVectorItf> list;
483 hr = mswr::MakeAndInitialize<StringVectorImpl>(
484 list.GetAddressOf(), std::vector<base::string16>(1, L".dat"));
485 if (FAILED(hr))
486 return hr;
488 boolean replaced = FALSE;
489 hr = choices->Insert(description.Get(), list.Get(), &replaced);
490 if (FAILED(hr))
491 return hr;
492 DCHECK_EQ(FALSE, replaced);
495 if (!default_path_.empty()) {
496 base::string16 file_part = default_path_.BaseName().value();
497 // If the suggested_name is a root directory, then don't set it as the
498 // suggested name.
499 if (file_part.size() == 1 && file_part[0] == L'\\')
500 file_part.clear();
501 hr = picker->put_SuggestedFileName(
502 mswrw::HStringReference(file_part.c_str()).Get());
503 if (FAILED(hr))
504 return hr;
507 mswr::ComPtr<SaveFileAsyncOp> completion;
508 hr = picker->PickSaveFileAsync(&completion);
509 if (FAILED(hr))
510 return hr;
512 // Create the callback method.
513 typedef winfoundtn::IAsyncOperationCompletedHandler<
514 winstorage::StorageFile*> HandlerDoneType;
515 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
516 this, &SaveFilePickerSession::FilePickerDone));
517 DCHECK(handler.Get() != NULL);
518 hr = completion->put_Completed(handler.Get());
520 return hr;
523 HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async,
524 AsyncStatus status) {
525 if (status == Completed) {
526 mswr::ComPtr<winstorage::IStorageFile> file;
527 HRESULT hr = async->GetResults(file.GetAddressOf());
529 if (file) {
530 mswr::ComPtr<winstorage::IStorageItem> storage_item;
531 if (SUCCEEDED(hr))
532 hr = file.As(&storage_item);
534 mswrw::HString file_path;
535 if (SUCCEEDED(hr))
536 hr = storage_item->get_Path(file_path.GetAddressOf());
538 if (SUCCEEDED(hr)) {
539 base::string16 path_str = MakeStdWString(file_path.Get());
540 result_ = path_str;
541 success_ = true;
543 } else {
544 LOG(ERROR) << "NULL IStorageItem";
546 } else {
547 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
549 app_view_->OnSaveFileCompleted(this, success_);
550 return S_OK;
553 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view,
554 const base::string16& title)
555 : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {}
557 HRESULT FolderPickerSession::StartFilePicker() {
558 mswrw::HStringReference class_name(
559 RuntimeClass_Windows_Storage_Pickers_FolderPicker);
561 // Create the folder picker.
562 mswr::ComPtr<winstorage::Pickers::IFolderPicker> picker;
563 HRESULT hr = ::Windows::Foundation::ActivateInstance(
564 class_name.Get(), picker.GetAddressOf());
565 CheckHR(hr);
567 // Set the file type filter
568 mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
569 hr = picker->get_FileTypeFilter(filter.GetAddressOf());
570 if (FAILED(hr))
571 return hr;
573 hr = filter->Append(mswrw::HStringReference(L"*").Get());
574 if (FAILED(hr))
575 return hr;
577 mswr::ComPtr<FolderPickerAsyncOp> completion;
578 hr = picker->PickSingleFolderAsync(&completion);
579 if (FAILED(hr))
580 return hr;
582 // Create the callback method.
583 typedef winfoundtn::IAsyncOperationCompletedHandler<
584 winstorage::StorageFolder*> HandlerDoneType;
585 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
586 this, &FolderPickerSession::FolderPickerDone));
587 DCHECK(handler.Get() != NULL);
588 hr = completion->put_Completed(handler.Get());
589 return hr;
592 HRESULT FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp* async,
593 AsyncStatus status) {
594 if (status == Completed) {
595 mswr::ComPtr<winstorage::IStorageFolder> folder;
596 HRESULT hr = async->GetResults(folder.GetAddressOf());
598 if (folder) {
599 mswr::ComPtr<winstorage::IStorageItem> storage_item;
600 if (SUCCEEDED(hr))
601 hr = folder.As(&storage_item);
603 mswrw::HString file_path;
604 if (SUCCEEDED(hr))
605 hr = storage_item->get_Path(file_path.GetAddressOf());
607 if (SUCCEEDED(hr)) {
608 base::string16 path_str = MakeStdWString(file_path.Get());
609 result_ = path_str;
610 success_ = true;
612 } else {
613 LOG(ERROR) << "NULL IStorageItem";
615 } else {
616 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
618 app_view_->OnFolderPickerCompleted(this, success_);
619 return S_OK;