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
29namespace {
30
31const char kKeyNamePath[] = "path";
32const int kSaveCompletePageIndex = 2;
33
34}  // namespace
35
36// Implementation of SelectFileDialog that shows an UI for choosing a file
37// or folder using FileBrowseUI.
38class 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
163SelectFileDialog* SelectFileDialog::Create(Listener* listener) {
164  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
165  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
166  return new SelectFileDialogImpl(listener);
167}
168
169SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener)
170    : SelectFileDialog(listener),
171      browser_mode_(true) {
172}
173
174SelectFileDialogImpl::~SelectFileDialogImpl() {
175  // All dialogs should be dismissed by now.
176  DCHECK(parents_.empty() && delegates_.empty());
177}
178
179bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const {
180  return parent_window && parents_.find(parent_window) != parents_.end();
181}
182
183void SelectFileDialogImpl::ListenerDestroyed() {
184  listener_ = NULL;
185}
186
187void 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
244void 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
314void 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
322SelectFileDialogImpl::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
346SelectFileDialogImpl::FileBrowseDelegate::~FileBrowseDelegate() {
347}
348
349bool SelectFileDialogImpl::FileBrowseDelegate::IsDialogModal() const {
350  return true;
351}
352
353std::wstring SelectFileDialogImpl::FileBrowseDelegate::GetDialogTitle() const {
354  return title_;
355}
356
357GURL SelectFileDialogImpl::FileBrowseDelegate::GetDialogContentURL() const {
358  std::string url_string(chrome::kChromeUIFileBrowseURL);
359
360  return GURL(url_string);
361}
362
363void SelectFileDialogImpl::FileBrowseDelegate::GetWebUIMessageHandlers(
364    std::vector<WebUIMessageHandler*>* handlers) const {
365  handlers->push_back(new FileBrowseDelegateHandler(
366      const_cast<FileBrowseDelegate*>(this)));
367  return;
368}
369
370void SelectFileDialogImpl::FileBrowseDelegate::GetDialogSize(
371    gfx::Size* size) const {
372  size->SetSize(320, 240);
373}
374
375std::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
459void SelectFileDialogImpl::FileBrowseDelegate::OnDialogClosed(
460    const std::string& json_retval) {
461  owner_->OnDialogClosed(this, json_retval);
462  delete this;
463  return;
464}
465
466SelectFileDialogImpl::FileBrowseDelegateHandler::FileBrowseDelegateHandler(
467    FileBrowseDelegate* delegate)
468    : delegate_(delegate) {
469}
470
471void SelectFileDialogImpl::FileBrowseDelegateHandler::RegisterMessages() {
472  web_ui_->RegisterMessageCallback("setDialogTitle",
473      NewCallback(this, &FileBrowseDelegateHandler::HandleSetDialogTitle));
474}
475
476void 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