file_dialog_gtk.cc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // Copyright (c) 2014 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "atom/browser/ui/file_dialog.h"
  5. #include "atom/browser/ui/util_gtk.h"
  6. #include "atom/browser/native_window_views.h"
  7. #include "atom/browser/unresponsive_suppressor.h"
  8. #include "base/callback.h"
  9. #include "base/files/file_util.h"
  10. #include "base/strings/string_util.h"
  11. #include "chrome/browser/ui/libgtkui/gtk_util.h"
  12. #include "ui/base/glib/glib_signal.h"
  13. #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
  14. namespace file_dialog {
  15. DialogSettings::DialogSettings() = default;
  16. DialogSettings::DialogSettings(const DialogSettings&) = default;
  17. DialogSettings::~DialogSettings() = default;
  18. namespace {
  19. // Makes sure that .jpg also shows .JPG.
  20. gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
  21. std::string* file_extension) {
  22. // Makes .* file extension matches all file types.
  23. if (*file_extension == ".*")
  24. return true;
  25. return base::EndsWith(file_info->filename, *file_extension,
  26. base::CompareCase::INSENSITIVE_ASCII);
  27. }
  28. // Deletes |data| when gtk_file_filter_add_custom() is done with it.
  29. void OnFileFilterDataDestroyed(std::string* file_extension) {
  30. delete file_extension;
  31. }
  32. class FileChooserDialog {
  33. public:
  34. FileChooserDialog(GtkFileChooserAction action, const DialogSettings& settings)
  35. : parent_(static_cast<atom::NativeWindowViews*>(settings.parent_window)),
  36. filters_(settings.filters) {
  37. const char* confirm_text = util_gtk::kOkLabel;
  38. if (!settings.button_label.empty())
  39. confirm_text = settings.button_label.c_str();
  40. else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
  41. confirm_text = util_gtk::kSaveLabel;
  42. else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
  43. confirm_text = util_gtk::kOpenLabel;
  44. dialog_ = gtk_file_chooser_dialog_new(
  45. settings.title.c_str(), NULL, action, util_gtk::kCancelLabel,
  46. GTK_RESPONSE_CANCEL, confirm_text, GTK_RESPONSE_ACCEPT, NULL);
  47. if (parent_) {
  48. parent_->SetEnabled(false);
  49. libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
  50. gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
  51. }
  52. if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
  53. gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog_),
  54. TRUE);
  55. if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
  56. gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE);
  57. if (!settings.default_path.empty()) {
  58. if (base::DirectoryExists(settings.default_path)) {
  59. gtk_file_chooser_set_current_folder(
  60. GTK_FILE_CHOOSER(dialog_), settings.default_path.value().c_str());
  61. } else {
  62. if (settings.default_path.IsAbsolute()) {
  63. gtk_file_chooser_set_current_folder(
  64. GTK_FILE_CHOOSER(dialog_),
  65. settings.default_path.DirName().value().c_str());
  66. }
  67. gtk_file_chooser_set_current_name(
  68. GTK_FILE_CHOOSER(dialog_),
  69. settings.default_path.BaseName().value().c_str());
  70. }
  71. }
  72. if (!settings.filters.empty())
  73. AddFilters(settings.filters);
  74. }
  75. ~FileChooserDialog() {
  76. gtk_widget_destroy(dialog_);
  77. if (parent_)
  78. parent_->SetEnabled(true);
  79. }
  80. void SetupProperties(int properties) {
  81. const auto hasProp = [properties](FileDialogProperty prop) {
  82. return gboolean((properties & prop) != 0);
  83. };
  84. auto* file_chooser = GTK_FILE_CHOOSER(dialog());
  85. gtk_file_chooser_set_select_multiple(file_chooser,
  86. hasProp(FILE_DIALOG_MULTI_SELECTIONS));
  87. gtk_file_chooser_set_show_hidden(file_chooser,
  88. hasProp(FILE_DIALOG_SHOW_HIDDEN_FILES));
  89. }
  90. void RunAsynchronous() {
  91. g_signal_connect(dialog_, "delete-event",
  92. G_CALLBACK(gtk_widget_hide_on_delete), NULL);
  93. g_signal_connect(dialog_, "response", G_CALLBACK(OnFileDialogResponseThunk),
  94. this);
  95. gtk_widget_show_all(dialog_);
  96. // We need to call gtk_window_present after making the widgets visible to
  97. // make sure window gets correctly raised and gets focus.
  98. int time = ui::X11EventSource::GetInstance()->GetTimestamp();
  99. gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
  100. }
  101. void RunSaveAsynchronous(const SaveDialogCallback& callback) {
  102. save_callback_ = callback;
  103. RunAsynchronous();
  104. }
  105. void RunOpenAsynchronous(const OpenDialogCallback& callback) {
  106. open_callback_ = callback;
  107. RunAsynchronous();
  108. }
  109. base::FilePath GetFileName() const {
  110. gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_));
  111. const base::FilePath path(filename);
  112. g_free(filename);
  113. return path;
  114. }
  115. std::vector<base::FilePath> GetFileNames() const {
  116. std::vector<base::FilePath> paths;
  117. auto* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_));
  118. for (auto* iter = filenames; iter != NULL; iter = iter->next) {
  119. auto* filename = static_cast<char*>(iter->data);
  120. paths.emplace_back(filename);
  121. g_free(filename);
  122. }
  123. g_slist_free(filenames);
  124. return paths;
  125. }
  126. CHROMEG_CALLBACK_1(FileChooserDialog,
  127. void,
  128. OnFileDialogResponse,
  129. GtkWidget*,
  130. int);
  131. GtkWidget* dialog() const { return dialog_; }
  132. private:
  133. void AddFilters(const Filters& filters);
  134. atom::NativeWindowViews* parent_;
  135. atom::UnresponsiveSuppressor unresponsive_suppressor_;
  136. GtkWidget* dialog_;
  137. Filters filters_;
  138. SaveDialogCallback save_callback_;
  139. OpenDialogCallback open_callback_;
  140. DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
  141. };
  142. void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
  143. gtk_widget_hide(dialog_);
  144. if (!save_callback_.is_null()) {
  145. if (response == GTK_RESPONSE_ACCEPT)
  146. save_callback_.Run(true, GetFileName());
  147. else
  148. save_callback_.Run(false, base::FilePath());
  149. } else if (!open_callback_.is_null()) {
  150. if (response == GTK_RESPONSE_ACCEPT)
  151. open_callback_.Run(true, GetFileNames());
  152. else
  153. open_callback_.Run(false, std::vector<base::FilePath>());
  154. }
  155. delete this;
  156. }
  157. void FileChooserDialog::AddFilters(const Filters& filters) {
  158. for (size_t i = 0; i < filters.size(); ++i) {
  159. const Filter& filter = filters[i];
  160. GtkFileFilter* gtk_filter = gtk_file_filter_new();
  161. for (size_t j = 0; j < filter.second.size(); ++j) {
  162. auto file_extension =
  163. std::make_unique<std::string>("." + filter.second[j]);
  164. gtk_file_filter_add_custom(
  165. gtk_filter, GTK_FILE_FILTER_FILENAME,
  166. reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
  167. file_extension.release(),
  168. reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
  169. }
  170. gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
  171. gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_), gtk_filter);
  172. }
  173. }
  174. } // namespace
  175. bool ShowOpenDialog(const DialogSettings& settings,
  176. std::vector<base::FilePath>* paths) {
  177. GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
  178. if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
  179. action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
  180. FileChooserDialog open_dialog(action, settings);
  181. open_dialog.SetupProperties(settings.properties);
  182. gtk_widget_show_all(open_dialog.dialog());
  183. int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog()));
  184. if (response == GTK_RESPONSE_ACCEPT) {
  185. *paths = open_dialog.GetFileNames();
  186. return true;
  187. } else {
  188. return false;
  189. }
  190. }
  191. void ShowOpenDialog(const DialogSettings& settings,
  192. const OpenDialogCallback& callback) {
  193. GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
  194. if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
  195. action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
  196. FileChooserDialog* open_dialog = new FileChooserDialog(action, settings);
  197. open_dialog->SetupProperties(settings.properties);
  198. open_dialog->RunOpenAsynchronous(callback);
  199. }
  200. bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) {
  201. FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
  202. gtk_widget_show_all(save_dialog.dialog());
  203. int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
  204. if (response == GTK_RESPONSE_ACCEPT) {
  205. *path = save_dialog.GetFileName();
  206. return true;
  207. } else {
  208. return false;
  209. }
  210. }
  211. void ShowSaveDialog(const DialogSettings& settings,
  212. const SaveDialogCallback& callback) {
  213. FileChooserDialog* save_dialog =
  214. new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
  215. save_dialog->RunSaveAsynchronous(callback);
  216. }
  217. } // namespace file_dialog