| // Copyright (c) 2011 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 "chrome/browser/ui/shell_dialogs.h" |
| |
| #include "base/callback.h" |
| #include "base/file_path.h" |
| #include "base/json/json_reader.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/string_util.h" |
| #include "base/sys_string_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_dialogs.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/views/html_dialog_view.h" |
| #include "chrome/browser/ui/webui/html_dialog_ui.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| #include "grit/generated_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "views/window/non_client_view.h" |
| #include "views/window/window.h" |
| |
| namespace { |
| |
| const char kKeyNamePath[] = "path"; |
| const int kSaveCompletePageIndex = 2; |
| |
| } // namespace |
| |
| // Implementation of SelectFileDialog that shows an UI for choosing a file |
| // or folder using FileBrowseUI. |
| class SelectFileDialogImpl : public SelectFileDialog { |
| public: |
| explicit SelectFileDialogImpl(Listener* listener); |
| |
| // BaseShellDialog implementation. |
| virtual bool IsRunning(gfx::NativeWindow parent_window) const; |
| virtual void ListenerDestroyed(); |
| |
| virtual void set_browser_mode(bool value) { |
| browser_mode_ = value; |
| } |
| |
| protected: |
| // SelectFileDialog implementation. |
| // |params| is user data we pass back via the Listener interface. |
| virtual void SelectFileImpl(Type type, |
| const string16& title, |
| const FilePath& default_path, |
| const FileTypeInfo* file_types, |
| int file_type_index, |
| const FilePath::StringType& default_extension, |
| gfx::NativeWindow owning_window, |
| void* params); |
| |
| private: |
| virtual ~SelectFileDialogImpl(); |
| |
| class FileBrowseDelegate : public HtmlDialogUIDelegate { |
| public: |
| FileBrowseDelegate(SelectFileDialogImpl* owner, |
| Type type, |
| const std::wstring& title, |
| const FilePath& default_path, |
| const FileTypeInfo* file_types, |
| int file_type_index, |
| const FilePath::StringType& default_extension, |
| gfx::NativeWindow parent, |
| void* params); |
| |
| // Owner of this FileBrowseDelegate. |
| scoped_refptr<SelectFileDialogImpl> owner_; |
| |
| // Parent window. |
| gfx::NativeWindow parent_; |
| |
| // The type of dialog we are showing the user. |
| Type type_; |
| |
| // The dialog title. |
| std::wstring title_; |
| |
| // Default path of the file dialog. |
| FilePath default_path_; |
| |
| // The file filters. |
| FileTypeInfo file_types_; |
| |
| // The index of the default selected file filter. |
| // Note: This starts from 1, not 0. |
| int file_type_index_; |
| |
| // Default extension to be added to file if user does not type one. |
| FilePath::StringType default_extension_; |
| |
| // Associated user data. |
| void* params_; |
| |
| private: |
| ~FileBrowseDelegate(); |
| |
| // Overridden from HtmlDialogUI::Delegate: |
| virtual bool IsDialogModal() const; |
| virtual std::wstring GetDialogTitle() const; |
| virtual GURL GetDialogContentURL() const; |
| virtual void GetWebUIMessageHandlers( |
| std::vector<WebUIMessageHandler*>* handlers) const; |
| virtual void GetDialogSize(gfx::Size* size) const; |
| virtual std::string GetDialogArgs() const; |
| virtual void OnDialogClosed(const std::string& json_retval); |
| virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { |
| } |
| virtual bool ShouldShowDialogTitle() const { return true; } |
| virtual bool HandleContextMenu(const ContextMenuParams& params) { |
| return true; |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(FileBrowseDelegate); |
| }; |
| |
| class FileBrowseDelegateHandler : public WebUIMessageHandler { |
| public: |
| explicit FileBrowseDelegateHandler(FileBrowseDelegate* delegate); |
| |
| // WebUIMessageHandler implementation. |
| virtual void RegisterMessages(); |
| |
| // Callback for the "setDialogTitle" message. |
| void HandleSetDialogTitle(const ListValue* args); |
| |
| private: |
| FileBrowseDelegate* delegate_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FileBrowseDelegateHandler); |
| }; |
| |
| // Notification from FileBrowseDelegate when file browse UI is dismissed. |
| void OnDialogClosed(FileBrowseDelegate* delegate, const std::string& json); |
| |
| // Callback method to open HTML |
| void OpenHtmlDialog(gfx::NativeWindow owning_window, |
| FileBrowseDelegate* file_browse_delegate); |
| |
| // The set of all parent windows for which we are currently running dialogs. |
| std::set<gfx::NativeWindow> parents_; |
| |
| // The set of all FileBrowseDelegate that we are currently running. |
| std::set<FileBrowseDelegate*> delegates_; |
| |
| // True when opening in browser, otherwise in OOBE/login mode. |
| bool browser_mode_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl); |
| }; |
| |
| // static |
| SelectFileDialog* SelectFileDialog::Create(Listener* listener) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| return new SelectFileDialogImpl(listener); |
| } |
| |
| SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener) |
| : SelectFileDialog(listener), |
| browser_mode_(true) { |
| } |
| |
| SelectFileDialogImpl::~SelectFileDialogImpl() { |
| // All dialogs should be dismissed by now. |
| DCHECK(parents_.empty() && delegates_.empty()); |
| } |
| |
| bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const { |
| return parent_window && parents_.find(parent_window) != parents_.end(); |
| } |
| |
| void SelectFileDialogImpl::ListenerDestroyed() { |
| listener_ = NULL; |
| } |
| |
| void SelectFileDialogImpl::SelectFileImpl( |
| Type type, |
| const string16& title, |
| const FilePath& default_path, |
| const FileTypeInfo* file_types, |
| int file_type_index, |
| const FilePath::StringType& default_extension, |
| gfx::NativeWindow owning_window, |
| void* params) { |
| std::wstring title_string; |
| if (title.empty()) { |
| int string_id; |
| switch (type) { |
| case SELECT_FOLDER: |
| string_id = IDS_SELECT_FOLDER_DIALOG_TITLE; |
| break; |
| case SELECT_OPEN_FILE: |
| case SELECT_OPEN_MULTI_FILE: |
| string_id = IDS_OPEN_FILE_DIALOG_TITLE; |
| break; |
| case SELECT_SAVEAS_FILE: |
| string_id = IDS_SAVE_AS_DIALOG_TITLE; |
| break; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| title_string = UTF16ToWide(l10n_util::GetStringUTF16(string_id)); |
| } else { |
| title_string = UTF16ToWide(title); |
| } |
| |
| if (owning_window) |
| parents_.insert(owning_window); |
| |
| FileBrowseDelegate* file_browse_delegate = new FileBrowseDelegate(this, |
| type, title_string, default_path, file_types, file_type_index, |
| default_extension, owning_window, params); |
| delegates_.insert(file_browse_delegate); |
| |
| if (browser_mode_) { |
| Browser* browser = BrowserList::GetLastActive(); |
| // As SelectFile may be invoked after a delay, it is entirely possible for |
| // it be invoked when no browser is around. Silently ignore this case. |
| if (browser) |
| browser->BrowserShowHtmlDialog(file_browse_delegate, owning_window); |
| } else { |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| NewRunnableMethod(this, |
| &SelectFileDialogImpl::OpenHtmlDialog, |
| owning_window, |
| file_browse_delegate)); |
| } |
| } |
| |
| void SelectFileDialogImpl::OnDialogClosed(FileBrowseDelegate* delegate, |
| const std::string& json) { |
| // Nothing to do if listener_ is gone. |
| |
| if (!listener_) |
| return; |
| |
| bool notification_fired = false; |
| |
| if (!json.empty()) { |
| scoped_ptr<Value> value(base::JSONReader::Read(json, false)); |
| if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) { |
| // Bad json value returned. |
| NOTREACHED(); |
| } else { |
| const DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); |
| if (delegate->type_ == SELECT_OPEN_FILE || |
| delegate->type_ == SELECT_SAVEAS_FILE || |
| delegate->type_ == SELECT_FOLDER) { |
| std::string path_string; |
| if (dict->HasKey(kKeyNamePath) && |
| dict->GetString(kKeyNamePath, &path_string)) { |
| #if defined(OS_WIN) |
| FilePath path(base::SysUTF8ToWide(path_string)); |
| #else |
| FilePath path( |
| base::SysWideToNativeMB(base::SysUTF8ToWide(path_string))); |
| #endif |
| listener_-> |
| FileSelected(path, kSaveCompletePageIndex, delegate->params_); |
| notification_fired = true; |
| } |
| } else if (delegate->type_ == SELECT_OPEN_MULTI_FILE) { |
| ListValue* paths_value = NULL; |
| if (dict->HasKey(kKeyNamePath) && |
| dict->GetList(kKeyNamePath, &paths_value) && |
| paths_value) { |
| std::vector<FilePath> paths; |
| paths.reserve(paths_value->GetSize()); |
| for (size_t i = 0; i < paths_value->GetSize(); ++i) { |
| std::string path_string; |
| if (paths_value->GetString(i, &path_string) && |
| !path_string.empty()) { |
| #if defined(OS_WIN) |
| FilePath path(base::SysUTF8ToWide(path_string)); |
| #else |
| FilePath path( |
| base::SysWideToNativeMB(base::SysUTF8ToWide(path_string))); |
| #endif |
| paths.push_back(path); |
| } |
| } |
| |
| listener_->MultiFilesSelected(paths, delegate->params_); |
| notification_fired = true; |
| } |
| } else { |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| // Always notify listener when dialog is dismissed. |
| if (!notification_fired) |
| listener_->FileSelectionCanceled(delegate->params_); |
| |
| parents_.erase(delegate->parent_); |
| delegates_.erase(delegate); |
| } |
| |
| void SelectFileDialogImpl::OpenHtmlDialog( |
| gfx::NativeWindow owning_window, |
| FileBrowseDelegate* file_browse_delegate) { |
| browser::ShowHtmlDialog(owning_window, |
| ProfileManager::GetDefaultProfile(), |
| file_browse_delegate); |
| } |
| |
| SelectFileDialogImpl::FileBrowseDelegate::FileBrowseDelegate( |
| SelectFileDialogImpl* owner, |
| Type type, |
| const std::wstring& title, |
| const FilePath& default_path, |
| const FileTypeInfo* file_types, |
| int file_type_index, |
| const FilePath::StringType& default_extension, |
| gfx::NativeWindow parent, |
| void* params) |
| : owner_(owner), |
| parent_(parent), |
| type_(type), |
| title_(title), |
| default_path_(default_path), |
| file_type_index_(file_type_index), |
| default_extension_(default_extension), |
| params_(params) { |
| if (file_types) |
| file_types_ = *file_types; |
| else |
| file_types_.include_all_files = true; |
| } |
| |
| SelectFileDialogImpl::FileBrowseDelegate::~FileBrowseDelegate() { |
| } |
| |
| bool SelectFileDialogImpl::FileBrowseDelegate::IsDialogModal() const { |
| return true; |
| } |
| |
| std::wstring SelectFileDialogImpl::FileBrowseDelegate::GetDialogTitle() const { |
| return title_; |
| } |
| |
| GURL SelectFileDialogImpl::FileBrowseDelegate::GetDialogContentURL() const { |
| std::string url_string(chrome::kChromeUIFileBrowseURL); |
| |
| return GURL(url_string); |
| } |
| |
| void SelectFileDialogImpl::FileBrowseDelegate::GetWebUIMessageHandlers( |
| std::vector<WebUIMessageHandler*>* handlers) const { |
| handlers->push_back(new FileBrowseDelegateHandler( |
| const_cast<FileBrowseDelegate*>(this))); |
| return; |
| } |
| |
| void SelectFileDialogImpl::FileBrowseDelegate::GetDialogSize( |
| gfx::Size* size) const { |
| size->SetSize(320, 240); |
| } |
| |
| std::string SelectFileDialogImpl::FileBrowseDelegate::GetDialogArgs() const { |
| // SelectFile inputs as json. |
| // { |
| // "type" : "open", // (or "open_multiple", "save", "folder" |
| // "all_files" : true, |
| // "file_types" : { |
| // "exts" : [ ["htm", "html"], ["txt"] ], |
| // "desc" : [ "HTML files", "Text files" ], |
| // }, |
| // "file_type_index" : 1, // 1-based file type index. |
| // } |
| // See browser/ui/shell_dialogs.h for more details. |
| |
| std::string type_string; |
| switch (type_) { |
| case SELECT_FOLDER: |
| type_string = "folder"; |
| break; |
| case SELECT_OPEN_FILE: |
| type_string = "open"; |
| break; |
| case SELECT_OPEN_MULTI_FILE: |
| type_string = "open_multiple"; |
| break; |
| case SELECT_SAVEAS_FILE: |
| type_string = "save"; |
| break; |
| default: |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| std::string exts_list; |
| std::string desc_list; |
| for (size_t i = 0; i < file_types_.extensions.size(); ++i) { |
| DCHECK(!file_types_.extensions[i].empty()); |
| |
| std::string exts; |
| for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) { |
| if (!exts.empty()) |
| exts.append(","); |
| base::StringAppendF(&exts, "\"%s\"", |
| file_types_.extensions[i][j].c_str()); |
| } |
| |
| if (!exts_list.empty()) |
| exts_list.append(","); |
| base::StringAppendF(&exts_list, "[%s]", exts.c_str()); |
| |
| std::string desc; |
| if (i < file_types_.extension_description_overrides.size()) { |
| desc = UTF16ToUTF8(file_types_.extension_description_overrides[i]); |
| } else { |
| #if defined(OS_WIN) |
| desc = WideToUTF8(file_types_.extensions[i][0]); |
| #elif defined(OS_POSIX) |
| desc = file_types_.extensions[i][0]; |
| #else |
| NOTIMPLEMENTED(); |
| #endif |
| } |
| |
| if (!desc_list.empty()) |
| desc_list.append(","); |
| base::StringAppendF(&desc_list, "\"%s\"", desc.c_str()); |
| } |
| |
| std::string filename = default_path_.BaseName().value(); |
| |
| return StringPrintf("{" |
| "\"type\":\"%s\"," |
| "\"all_files\":%s," |
| "\"current_file\":\"%s\"," |
| "\"file_types\":{\"exts\":[%s],\"desc\":[%s]}," |
| "\"file_type_index\":%d" |
| "}", |
| type_string.c_str(), |
| file_types_.include_all_files ? "true" : "false", |
| filename.c_str(), |
| exts_list.c_str(), |
| desc_list.c_str(), |
| file_type_index_); |
| } |
| |
| void SelectFileDialogImpl::FileBrowseDelegate::OnDialogClosed( |
| const std::string& json_retval) { |
| owner_->OnDialogClosed(this, json_retval); |
| delete this; |
| return; |
| } |
| |
| SelectFileDialogImpl::FileBrowseDelegateHandler::FileBrowseDelegateHandler( |
| FileBrowseDelegate* delegate) |
| : delegate_(delegate) { |
| } |
| |
| void SelectFileDialogImpl::FileBrowseDelegateHandler::RegisterMessages() { |
| web_ui_->RegisterMessageCallback("setDialogTitle", |
| NewCallback(this, &FileBrowseDelegateHandler::HandleSetDialogTitle)); |
| } |
| |
| void SelectFileDialogImpl::FileBrowseDelegateHandler::HandleSetDialogTitle( |
| const ListValue* args) { |
| std::wstring new_title = UTF16ToWideHack(ExtractStringValue(args)); |
| if (new_title != delegate_->title_) { |
| delegate_->title_ = new_title; |
| |
| // Notify the containing view about the title change. |
| // The current HtmlDialogUIDelegate and HtmlDialogView does not support |
| // dynamic title change. We hijacked the mechanism between HTMLDialogUI |
| // and HtmlDialogView to get the HtmlDialogView and forced it to update |
| // its title. |
| // TODO(xiyuan): Change this when the infrastructure is improved. |
| HtmlDialogUIDelegate** delegate = HtmlDialogUI::GetPropertyAccessor(). |
| GetProperty(web_ui_->tab_contents()->property_bag()); |
| HtmlDialogView* containing_view = static_cast<HtmlDialogView*>(*delegate); |
| DCHECK(containing_view); |
| |
| containing_view->GetWindow()->UpdateWindowTitle(); |
| containing_view->GetWindow()->non_client_view()->SchedulePaint(); |
| } |
| } |