Home | History | Annotate | Download | only in views
      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