// 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 "ui/shell_dialogs/select_file_dialog.h" #import #include #include #include #include #include "base/files/file_util.h" #include "base/logging.h" #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #import "base/mac/scoped_nsobject.h" #include "base/strings/sys_string_conversions.h" #include "base/threading/thread_restrictions.h" #import "ui/base/cocoa/nib_loading.h" #include "ui/base/l10n/l10n_util_mac.h" #include "ui/strings/grit/ui_strings.h" namespace { const int kFileTypePopupTag = 1234; CFStringRef CreateUTIFromExtension(const base::FilePath::StringType& ext) { base::ScopedCFTypeRef ext_cf(base::SysUTF8ToCFStringRef(ext)); return UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension, ext_cf.get(), NULL); } } // namespace class SelectFileDialogImpl; // A bridge class to act as the modal delegate to the save/open sheet and send // the results to the C++ class. @interface SelectFileDialogBridge : NSObject { @private SelectFileDialogImpl* selectFileDialogImpl_; // WEAK; owns us } - (id)initWithSelectFileDialogImpl:(SelectFileDialogImpl*)s; - (void)endedPanel:(NSSavePanel*)panel didCancel:(bool)did_cancel type:(ui::SelectFileDialog::Type)type parentWindow:(NSWindow*)parentWindow; // NSSavePanel delegate method - (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url; @end // Implementation of SelectFileDialog that shows Cocoa dialogs for choosing a // file or folder. class SelectFileDialogImpl : public ui::SelectFileDialog { public: explicit SelectFileDialogImpl(Listener* listener, ui::SelectFilePolicy* policy); // BaseShellDialog implementation. bool IsRunning(gfx::NativeWindow parent_window) const override; void ListenerDestroyed() override; // Callback from ObjC bridge. void FileWasSelected(NSSavePanel* dialog, NSWindow* parent_window, bool was_cancelled, bool is_multi, const std::vector& files, int index); protected: // SelectFileDialog implementation. // |params| is user data we pass back via the Listener interface. void SelectFileImpl(Type type, const base::string16& title, const base::FilePath& default_path, const FileTypeInfo* file_types, int file_type_index, const base::FilePath::StringType& default_extension, gfx::NativeWindow owning_window, void* params) override; private: ~SelectFileDialogImpl() override; // Gets the accessory view for the save dialog. NSView* GetAccessoryView(const FileTypeInfo* file_types, int file_type_index); bool HasMultipleFileTypeChoicesImpl() override; // The bridge for results from Cocoa to return to us. base::scoped_nsobject bridge_; // A map from file dialogs to the |params| user data associated with them. std::map params_map_; // The set of all parent windows for which we are currently running dialogs. std::set parents_; // A map from file dialogs to their types. std::map type_map_; bool hasMultipleFileTypeChoices_; DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl); }; SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener, ui::SelectFilePolicy* policy) : SelectFileDialog(listener, policy), bridge_([[SelectFileDialogBridge alloc] initWithSelectFileDialogImpl:this]) { } bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const { return parents_.find(parent_window) != parents_.end(); } void SelectFileDialogImpl::ListenerDestroyed() { listener_ = NULL; } void SelectFileDialogImpl::FileWasSelected( NSSavePanel* dialog, NSWindow* parent_window, bool was_cancelled, bool is_multi, const std::vector& files, int index) { void* params = params_map_[dialog]; params_map_.erase(dialog); parents_.erase(parent_window); type_map_.erase(dialog); [dialog setDelegate:nil]; if (!listener_) return; if (was_cancelled || files.empty()) { listener_->FileSelectionCanceled(params); } else { if (is_multi) { listener_->MultiFilesSelected(files, params); } else { listener_->FileSelected(files[0], index, params); } } } void SelectFileDialogImpl::SelectFileImpl( Type type, const base::string16& title, const base::FilePath& default_path, const FileTypeInfo* file_types, int file_type_index, const base::FilePath::StringType& default_extension, gfx::NativeWindow owning_window, void* params) { DCHECK(type == SELECT_FOLDER || type == SELECT_UPLOAD_FOLDER || type == SELECT_OPEN_FILE || type == SELECT_OPEN_MULTI_FILE || type == SELECT_SAVEAS_FILE); parents_.insert(owning_window); // Note: we need to retain the dialog as owning_window can be null. // (See http://crbug.com/29213 .) NSSavePanel* dialog; if (type == SELECT_SAVEAS_FILE) dialog = [[NSSavePanel savePanel] retain]; else dialog = [[NSOpenPanel openPanel] retain]; if (!title.empty()) [dialog setMessage:base::SysUTF16ToNSString(title)]; NSString* default_dir = nil; NSString* default_filename = nil; if (!default_path.empty()) { // The file dialog is going to do a ton of stats anyway. Not much // point in eliminating this one. base::ThreadRestrictions::ScopedAllowIO allow_io; if (base::DirectoryExists(default_path)) { default_dir = base::SysUTF8ToNSString(default_path.value()); } else { default_dir = base::SysUTF8ToNSString(default_path.DirName().value()); default_filename = base::SysUTF8ToNSString(default_path.BaseName().value()); } } NSArray* allowed_file_types = nil; if (file_types) { if (!file_types->extensions.empty()) { // While the example given in the header for FileTypeInfo lists an example // |file_types->extensions| value as // { { "htm", "html" }, { "txt" } } // it is not always the case that the given extensions in one of the sub- // lists are all synonyms. In fact, in the case of a