1 // Copyright (c) 2013 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 <gtk/gtk.h> 6 #include <map> 7 #include <set> 8 #include <vector> 9 10 #include "base/file_util.h" 11 #include "base/logging.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/sys_string_conversions.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/threading/thread.h" 18 #include "base/threading/thread_restrictions.h" 19 #include "grit/ui_strings.h" 20 #include "ui/base/gtk/gtk_signal.h" 21 #include "ui/base/l10n/l10n_util.h" 22 #include "ui/shell_dialogs/gtk/select_file_dialog_impl.h" 23 #include "ui/shell_dialogs/select_file_dialog.h" 24 25 namespace { 26 27 // Makes sure that .jpg also shows .JPG. 28 gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info, 29 std::string* file_extension) { 30 return EndsWith(file_info->filename, *file_extension, false); 31 } 32 33 // Deletes |data| when gtk_file_filter_add_custom() is done with it. 34 void OnFileFilterDataDestroyed(std::string* file_extension) { 35 delete file_extension; 36 } 37 38 // Implementation of SelectFileDialog that shows a Gtk common dialog for 39 // choosing a file or folder. This acts as a modal dialog. 40 class SelectFileDialogImplGTK : public ui::SelectFileDialogImpl { 41 public: 42 explicit SelectFileDialogImplGTK(Listener* listener, 43 ui::SelectFilePolicy* policy); 44 45 protected: 46 virtual ~SelectFileDialogImplGTK(); 47 48 // SelectFileDialog implementation. 49 // |params| is user data we pass back via the Listener interface. 50 virtual void SelectFileImpl( 51 Type type, 52 const base::string16& title, 53 const base::FilePath& default_path, 54 const FileTypeInfo* file_types, 55 int file_type_index, 56 const base::FilePath::StringType& default_extension, 57 gfx::NativeWindow owning_window, 58 void* params) OVERRIDE; 59 60 private: 61 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE; 62 63 // Add the filters from |file_types_| to |chooser|. 64 void AddFilters(GtkFileChooser* chooser); 65 66 // Notifies the listener that a single file was chosen. 67 void FileSelected(GtkWidget* dialog, const base::FilePath& path); 68 69 // Notifies the listener that multiple files were chosen. 70 void MultiFilesSelected(GtkWidget* dialog, 71 const std::vector<base::FilePath>& files); 72 73 // Notifies the listener that no file was chosen (the action was canceled). 74 // Dialog is passed so we can find that |params| pointer that was passed to 75 // us when we were told to show the dialog. 76 void FileNotSelected(GtkWidget* dialog); 77 78 GtkWidget* CreateSelectFolderDialog( 79 Type type, 80 const std::string& title, 81 const base::FilePath& default_path, 82 gfx::NativeWindow parent); 83 84 GtkWidget* CreateFileOpenDialog(const std::string& title, 85 const base::FilePath& default_path, gfx::NativeWindow parent); 86 87 GtkWidget* CreateMultiFileOpenDialog(const std::string& title, 88 const base::FilePath& default_path, gfx::NativeWindow parent); 89 90 GtkWidget* CreateSaveAsDialog(const std::string& title, 91 const base::FilePath& default_path, gfx::NativeWindow parent); 92 93 // Removes and returns the |params| associated with |dialog| from 94 // |params_map_|. 95 void* PopParamsForDialog(GtkWidget* dialog); 96 97 // Take care of internal data structures when a file dialog is destroyed. 98 void FileDialogDestroyed(GtkWidget* dialog); 99 100 // Check whether response_id corresponds to the user cancelling/closing the 101 // dialog. Used as a helper for the below callbacks. 102 bool IsCancelResponse(gint response_id); 103 104 // Common function for OnSelectSingleFileDialogResponse and 105 // OnSelectSingleFolderDialogResponse. 106 void SelectSingleFileHelper(GtkWidget* dialog, 107 gint response_id, 108 bool allow_folder); 109 110 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog. 111 GtkWidget* CreateFileOpenHelper(const std::string& title, 112 const base::FilePath& default_path, 113 gfx::NativeWindow parent); 114 115 // Callback for when the user responds to a Save As or Open File dialog. 116 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void, 117 OnSelectSingleFileDialogResponse, int); 118 119 // Callback for when the user responds to a Select Folder dialog. 120 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void, 121 OnSelectSingleFolderDialogResponse, int); 122 123 // Callback for when the user responds to a Open Multiple Files dialog. 124 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void, 125 OnSelectMultiFileDialogResponse, int); 126 127 // Callback for when the file chooser gets destroyed. 128 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy); 129 130 // Callback for when we update the preview for the selection. 131 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview); 132 133 // A map from dialog windows to the |params| user data associated with them. 134 std::map<GtkWidget*, void*> params_map_; 135 136 // The GtkImage widget for showing previews of selected images. 137 GtkWidget* preview_; 138 139 // All our dialogs. 140 std::set<GtkWidget*> dialogs_; 141 142 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK); 143 }; 144 145 // The size of the preview we display for selected image files. We set height 146 // larger than width because generally there is more free space vertically 147 // than horiztonally (setting the preview image will alway expand the width of 148 // the dialog, but usually not the height). The image's aspect ratio will always 149 // be preserved. 150 static const int kPreviewWidth = 256; 151 static const int kPreviewHeight = 512; 152 153 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener, 154 ui::SelectFilePolicy* policy) 155 : SelectFileDialogImpl(listener, policy), 156 preview_(NULL) { 157 } 158 159 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() { 160 while (dialogs_.begin() != dialogs_.end()) { 161 gtk_widget_destroy(*(dialogs_.begin())); 162 } 163 } 164 165 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() { 166 return file_types_.extensions.size() > 1; 167 } 168 169 // We ignore |default_extension|. 170 void SelectFileDialogImplGTK::SelectFileImpl( 171 Type type, 172 const base::string16& title, 173 const base::FilePath& default_path, 174 const FileTypeInfo* file_types, 175 int file_type_index, 176 const base::FilePath::StringType& default_extension, 177 gfx::NativeWindow owning_window, 178 void* params) { 179 type_ = type; 180 // |owning_window| can be null when user right-clicks on a downloadable item 181 // and chooses 'Open Link in New Tab' when 'Ask where to save each file 182 // before downloading.' preference is turned on. (http://crbug.com/29213) 183 if (owning_window) 184 parents_.insert(owning_window); 185 186 std::string title_string = UTF16ToUTF8(title); 187 188 file_type_index_ = file_type_index; 189 if (file_types) 190 file_types_ = *file_types; 191 else 192 file_types_.include_all_files = true; 193 194 GtkWidget* dialog = NULL; 195 switch (type) { 196 case SELECT_FOLDER: 197 case SELECT_UPLOAD_FOLDER: 198 dialog = CreateSelectFolderDialog(type, title_string, default_path, 199 owning_window); 200 break; 201 case SELECT_OPEN_FILE: 202 dialog = CreateFileOpenDialog(title_string, default_path, owning_window); 203 break; 204 case SELECT_OPEN_MULTI_FILE: 205 dialog = CreateMultiFileOpenDialog(title_string, default_path, 206 owning_window); 207 break; 208 case SELECT_SAVEAS_FILE: 209 dialog = CreateSaveAsDialog(title_string, default_path, owning_window); 210 break; 211 default: 212 NOTREACHED(); 213 return; 214 } 215 g_signal_connect(dialog, "delete-event", 216 G_CALLBACK(gtk_widget_hide_on_delete), NULL); 217 dialogs_.insert(dialog); 218 219 preview_ = gtk_image_new(); 220 g_signal_connect(dialog, "destroy", 221 G_CALLBACK(OnFileChooserDestroyThunk), this); 222 g_signal_connect(dialog, "update-preview", 223 G_CALLBACK(OnUpdatePreviewThunk), this); 224 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_); 225 226 params_map_[dialog] = params; 227 228 // Set window-to-parent modality by adding the dialog to the same window 229 // group as the parent. 230 gtk_window_group_add_window(gtk_window_get_group(owning_window), 231 GTK_WINDOW(dialog)); 232 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); 233 234 gtk_widget_show_all(dialog); 235 } 236 237 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) { 238 for (size_t i = 0; i < file_types_.extensions.size(); ++i) { 239 GtkFileFilter* filter = NULL; 240 std::set<std::string> fallback_labels; 241 242 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) { 243 const std::string& current_extension = file_types_.extensions[i][j]; 244 if (!current_extension.empty()) { 245 if (!filter) 246 filter = gtk_file_filter_new(); 247 scoped_ptr<std::string> file_extension( 248 new std::string("." + current_extension)); 249 fallback_labels.insert(std::string("*").append(*file_extension)); 250 gtk_file_filter_add_custom( 251 filter, 252 GTK_FILE_FILTER_FILENAME, 253 reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive), 254 file_extension.release(), 255 reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed)); 256 } 257 } 258 // We didn't find any non-empty extensions to filter on. 259 if (!filter) 260 continue; 261 262 // The description vector may be blank, in which case we are supposed to 263 // use some sort of default description based on the filter. 264 if (i < file_types_.extension_description_overrides.size()) { 265 gtk_file_filter_set_name(filter, UTF16ToUTF8( 266 file_types_.extension_description_overrides[i]).c_str()); 267 } else { 268 // There is no system default filter description so we use 269 // the extensions themselves if the description is blank. 270 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(), 271 fallback_labels.end()); 272 std::string fallback_label = JoinString(fallback_labels_vector, ','); 273 gtk_file_filter_set_name(filter, fallback_label.c_str()); 274 } 275 276 gtk_file_chooser_add_filter(chooser, filter); 277 if (i == file_type_index_ - 1) 278 gtk_file_chooser_set_filter(chooser, filter); 279 } 280 281 // Add the *.* filter, but only if we have added other filters (otherwise it 282 // is implied). 283 if (file_types_.include_all_files && !file_types_.extensions.empty()) { 284 GtkFileFilter* filter = gtk_file_filter_new(); 285 gtk_file_filter_add_pattern(filter, "*"); 286 gtk_file_filter_set_name(filter, 287 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str()); 288 gtk_file_chooser_add_filter(chooser, filter); 289 } 290 } 291 292 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog, 293 const base::FilePath& path) { 294 if (type_ == SELECT_SAVEAS_FILE) 295 *last_saved_path_ = path.DirName(); 296 else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER) 297 *last_opened_path_ = path.DirName(); 298 else 299 NOTREACHED(); 300 301 if (listener_) { 302 GtkFileFilter* selected_filter = 303 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); 304 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog)); 305 int idx = g_slist_index(filters, selected_filter); 306 g_slist_free(filters); 307 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog)); 308 } 309 gtk_widget_destroy(dialog); 310 } 311 312 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog, 313 const std::vector<base::FilePath>& files) { 314 *last_opened_path_ = files[0].DirName(); 315 316 if (listener_) 317 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog)); 318 gtk_widget_destroy(dialog); 319 } 320 321 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) { 322 void* params = PopParamsForDialog(dialog); 323 if (listener_) 324 listener_->FileSelectionCanceled(params); 325 gtk_widget_destroy(dialog); 326 } 327 328 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper( 329 const std::string& title, 330 const base::FilePath& default_path, 331 gfx::NativeWindow parent) { 332 GtkWidget* dialog = 333 gtk_file_chooser_dialog_new(title.c_str(), parent, 334 GTK_FILE_CHOOSER_ACTION_OPEN, 335 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 336 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, 337 NULL); 338 AddFilters(GTK_FILE_CHOOSER(dialog)); 339 340 if (!default_path.empty()) { 341 if (CallDirectoryExistsOnUIThread(default_path)) { 342 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), 343 default_path.value().c_str()); 344 } else { 345 // If the file doesn't exist, this will just switch to the correct 346 // directory. That's good enough. 347 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), 348 default_path.value().c_str()); 349 } 350 } else if (!last_opened_path_->empty()) { 351 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), 352 last_opened_path_->value().c_str()); 353 } 354 return dialog; 355 } 356 357 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog( 358 Type type, 359 const std::string& title, 360 const base::FilePath& default_path, 361 gfx::NativeWindow parent) { 362 std::string title_string = title; 363 if (title_string.empty()) { 364 title_string = (type == SELECT_UPLOAD_FOLDER) ? 365 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) : 366 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE); 367 } 368 std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ? 369 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) : 370 GTK_STOCK_OPEN; 371 372 GtkWidget* dialog = 373 gtk_file_chooser_dialog_new(title_string.c_str(), parent, 374 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, 375 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 376 accept_button_label.c_str(), 377 GTK_RESPONSE_ACCEPT, 378 NULL); 379 380 if (!default_path.empty()) { 381 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), 382 default_path.value().c_str()); 383 } else if (!last_opened_path_->empty()) { 384 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), 385 last_opened_path_->value().c_str()); 386 } 387 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); 388 g_signal_connect(dialog, "response", 389 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this); 390 return dialog; 391 } 392 393 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog( 394 const std::string& title, 395 const base::FilePath& default_path, 396 gfx::NativeWindow parent) { 397 std::string title_string = !title.empty() ? title : 398 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE); 399 400 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent); 401 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); 402 g_signal_connect(dialog, "response", 403 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this); 404 return dialog; 405 } 406 407 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog( 408 const std::string& title, 409 const base::FilePath& default_path, 410 gfx::NativeWindow parent) { 411 std::string title_string = !title.empty() ? title : 412 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE); 413 414 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent); 415 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); 416 g_signal_connect(dialog, "response", 417 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this); 418 return dialog; 419 } 420 421 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title, 422 const base::FilePath& default_path, gfx::NativeWindow parent) { 423 std::string title_string = !title.empty() ? title : 424 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE); 425 426 GtkWidget* dialog = 427 gtk_file_chooser_dialog_new(title_string.c_str(), parent, 428 GTK_FILE_CHOOSER_ACTION_SAVE, 429 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 430 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, 431 NULL); 432 433 AddFilters(GTK_FILE_CHOOSER(dialog)); 434 if (!default_path.empty()) { 435 // Since the file may not already exist, we use 436 // set_current_folder() followed by set_current_name(), as per the 437 // recommendation of the GTK docs. 438 if (CallDirectoryExistsOnUIThread(default_path)) { 439 gtk_file_chooser_set_current_folder( 440 GTK_FILE_CHOOSER(dialog), default_path.value().c_str()); 441 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), ""); 442 } else { 443 gtk_file_chooser_set_current_folder( 444 GTK_FILE_CHOOSER(dialog), default_path.DirName().value().c_str()); 445 gtk_file_chooser_set_current_name( 446 GTK_FILE_CHOOSER(dialog), default_path.BaseName().value().c_str()); 447 } 448 } else if (!last_saved_path_->empty()) { 449 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), 450 last_saved_path_->value().c_str()); 451 } 452 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); 453 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), 454 TRUE); 455 g_signal_connect(dialog, "response", 456 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this); 457 return dialog; 458 } 459 460 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) { 461 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog); 462 DCHECK(iter != params_map_.end()); 463 void* params = iter->second; 464 params_map_.erase(iter); 465 return params; 466 } 467 468 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) { 469 dialogs_.erase(dialog); 470 471 // Parent may be NULL in a few cases: 1) on shutdown when 472 // AllBrowsersClosed() trigger this handler after all the browser 473 // windows got destroyed, or 2) when the parent tab has been opened by 474 // 'Open Link in New Tab' context menu on a downloadable item and 475 // the tab has no content (see the comment in SelectFile as well). 476 GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(dialog)); 477 if (!parent) 478 return; 479 std::set<GtkWindow*>::iterator iter = parents_.find(parent); 480 if (iter != parents_.end()) 481 parents_.erase(iter); 482 else 483 NOTREACHED(); 484 } 485 486 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) { 487 bool is_cancel = response_id == GTK_RESPONSE_CANCEL || 488 response_id == GTK_RESPONSE_DELETE_EVENT; 489 if (is_cancel) 490 return true; 491 492 DCHECK(response_id == GTK_RESPONSE_ACCEPT); 493 return false; 494 } 495 496 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog, 497 gint response_id, 498 bool allow_folder) { 499 if (IsCancelResponse(response_id)) { 500 FileNotSelected(dialog); 501 return; 502 } 503 504 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); 505 if (!filename) { 506 FileNotSelected(dialog); 507 return; 508 } 509 510 base::FilePath path(filename); 511 g_free(filename); 512 513 if (allow_folder) { 514 FileSelected(dialog, path); 515 return; 516 } 517 518 if (CallDirectoryExistsOnUIThread(path)) 519 FileNotSelected(dialog); 520 else 521 FileSelected(dialog, path); 522 } 523 524 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse( 525 GtkWidget* dialog, int response_id) { 526 SelectSingleFileHelper(dialog, response_id, false); 527 } 528 529 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse( 530 GtkWidget* dialog, int response_id) { 531 SelectSingleFileHelper(dialog, response_id, true); 532 } 533 534 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog, 535 int response_id) { 536 if (IsCancelResponse(response_id)) { 537 FileNotSelected(dialog); 538 return; 539 } 540 541 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); 542 if (!filenames) { 543 FileNotSelected(dialog); 544 return; 545 } 546 547 std::vector<base::FilePath> filenames_fp; 548 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) { 549 base::FilePath path(static_cast<char*>(iter->data)); 550 g_free(iter->data); 551 if (CallDirectoryExistsOnUIThread(path)) 552 continue; 553 filenames_fp.push_back(path); 554 } 555 g_slist_free(filenames); 556 557 if (filenames_fp.empty()) { 558 FileNotSelected(dialog); 559 return; 560 } 561 MultiFilesSelected(dialog, filenames_fp); 562 } 563 564 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) { 565 FileDialogDestroyed(dialog); 566 } 567 568 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) { 569 gchar* filename = gtk_file_chooser_get_preview_filename( 570 GTK_FILE_CHOOSER(chooser)); 571 if (!filename) 572 return; 573 // This will preserve the image's aspect ratio. 574 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, 575 kPreviewHeight, NULL); 576 g_free(filename); 577 if (pixbuf) { 578 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf); 579 g_object_unref(pixbuf); 580 } 581 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), 582 pixbuf ? TRUE : FALSE); 583 } 584 585 } // namespace 586 587 namespace ui { 588 589 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK( 590 Listener* listener, ui::SelectFilePolicy* policy) { 591 return new SelectFileDialogImplGTK(listener, policy); 592 } 593 594 } // namespace ui 595