1 // Copyright (c) 2011 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. 4 5 #include "chrome/browser/ui/shell_dialogs.h" 6 7 #include "base/callback.h" 8 #include "base/file_path.h" 9 #include "base/json/json_reader.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/string_util.h" 12 #include "base/sys_string_conversions.h" 13 #include "base/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/profiles/profile_manager.h" 16 #include "chrome/browser/ui/browser.h" 17 #include "chrome/browser/ui/browser_dialogs.h" 18 #include "chrome/browser/ui/browser_list.h" 19 #include "chrome/browser/ui/views/html_dialog_view.h" 20 #include "chrome/browser/ui/webui/html_dialog_ui.h" 21 #include "chrome/common/url_constants.h" 22 #include "content/browser/browser_thread.h" 23 #include "content/browser/tab_contents/tab_contents.h" 24 #include "grit/generated_resources.h" 25 #include "ui/base/l10n/l10n_util.h" 26 #include "views/window/non_client_view.h" 27 #include "views/window/window.h" 28 29 namespace { 30 31 const char kKeyNamePath[] = "path"; 32 const int kSaveCompletePageIndex = 2; 33 34 } // namespace 35 36 // Implementation of SelectFileDialog that shows an UI for choosing a file 37 // or folder using FileBrowseUI. 38 class SelectFileDialogImpl : public SelectFileDialog { 39 public: 40 explicit SelectFileDialogImpl(Listener* listener); 41 42 // BaseShellDialog implementation. 43 virtual bool IsRunning(gfx::NativeWindow parent_window) const; 44 virtual void ListenerDestroyed(); 45 46 virtual void set_browser_mode(bool value) { 47 browser_mode_ = value; 48 } 49 50 protected: 51 // SelectFileDialog implementation. 52 // |params| is user data we pass back via the Listener interface. 53 virtual void SelectFileImpl(Type type, 54 const string16& title, 55 const FilePath& default_path, 56 const FileTypeInfo* file_types, 57 int file_type_index, 58 const FilePath::StringType& default_extension, 59 gfx::NativeWindow owning_window, 60 void* params); 61 62 private: 63 virtual ~SelectFileDialogImpl(); 64 65 class FileBrowseDelegate : public HtmlDialogUIDelegate { 66 public: 67 FileBrowseDelegate(SelectFileDialogImpl* owner, 68 Type type, 69 const std::wstring& title, 70 const FilePath& default_path, 71 const FileTypeInfo* file_types, 72 int file_type_index, 73 const FilePath::StringType& default_extension, 74 gfx::NativeWindow parent, 75 void* params); 76 77 // Owner of this FileBrowseDelegate. 78 scoped_refptr<SelectFileDialogImpl> owner_; 79 80 // Parent window. 81 gfx::NativeWindow parent_; 82 83 // The type of dialog we are showing the user. 84 Type type_; 85 86 // The dialog title. 87 std::wstring title_; 88 89 // Default path of the file dialog. 90 FilePath default_path_; 91 92 // The file filters. 93 FileTypeInfo file_types_; 94 95 // The index of the default selected file filter. 96 // Note: This starts from 1, not 0. 97 int file_type_index_; 98 99 // Default extension to be added to file if user does not type one. 100 FilePath::StringType default_extension_; 101 102 // Associated user data. 103 void* params_; 104 105 private: 106 ~FileBrowseDelegate(); 107 108 // Overridden from HtmlDialogUI::Delegate: 109 virtual bool IsDialogModal() const; 110 virtual std::wstring GetDialogTitle() const; 111 virtual GURL GetDialogContentURL() const; 112 virtual void GetWebUIMessageHandlers( 113 std::vector<WebUIMessageHandler*>* handlers) const; 114 virtual void GetDialogSize(gfx::Size* size) const; 115 virtual std::string GetDialogArgs() const; 116 virtual void OnDialogClosed(const std::string& json_retval); 117 virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { 118 } 119 virtual bool ShouldShowDialogTitle() const { return true; } 120 virtual bool HandleContextMenu(const ContextMenuParams& params) { 121 return true; 122 } 123 124 DISALLOW_COPY_AND_ASSIGN(FileBrowseDelegate); 125 }; 126 127 class FileBrowseDelegateHandler : public WebUIMessageHandler { 128 public: 129 explicit FileBrowseDelegateHandler(FileBrowseDelegate* delegate); 130 131 // WebUIMessageHandler implementation. 132 virtual void RegisterMessages(); 133 134 // Callback for the "setDialogTitle" message. 135 void HandleSetDialogTitle(const ListValue* args); 136 137 private: 138 FileBrowseDelegate* delegate_; 139 140 DISALLOW_COPY_AND_ASSIGN(FileBrowseDelegateHandler); 141 }; 142 143 // Notification from FileBrowseDelegate when file browse UI is dismissed. 144 void OnDialogClosed(FileBrowseDelegate* delegate, const std::string& json); 145 146 // Callback method to open HTML 147 void OpenHtmlDialog(gfx::NativeWindow owning_window, 148 FileBrowseDelegate* file_browse_delegate); 149 150 // The set of all parent windows for which we are currently running dialogs. 151 std::set<gfx::NativeWindow> parents_; 152 153 // The set of all FileBrowseDelegate that we are currently running. 154 std::set<FileBrowseDelegate*> delegates_; 155 156 // True when opening in browser, otherwise in OOBE/login mode. 157 bool browser_mode_; 158 159 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl); 160 }; 161 162 // static 163 SelectFileDialog* SelectFileDialog::Create(Listener* listener) { 164 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO)); 165 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 166 return new SelectFileDialogImpl(listener); 167 } 168 169 SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener) 170 : SelectFileDialog(listener), 171 browser_mode_(true) { 172 } 173 174 SelectFileDialogImpl::~SelectFileDialogImpl() { 175 // All dialogs should be dismissed by now. 176 DCHECK(parents_.empty() && delegates_.empty()); 177 } 178 179 bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const { 180 return parent_window && parents_.find(parent_window) != parents_.end(); 181 } 182 183 void SelectFileDialogImpl::ListenerDestroyed() { 184 listener_ = NULL; 185 } 186 187 void SelectFileDialogImpl::SelectFileImpl( 188 Type type, 189 const string16& title, 190 const FilePath& default_path, 191 const FileTypeInfo* file_types, 192 int file_type_index, 193 const FilePath::StringType& default_extension, 194 gfx::NativeWindow owning_window, 195 void* params) { 196 std::wstring title_string; 197 if (title.empty()) { 198 int string_id; 199 switch (type) { 200 case SELECT_FOLDER: 201 string_id = IDS_SELECT_FOLDER_DIALOG_TITLE; 202 break; 203 case SELECT_OPEN_FILE: 204 case SELECT_OPEN_MULTI_FILE: 205 string_id = IDS_OPEN_FILE_DIALOG_TITLE; 206 break; 207 case SELECT_SAVEAS_FILE: 208 string_id = IDS_SAVE_AS_DIALOG_TITLE; 209 break; 210 default: 211 NOTREACHED(); 212 return; 213 } 214 title_string = UTF16ToWide(l10n_util::GetStringUTF16(string_id)); 215 } else { 216 title_string = UTF16ToWide(title); 217 } 218 219 if (owning_window) 220 parents_.insert(owning_window); 221 222 FileBrowseDelegate* file_browse_delegate = new FileBrowseDelegate(this, 223 type, title_string, default_path, file_types, file_type_index, 224 default_extension, owning_window, params); 225 delegates_.insert(file_browse_delegate); 226 227 if (browser_mode_) { 228 Browser* browser = BrowserList::GetLastActive(); 229 // As SelectFile may be invoked after a delay, it is entirely possible for 230 // it be invoked when no browser is around. Silently ignore this case. 231 if (browser) 232 browser->BrowserShowHtmlDialog(file_browse_delegate, owning_window); 233 } else { 234 BrowserThread::PostTask( 235 BrowserThread::UI, 236 FROM_HERE, 237 NewRunnableMethod(this, 238 &SelectFileDialogImpl::OpenHtmlDialog, 239 owning_window, 240 file_browse_delegate)); 241 } 242 } 243 244 void SelectFileDialogImpl::OnDialogClosed(FileBrowseDelegate* delegate, 245 const std::string& json) { 246 // Nothing to do if listener_ is gone. 247 248 if (!listener_) 249 return; 250 251 bool notification_fired = false; 252 253 if (!json.empty()) { 254 scoped_ptr<Value> value(base::JSONReader::Read(json, false)); 255 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) { 256 // Bad json value returned. 257 NOTREACHED(); 258 } else { 259 const DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); 260 if (delegate->type_ == SELECT_OPEN_FILE || 261 delegate->type_ == SELECT_SAVEAS_FILE || 262 delegate->type_ == SELECT_FOLDER) { 263 std::string path_string; 264 if (dict->HasKey(kKeyNamePath) && 265 dict->GetString(kKeyNamePath, &path_string)) { 266 #if defined(OS_WIN) 267 FilePath path(base::SysUTF8ToWide(path_string)); 268 #else 269 FilePath path( 270 base::SysWideToNativeMB(base::SysUTF8ToWide(path_string))); 271 #endif 272 listener_-> 273 FileSelected(path, kSaveCompletePageIndex, delegate->params_); 274 notification_fired = true; 275 } 276 } else if (delegate->type_ == SELECT_OPEN_MULTI_FILE) { 277 ListValue* paths_value = NULL; 278 if (dict->HasKey(kKeyNamePath) && 279 dict->GetList(kKeyNamePath, &paths_value) && 280 paths_value) { 281 std::vector<FilePath> paths; 282 paths.reserve(paths_value->GetSize()); 283 for (size_t i = 0; i < paths_value->GetSize(); ++i) { 284 std::string path_string; 285 if (paths_value->GetString(i, &path_string) && 286 !path_string.empty()) { 287 #if defined(OS_WIN) 288 FilePath path(base::SysUTF8ToWide(path_string)); 289 #else 290 FilePath path( 291 base::SysWideToNativeMB(base::SysUTF8ToWide(path_string))); 292 #endif 293 paths.push_back(path); 294 } 295 } 296 297 listener_->MultiFilesSelected(paths, delegate->params_); 298 notification_fired = true; 299 } 300 } else { 301 NOTREACHED(); 302 } 303 } 304 } 305 306 // Always notify listener when dialog is dismissed. 307 if (!notification_fired) 308 listener_->FileSelectionCanceled(delegate->params_); 309 310 parents_.erase(delegate->parent_); 311 delegates_.erase(delegate); 312 } 313 314 void SelectFileDialogImpl::OpenHtmlDialog( 315 gfx::NativeWindow owning_window, 316 FileBrowseDelegate* file_browse_delegate) { 317 browser::ShowHtmlDialog(owning_window, 318 ProfileManager::GetDefaultProfile(), 319 file_browse_delegate); 320 } 321 322 SelectFileDialogImpl::FileBrowseDelegate::FileBrowseDelegate( 323 SelectFileDialogImpl* owner, 324 Type type, 325 const std::wstring& title, 326 const FilePath& default_path, 327 const FileTypeInfo* file_types, 328 int file_type_index, 329 const FilePath::StringType& default_extension, 330 gfx::NativeWindow parent, 331 void* params) 332 : owner_(owner), 333 parent_(parent), 334 type_(type), 335 title_(title), 336 default_path_(default_path), 337 file_type_index_(file_type_index), 338 default_extension_(default_extension), 339 params_(params) { 340 if (file_types) 341 file_types_ = *file_types; 342 else 343 file_types_.include_all_files = true; 344 } 345 346 SelectFileDialogImpl::FileBrowseDelegate::~FileBrowseDelegate() { 347 } 348 349 bool SelectFileDialogImpl::FileBrowseDelegate::IsDialogModal() const { 350 return true; 351 } 352 353 std::wstring SelectFileDialogImpl::FileBrowseDelegate::GetDialogTitle() const { 354 return title_; 355 } 356 357 GURL SelectFileDialogImpl::FileBrowseDelegate::GetDialogContentURL() const { 358 std::string url_string(chrome::kChromeUIFileBrowseURL); 359 360 return GURL(url_string); 361 } 362 363 void SelectFileDialogImpl::FileBrowseDelegate::GetWebUIMessageHandlers( 364 std::vector<WebUIMessageHandler*>* handlers) const { 365 handlers->push_back(new FileBrowseDelegateHandler( 366 const_cast<FileBrowseDelegate*>(this))); 367 return; 368 } 369 370 void SelectFileDialogImpl::FileBrowseDelegate::GetDialogSize( 371 gfx::Size* size) const { 372 size->SetSize(320, 240); 373 } 374 375 std::string SelectFileDialogImpl::FileBrowseDelegate::GetDialogArgs() const { 376 // SelectFile inputs as json. 377 // { 378 // "type" : "open", // (or "open_multiple", "save", "folder" 379 // "all_files" : true, 380 // "file_types" : { 381 // "exts" : [ ["htm", "html"], ["txt"] ], 382 // "desc" : [ "HTML files", "Text files" ], 383 // }, 384 // "file_type_index" : 1, // 1-based file type index. 385 // } 386 // See browser/ui/shell_dialogs.h for more details. 387 388 std::string type_string; 389 switch (type_) { 390 case SELECT_FOLDER: 391 type_string = "folder"; 392 break; 393 case SELECT_OPEN_FILE: 394 type_string = "open"; 395 break; 396 case SELECT_OPEN_MULTI_FILE: 397 type_string = "open_multiple"; 398 break; 399 case SELECT_SAVEAS_FILE: 400 type_string = "save"; 401 break; 402 default: 403 NOTREACHED(); 404 return std::string(); 405 } 406 407 std::string exts_list; 408 std::string desc_list; 409 for (size_t i = 0; i < file_types_.extensions.size(); ++i) { 410 DCHECK(!file_types_.extensions[i].empty()); 411 412 std::string exts; 413 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) { 414 if (!exts.empty()) 415 exts.append(","); 416 base::StringAppendF(&exts, "\"%s\"", 417 file_types_.extensions[i][j].c_str()); 418 } 419 420 if (!exts_list.empty()) 421 exts_list.append(","); 422 base::StringAppendF(&exts_list, "[%s]", exts.c_str()); 423 424 std::string desc; 425 if (i < file_types_.extension_description_overrides.size()) { 426 desc = UTF16ToUTF8(file_types_.extension_description_overrides[i]); 427 } else { 428 #if defined(OS_WIN) 429 desc = WideToUTF8(file_types_.extensions[i][0]); 430 #elif defined(OS_POSIX) 431 desc = file_types_.extensions[i][0]; 432 #else 433 NOTIMPLEMENTED(); 434 #endif 435 } 436 437 if (!desc_list.empty()) 438 desc_list.append(","); 439 base::StringAppendF(&desc_list, "\"%s\"", desc.c_str()); 440 } 441 442 std::string filename = default_path_.BaseName().value(); 443 444 return StringPrintf("{" 445 "\"type\":\"%s\"," 446 "\"all_files\":%s," 447 "\"current_file\":\"%s\"," 448 "\"file_types\":{\"exts\":[%s],\"desc\":[%s]}," 449 "\"file_type_index\":%d" 450 "}", 451 type_string.c_str(), 452 file_types_.include_all_files ? "true" : "false", 453 filename.c_str(), 454 exts_list.c_str(), 455 desc_list.c_str(), 456 file_type_index_); 457 } 458 459 void SelectFileDialogImpl::FileBrowseDelegate::OnDialogClosed( 460 const std::string& json_retval) { 461 owner_->OnDialogClosed(this, json_retval); 462 delete this; 463 return; 464 } 465 466 SelectFileDialogImpl::FileBrowseDelegateHandler::FileBrowseDelegateHandler( 467 FileBrowseDelegate* delegate) 468 : delegate_(delegate) { 469 } 470 471 void SelectFileDialogImpl::FileBrowseDelegateHandler::RegisterMessages() { 472 web_ui_->RegisterMessageCallback("setDialogTitle", 473 NewCallback(this, &FileBrowseDelegateHandler::HandleSetDialogTitle)); 474 } 475 476 void SelectFileDialogImpl::FileBrowseDelegateHandler::HandleSetDialogTitle( 477 const ListValue* args) { 478 std::wstring new_title = UTF16ToWideHack(ExtractStringValue(args)); 479 if (new_title != delegate_->title_) { 480 delegate_->title_ = new_title; 481 482 // Notify the containing view about the title change. 483 // The current HtmlDialogUIDelegate and HtmlDialogView does not support 484 // dynamic title change. We hijacked the mechanism between HTMLDialogUI 485 // and HtmlDialogView to get the HtmlDialogView and forced it to update 486 // its title. 487 // TODO(xiyuan): Change this when the infrastructure is improved. 488 HtmlDialogUIDelegate** delegate = HtmlDialogUI::GetPropertyAccessor(). 489 GetProperty(web_ui_->tab_contents()->property_bag()); 490 HtmlDialogView* containing_view = static_cast<HtmlDialogView*>(*delegate); 491 DCHECK(containing_view); 492 493 containing_view->GetWindow()->UpdateWindowTitle(); 494 containing_view->GetWindow()->non_client_view()->SchedulePaint(); 495 } 496 } 497