michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMetroFilePicker.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "MetroUtils.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: using namespace ABI::Windows::Foundation; michael@0: using namespace ABI::Windows::Foundation::Collections; michael@0: using namespace ABI::Windows::Storage; michael@0: using namespace ABI::Windows::Storage::Pickers; michael@0: using namespace ABI::Windows::Storage::Streams; michael@0: using namespace ABI::Windows::UI; michael@0: using namespace ABI::Windows::UI::ViewManagement; michael@0: using namespace Microsoft::WRL; michael@0: using namespace Microsoft::WRL::Wrappers; michael@0: using namespace mozilla::widget::winrt; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // nsIFilePicker michael@0: michael@0: nsMetroFilePicker::nsMetroFilePicker() michael@0: { michael@0: } michael@0: michael@0: nsMetroFilePicker::~nsMetroFilePicker() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsMetroFilePicker, nsIFilePicker) michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::Init(nsIDOMWindow *parent, const nsAString& title, int16_t mode) michael@0: { michael@0: mMode = mode; michael@0: HRESULT hr; michael@0: switch(mMode) { michael@0: case nsIFilePicker::modeOpen: michael@0: case nsIFilePicker::modeOpenMultiple: michael@0: hr = ActivateGenericInstance(RuntimeClass_Windows_Storage_Pickers_FileOpenPicker, mFileOpenPicker); michael@0: AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); michael@0: return NS_OK; michael@0: case nsIFilePicker::modeSave: michael@0: hr = ActivateGenericInstance(RuntimeClass_Windows_Storage_Pickers_FileSavePicker, mFileSavePicker); michael@0: AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); michael@0: return NS_OK; michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::Show(int16_t *aReturnVal) michael@0: { michael@0: // Metro file picker only offers an async variant which calls back to the michael@0: // UI thread, which is the main thread. We therefore can't call it michael@0: // synchronously from the main thread. michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: HRESULT nsMetroFilePicker::OnPickSingleFile(IAsyncOperation* aFile, michael@0: AsyncStatus aStatus) michael@0: { michael@0: if (aStatus != ABI::Windows::Foundation::AsyncStatus::Completed) { michael@0: if (mCallback) michael@0: mCallback->Done(nsIFilePicker::returnCancel); michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT hr; michael@0: ComPtr file; michael@0: hr = aFile->GetResults(file.GetAddressOf()); michael@0: // When the user cancels hr == S_OK and file is nullptr michael@0: if (FAILED(hr) || !file) { michael@0: if (mCallback) michael@0: mCallback->Done(nsIFilePicker::returnCancel); michael@0: return S_OK; michael@0: } michael@0: ComPtr storageItem; michael@0: hr = file.As(&storageItem); michael@0: if (FAILED(hr)) { michael@0: if (mCallback) michael@0: mCallback->Done(nsIFilePicker::returnCancel); michael@0: return S_OK; michael@0: } michael@0: michael@0: HSTRING path; michael@0: if (FAILED(storageItem->get_Path(&path))) { michael@0: if (mCallback) michael@0: mCallback->Done(nsIFilePicker::returnCancel); michael@0: return S_OK; michael@0: } michael@0: WindowsDuplicateString(path, mFilePath.GetAddressOf()); michael@0: WindowsDeleteString(path); michael@0: michael@0: if (mCallback) { michael@0: mCallback->Done(nsIFilePicker::returnOK); michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsMetroFilePicker::OnPickMultipleFiles(IAsyncOperation*>* aFileList, michael@0: AsyncStatus aStatus) michael@0: { michael@0: if (aStatus != ABI::Windows::Foundation::AsyncStatus::Completed) { michael@0: if (mCallback) michael@0: mCallback->Done(nsIFilePicker::returnCancel); michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT hr; michael@0: ComPtr> view; michael@0: hr = aFileList->GetResults(view.GetAddressOf()); michael@0: if (FAILED(hr)) { michael@0: if (mCallback) michael@0: mCallback->Done(nsIFilePicker::returnCancel); michael@0: return S_OK; michael@0: } michael@0: michael@0: unsigned int length; michael@0: view->get_Size(&length); michael@0: for (unsigned int idx = 0; idx < length; idx++) { michael@0: ComPtr file; michael@0: hr = view->GetAt(idx, file.GetAddressOf()); michael@0: if (FAILED(hr)) { michael@0: continue; michael@0: } michael@0: michael@0: ComPtr storageItem; michael@0: hr = file.As(&storageItem); michael@0: if (FAILED(hr)) { michael@0: continue; michael@0: } michael@0: michael@0: HSTRING path; michael@0: if (SUCCEEDED(storageItem->get_Path(&path))) { michael@0: nsCOMPtr file = michael@0: do_CreateInstance("@mozilla.org/file/local;1"); michael@0: unsigned int tmp; michael@0: if (NS_SUCCEEDED(file->InitWithPath( michael@0: nsAutoString(WindowsGetStringRawBuffer(path, &tmp))))) { michael@0: mFiles.AppendObject(file); michael@0: } michael@0: } michael@0: WindowsDeleteString(path); michael@0: } michael@0: michael@0: if (mCallback) { michael@0: mCallback->Done(nsIFilePicker::returnOK); michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::Open(nsIFilePickerShownCallback *aCallback) michael@0: { michael@0: HRESULT hr; michael@0: // Capture a reference to the callback which we'll also pass into the michael@0: // closure to ensure it's not freed. michael@0: mCallback = aCallback; michael@0: michael@0: // The filepicker cannot open when in snapped view, try to unsnapp michael@0: // before showing the filepicker. michael@0: ApplicationViewState viewState; michael@0: MetroUtils::GetViewState(viewState); michael@0: if (viewState == ApplicationViewState::ApplicationViewState_Snapped) { michael@0: bool unsnapped = SUCCEEDED(MetroUtils::TryUnsnap()); michael@0: NS_ENSURE_TRUE(unsnapped, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: switch(mMode) { michael@0: case nsIFilePicker::modeOpen: { michael@0: NS_ENSURE_ARG_POINTER(mFileOpenPicker); michael@0: michael@0: // Initiate the file picker operation michael@0: ComPtr> asyncOperation; michael@0: hr = mFileOpenPicker->PickSingleFileAsync(asyncOperation.GetAddressOf()); michael@0: AssertRetHRESULT(hr, NS_ERROR_FAILURE); michael@0: michael@0: // Subscribe to the completed event michael@0: ComPtr> michael@0: completedHandler(Callback>( michael@0: this, &nsMetroFilePicker::OnPickSingleFile)); michael@0: hr = asyncOperation->put_Completed(completedHandler.Get()); michael@0: AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); michael@0: break; michael@0: } michael@0: michael@0: case nsIFilePicker::modeOpenMultiple: { michael@0: NS_ENSURE_ARG_POINTER(mFileOpenPicker); michael@0: michael@0: typedef IVectorView StorageTemplate; michael@0: typedef IAsyncOperation AsyncCallbackTemplate; michael@0: typedef IAsyncOperationCompletedHandler HandlerTemplate; michael@0: michael@0: // Initiate the file picker operation michael@0: ComPtr asyncOperation; michael@0: hr = mFileOpenPicker->PickMultipleFilesAsync(asyncOperation.GetAddressOf()); michael@0: AssertRetHRESULT(hr, NS_ERROR_FAILURE); michael@0: michael@0: // Subscribe to the completed event michael@0: ComPtr completedHandler(Callback( michael@0: this, &nsMetroFilePicker::OnPickMultipleFiles)); michael@0: hr = asyncOperation->put_Completed(completedHandler.Get()); michael@0: AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); michael@0: break; michael@0: } michael@0: michael@0: case nsIFilePicker::modeSave: { michael@0: NS_ENSURE_ARG_POINTER(mFileSavePicker); michael@0: michael@0: // Set the default file name michael@0: mFileSavePicker->put_SuggestedFileName(HStringReference(mDefaultFilename.BeginReading()).Get()); michael@0: michael@0: // Set the default file extension michael@0: if (mDefaultExtension.Length() > 0) { michael@0: nsAutoString defaultFileExtension(mDefaultExtension); michael@0: michael@0: // Touch up the extansion format platform hands us. michael@0: if (defaultFileExtension[0] == L'*') { michael@0: defaultFileExtension.Cut(0, 1); michael@0: } else if (defaultFileExtension[0] != L'.') { michael@0: defaultFileExtension.Insert(L".", 0); michael@0: } michael@0: michael@0: // Sometimes the default file extension is not passed in correctly, michael@0: // so we purposfully ignore failures here. michael@0: HString ext; michael@0: ext.Set(defaultFileExtension.BeginReading()); michael@0: hr = mFileSavePicker->put_DefaultFileExtension(ext.Get()); michael@0: NS_ASSERTION(SUCCEEDED(hr), "put_DefaultFileExtension failed, bad format for extension?"); michael@0: michael@0: // Due to a bug in the WinRT file picker, the first file extension in the michael@0: // list is always used when saving a file. So we explicitly make sure the michael@0: // default extension is the first one in the list here. michael@0: if (mFirstTitle.Get()) { michael@0: ComPtr*>> map; michael@0: mFileSavePicker->get_FileTypeChoices(map.GetAddressOf()); michael@0: if (map) { michael@0: boolean found = false; michael@0: unsigned int index; michael@0: map->HasKey(mFirstTitle.Get(), &found); michael@0: if (found) { michael@0: ComPtr> list; michael@0: if (SUCCEEDED(map->Lookup(mFirstTitle.Get(), list.GetAddressOf()))) { michael@0: HString ext; michael@0: ext.Set(defaultFileExtension.get()); michael@0: found = false; michael@0: list->IndexOf(HStringReference(defaultFileExtension.get()).Get(), &index, &found); michael@0: if (found) { michael@0: list->RemoveAt(index); michael@0: list->InsertAt(0, HStringReference(defaultFileExtension.get()).Get()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Dispatch the async show operation michael@0: ComPtr> asyncOperation; michael@0: hr = mFileSavePicker->PickSaveFileAsync(asyncOperation.GetAddressOf()); michael@0: AssertRetHRESULT(hr, NS_ERROR_FAILURE); michael@0: michael@0: // Subscribe to the completed event michael@0: ComPtr> michael@0: completedHandler(Callback>( michael@0: this, &nsMetroFilePicker::OnPickSingleFile)); michael@0: hr = asyncOperation->put_Completed(completedHandler.Get()); michael@0: AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); michael@0: break; michael@0: } michael@0: michael@0: case modeGetFolder: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::GetFile(nsIFile **aFile) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: *aFile = nullptr; michael@0: michael@0: if (WindowsIsStringEmpty(mFilePath.Get())) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); michael@0: NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); michael@0: unsigned int length; michael@0: file->InitWithPath(nsAutoString(mFilePath.GetRawBuffer(&length))); michael@0: NS_ADDREF(*aFile = file); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::GetFileURL(nsIURI **aFileURL) michael@0: { michael@0: *aFileURL = nullptr; michael@0: nsCOMPtr file; michael@0: nsresult rv = GetFile(getter_AddRefs(file)); michael@0: if (!file) michael@0: return rv; michael@0: michael@0: return NS_NewFileURI(aFileURL, file); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::GetFiles(nsISimpleEnumerator **aFiles) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFiles); michael@0: return NS_NewArrayEnumerator(aFiles, mFiles); michael@0: } michael@0: michael@0: // Set the filter index michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::GetFilterIndex(int32_t *aFilterIndex) michael@0: { michael@0: // No associated concept with a Metro file picker michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::SetFilterIndex(int32_t aFilterIndex) michael@0: { michael@0: // No associated concept with a Metro file picker michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // AFACT, it's up to use to supply the implementation of a vector list. michael@0: class MozHStringVector : public RuntimeClass> { michael@0: InspectableClass(L"MozHStringVector", TrustLevel::BaseTrust) michael@0: ~MozHStringVector() { michael@0: Clear(); michael@0: } michael@0: michael@0: // See IVector_impl in windows.foundation.collections.h michael@0: public: michael@0: STDMETHOD(GetAt)(unsigned aIndex, HSTRING* aString) { michael@0: if (aIndex >= mList.Length()) { michael@0: return E_INVALIDARG; michael@0: } michael@0: return WindowsDuplicateString(mList[aIndex], aString); michael@0: } michael@0: michael@0: STDMETHOD(get_Size)(unsigned int* aLength) { michael@0: if (!aLength) { michael@0: return E_INVALIDARG; michael@0: } michael@0: *aLength = mList.Length(); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHOD(Append)(HSTRING aString) { michael@0: HSTRING str; michael@0: if (FAILED(WindowsDuplicateString(aString, &str))) { michael@0: return E_INVALIDARG; michael@0: } michael@0: mList.AppendElement(str); michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHOD(Clear)() { michael@0: int length = mList.Length(); michael@0: for (int idx = 0; idx < length; idx++) michael@0: WindowsDeleteString(mList[idx]); michael@0: mList.Clear(); michael@0: return S_OK; michael@0: } michael@0: michael@0: // interfaces picker code doesn't seem to need michael@0: STDMETHOD(GetView)(IVectorView **aView) { return E_NOTIMPL; } michael@0: STDMETHOD(IndexOf)(HSTRING aValue, unsigned *aIndex, boolean *found) { return E_NOTIMPL; } michael@0: STDMETHOD(SetAt)(unsigned aIndex, HSTRING aString) { return E_NOTIMPL; } michael@0: STDMETHOD(InsertAt)(unsigned aIndex, HSTRING aString) { return E_NOTIMPL; } michael@0: STDMETHOD(RemoveAt)(unsigned aIndex) { return E_NOTIMPL; } michael@0: STDMETHOD(RemoveAtEnd)() { return E_NOTIMPL; } michael@0: michael@0: private: michael@0: nsTArray mList; michael@0: }; michael@0: michael@0: nsresult michael@0: nsMetroFilePicker::ParseFiltersIntoVector(ComPtr>& aVector, michael@0: const nsAString& aFilter, michael@0: bool aAllowAll) michael@0: { michael@0: const char16_t *beg = aFilter.BeginReading(); michael@0: const char16_t *end = aFilter.EndReading(); michael@0: for (const char16_t *cur = beg, *fileTypeStart = beg; cur <= end; ++cur) { michael@0: // Start of a a filetype, example: *.png michael@0: if (cur == end || char16_t(' ') == *cur) { michael@0: int32_t startPos = fileTypeStart - beg; michael@0: int32_t endPos = cur - fileTypeStart - (cur == end ? 0 : 1); michael@0: const nsAString& fileType = Substring(aFilter, michael@0: startPos, michael@0: endPos); michael@0: // There is no way to say show all files in Metro save file picker, so michael@0: // just use .data if * or *.* is specified. michael@0: if (fileType.IsEmpty() || michael@0: fileType.Equals(L"*") || michael@0: fileType.Equals(L"*.*")) { michael@0: HString str; michael@0: if (aAllowAll) { michael@0: str.Set(L"*"); michael@0: aVector->Append(str.Get()); michael@0: } else { michael@0: str.Set(L".data"); michael@0: aVector->Append(str.Get()); michael@0: } michael@0: } else { michael@0: nsAutoString filter(fileType); michael@0: if (filter[0] == L'*') { michael@0: filter.Cut(0, 1); michael@0: } else if (filter[0] != L'.') { michael@0: filter.Insert(L".", 0); michael@0: } michael@0: HString str; michael@0: str.Set(filter.BeginReading()); michael@0: aVector->Append(str.Get()); michael@0: } michael@0: michael@0: fileTypeStart = cur + 1; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMetroFilePicker::AppendFilter(const nsAString& aTitle, michael@0: const nsAString& aFilter) michael@0: { michael@0: HRESULT hr; michael@0: switch(mMode) { michael@0: case nsIFilePicker::modeOpen: michael@0: case nsIFilePicker::modeOpenMultiple: { michael@0: NS_ENSURE_ARG_POINTER(mFileOpenPicker); michael@0: ComPtr> list; michael@0: mFileOpenPicker->get_FileTypeFilter(list.GetAddressOf()); michael@0: nsresult rv = ParseFiltersIntoVector(list, aFilter, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: case nsIFilePicker::modeSave: { michael@0: NS_ENSURE_ARG_POINTER(mFileSavePicker); michael@0: michael@0: ComPtr*>> map; michael@0: hr = mFileSavePicker->get_FileTypeChoices(map.GetAddressOf()); michael@0: AssertRetHRESULT(hr, NS_ERROR_FAILURE); michael@0: michael@0: HString key; michael@0: key.Set(aTitle.BeginReading()); michael@0: michael@0: ComPtr> saveTypes; michael@0: saveTypes = Make(); michael@0: nsresult rv = ParseFiltersIntoVector(saveTypes, aFilter, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (WindowsIsStringEmpty(mFirstTitle.Get())) { michael@0: mFirstTitle.Set(key.Get()); michael@0: } michael@0: michael@0: boolean replaced; michael@0: map->Insert(key.Get(), saveTypes.Get(), &replaced); michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: