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