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