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