// 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();
}
}