| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gtk/select_file_dialog_linux_gtk.h" |
| |
| #include <glib/gi18n.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <memory> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/base/glib/scoped_gobject.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gtk/gtk_compat.h" |
| #include "ui/gtk/gtk_ui.h" |
| #include "ui/gtk/gtk_ui_platform.h" |
| #include "ui/gtk/gtk_util.h" |
| #include "ui/shell_dialogs/select_file_dialog.h" |
| #include "ui/shell_dialogs/select_file_dialog_linux.h" |
| #include "ui/shell_dialogs/selected_file_info.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h" |
| #include "url/gurl.h" |
| |
| namespace gtk { |
| |
| namespace { |
| |
| // TODO(https://crbug.com/981309): These getters will be unnecessary after |
| // migrating to GtkFileChooserNative. |
| const char* GettextPackage() { |
| static base::NoDestructor<std::string> gettext_package( |
| "gtk" + base::NumberToString(GtkVersion().components()[0]) + "0"); |
| return gettext_package->c_str(); |
| } |
| |
| const char* GtkGettext(const char* str) { |
| return g_dgettext(GettextPackage(), str); |
| } |
| |
| const char* GetCancelLabel() { |
| if (!GtkCheckVersion(4)) |
| return "gtk-cancel"; // In GTK3, this is GTK_STOCK_CANCEL. |
| static const char* cancel = GtkGettext("_Cancel"); |
| return cancel; |
| } |
| |
| const char* GetOpenLabel() { |
| if (!GtkCheckVersion(4)) |
| return "gtk-open"; // In GTK3, this is GTK_STOCK_OPEN. |
| static const char* open = GtkGettext("_Open"); |
| return open; |
| } |
| |
| const char* GetSaveLabel() { |
| if (!GtkCheckVersion(4)) |
| return "gtk-save"; // In GTK3, this is GTK_STOCK_SAVE. |
| static const char* save = GtkGettext("_Save"); |
| return save; |
| } |
| |
| void GtkFileChooserSetFilename(GtkFileChooser* dialog, |
| const base::FilePath& path) { |
| if (GtkCheckVersion(4)) { |
| auto file = TakeGObject(g_file_new_for_path(path.value().c_str())); |
| gtk_file_chooser_set_file(dialog, file, nullptr); |
| } else { |
| gtk_file_chooser_set_filename(dialog, path.value().c_str()); |
| } |
| } |
| |
| int GtkDialogSelectedFilterIndex(GtkWidget* dialog) { |
| GtkFileFilter* selected_filter = |
| gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); |
| int idx = -1; |
| if (GtkCheckVersion(4)) { |
| auto filters = |
| TakeGObject(gtk_file_chooser_get_filters(GTK_FILE_CHOOSER(dialog))); |
| int size = g_list_model_get_n_items(filters); |
| for (; idx < size; ++idx) { |
| if (g_list_model_get_item(filters, idx) == selected_filter) |
| break; |
| } |
| } else { |
| GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog)); |
| idx = g_slist_index(filters, selected_filter); |
| g_slist_free(filters); |
| } |
| return idx; |
| } |
| |
| std::string GtkFileChooserGetFilename(GtkWidget* dialog) { |
| const char* filename = nullptr; |
| struct GFreeDeleter { |
| void operator()(gchar* ptr) const { g_free(ptr); } |
| }; |
| std::unique_ptr<gchar, GFreeDeleter> gchar_filename; |
| if (GtkCheckVersion(4)) { |
| if (auto file = |
| TakeGObject(gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)))) { |
| filename = g_file_peek_path(file); |
| } |
| } else { |
| gchar_filename = std::unique_ptr<gchar, GFreeDeleter>( |
| gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); |
| filename = gchar_filename.get(); |
| } |
| return filename ? std::string(filename) : std::string(); |
| } |
| |
| std::vector<base::FilePath> GtkFileChooserGetFilenames(GtkWidget* dialog) { |
| std::vector<base::FilePath> filenames_fp; |
| if (GtkCheckVersion(4)) { |
| auto files = Gtk4FileChooserGetFiles(GTK_FILE_CHOOSER(dialog)); |
| auto size = g_list_model_get_n_items(files); |
| for (unsigned int i = 0; i < size; ++i) { |
| auto file = TakeGObject(G_FILE(g_list_model_get_object(files, i))); |
| filenames_fp.emplace_back(g_file_peek_path(file)); |
| } |
| } else { |
| GSList* filenames = |
| gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); |
| if (!filenames) |
| return {}; |
| for (GSList* iter = filenames; iter != nullptr; iter = g_slist_next(iter)) { |
| base::FilePath path(static_cast<char*>(iter->data)); |
| g_free(iter->data); |
| filenames_fp.push_back(path); |
| } |
| g_slist_free(filenames); |
| } |
| return filenames_fp; |
| } |
| |
| } // namespace |
| |
| // The size of the preview we display for selected image files. We set height |
| // larger than width because generally there is more free space vertically |
| // than horizontally (setting the preview image will always expand the width of |
| // the dialog, but usually not the height). The image's aspect ratio will always |
| // be preserved. Only used on GTK3. |
| static const int kPreviewWidth = 256; |
| static const int kPreviewHeight = 512; |
| |
| SelectFileDialogLinuxGtk::DialogState::DialogState() = default; |
| |
| SelectFileDialogLinuxGtk::DialogState::DialogState( |
| void* params, |
| std::vector<ScopedGSignal> signals, |
| aura::Window* parent, |
| base::OnceClosure reenable_parent_events) |
| : params(params), |
| signals(std::move(signals)), |
| parent(parent), |
| reenable_parent_events(std::move(reenable_parent_events)) {} |
| |
| SelectFileDialogLinuxGtk::DialogState::DialogState(DialogState&& other) = |
| default; |
| |
| SelectFileDialogLinuxGtk::DialogState& |
| SelectFileDialogLinuxGtk::DialogState::operator=(DialogState&& other) = default; |
| |
| SelectFileDialogLinuxGtk::DialogState::~DialogState() = default; |
| |
| SelectFileDialogLinuxGtk::SelectFileDialogLinuxGtk( |
| Listener* listener, |
| std::unique_ptr<ui::SelectFilePolicy> policy) |
| : SelectFileDialogLinux(listener, std::move(policy)) {} |
| |
| SelectFileDialogLinuxGtk::~SelectFileDialogLinuxGtk() { |
| // `OnFileChooserDestroy()` mutates `dialogs_`, so make a copy to avoid |
| // modifying it during iteration. |
| std::vector<GtkWidget*> dialogs; |
| dialogs.reserve(dialogs_.size()); |
| for (auto& pair : dialogs_) { |
| // Disconnect the signal handler now so `GtkWindowDestroy()` doesn't |
| // trigger `OnFileChooserDestroy()`. |
| pair.second.signals.clear(); |
| dialogs.push_back(pair.first); |
| } |
| for (GtkWidget* dialog : dialogs) { |
| GtkWindowDestroy(dialog); |
| OnFileChooserDestroy(dialog); |
| } |
| CHECK(dialogs_.empty()); |
| } |
| |
| bool SelectFileDialogLinuxGtk::IsRunning( |
| gfx::NativeWindow parent_window) const { |
| return std::any_of(dialogs_.begin(), dialogs_.end(), |
| [=](const std::pair<GtkWidget*, DialogState>& pair) { |
| return pair.second.parent == parent_window; |
| }); |
| } |
| |
| bool SelectFileDialogLinuxGtk::HasMultipleFileTypeChoicesImpl() { |
| return file_types().extensions.size() > 1; |
| } |
| |
| void SelectFileDialogLinuxGtk::OnWindowDestroying(aura::Window* window) { |
| // Remove the |parent| property associated with the |dialog|. |
| for (auto& pair : dialogs_) { |
| GtkWidget* dialog = pair.first; |
| auto& state = pair.second; |
| if (state.parent == window) { |
| ClearAuraTransientParent(dialog, window); |
| window->RemoveObserver(this); |
| state.parent = nullptr; |
| return; |
| } |
| } |
| } |
| |
| // We ignore |default_extension|. |
| void SelectFileDialogLinuxGtk::SelectFileImpl( |
| Type type, |
| const std::u16string& title, |
| const base::FilePath& default_path, |
| const FileTypeInfo* file_types, |
| int file_type_index, |
| const base::FilePath::StringType& default_extension, |
| gfx::NativeWindow owning_window, |
| void* params, |
| const GURL* caller) { |
| set_type(type); |
| |
| std::string title_string = base::UTF16ToUTF8(title); |
| |
| set_file_type_index(file_type_index); |
| if (file_types) |
| set_file_types(*file_types); |
| |
| GtkWidget* dialog = nullptr; |
| std::vector<ScopedGSignal> signals; |
| auto connect = [&](const char* detailed_signal, auto receiver) { |
| // Unretained() is safe since SelectFileDialogLinuxGtk will own the |
| // ScopedGSignal. |
| signals.emplace_back(dialog, detailed_signal, |
| base::BindRepeating(receiver, base::Unretained(this)), |
| G_CONNECT_AFTER); |
| }; |
| switch (type) { |
| case SELECT_FOLDER: |
| case SELECT_UPLOAD_FOLDER: |
| case SELECT_EXISTING_FOLDER: |
| dialog = CreateSelectFolderDialog(type, title_string, default_path, |
| owning_window); |
| connect("response", |
| &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse); |
| break; |
| case SELECT_OPEN_FILE: |
| dialog = CreateFileOpenDialog(title_string, default_path, owning_window); |
| connect("response", |
| &SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse); |
| break; |
| case SELECT_OPEN_MULTI_FILE: |
| dialog = |
| CreateMultiFileOpenDialog(title_string, default_path, owning_window); |
| connect("response", |
| &SelectFileDialogLinuxGtk::OnSelectMultiFileDialogResponse); |
| break; |
| case SELECT_SAVEAS_FILE: |
| dialog = CreateSaveAsDialog(title_string, default_path, owning_window); |
| connect("response", |
| &SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse); |
| break; |
| case SELECT_NONE: |
| NOTREACHED(); |
| return; |
| } |
| if (GtkCheckVersion(4)) { |
| gtk_window_set_hide_on_close(GTK_WINDOW(dialog), true); |
| } else { |
| signals.emplace_back(dialog, "delete-event", |
| base::BindRepeating(gtk_widget_hide_on_delete)); |
| } |
| |
| if (owning_window) { |
| owning_window->AddObserver(this); |
| } |
| |
| connect("destroy", &SelectFileDialogLinuxGtk::OnFileChooserDestroy); |
| |
| if (!GtkCheckVersion(4)) { |
| preview_ = gtk_image_new(); |
| connect("update-preview", &SelectFileDialogLinuxGtk::OnUpdatePreview); |
| gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_); |
| } |
| |
| base::OnceClosure reenable_input_events = |
| DisableHostInputHandling(dialog, owning_window); |
| |
| dialogs_[dialog] = DialogState(params, std::move(signals), owning_window, |
| std::move(reenable_input_events)); |
| |
| if (!GtkCheckVersion(4)) |
| gtk_widget_show_all(dialog); |
| gtk::GtkUi::GetPlatform()->ShowGtkWindow(GTK_WINDOW(dialog)); |
| } |
| |
| void SelectFileDialogLinuxGtk::AddFilters(GtkFileChooser* chooser) { |
| for (size_t i = 0; i < file_types().extensions.size(); ++i) { |
| GtkFileFilter* filter = nullptr; |
| std::set<std::string> fallback_labels; |
| |
| for (const auto& current_extension : file_types().extensions[i]) { |
| if (!current_extension.empty()) { |
| if (!filter) |
| filter = gtk_file_filter_new(); |
| for (std::string pattern : |
| {"*." + base::ToLowerASCII(current_extension), |
| "*." + base::ToUpperASCII(current_extension)}) { |
| gtk_file_filter_add_pattern(filter, pattern.c_str()); |
| fallback_labels.insert(pattern); |
| } |
| } |
| } |
| // We didn't find any non-empty extensions to filter on. |
| if (!filter) |
| continue; |
| |
| // The description vector may be blank, in which case we are supposed to |
| // use some sort of default description based on the filter. |
| if (i < file_types().extension_description_overrides.size()) { |
| gtk_file_filter_set_name( |
| filter, |
| base::UTF16ToUTF8(file_types().extension_description_overrides[i]) |
| .c_str()); |
| } else { |
| // There is no system default filter description so we use |
| // the extensions themselves if the description is blank. |
| std::vector<std::string> fallback_labels_vector(fallback_labels.begin(), |
| fallback_labels.end()); |
| std::string fallback_label = |
| base::JoinString(fallback_labels_vector, ","); |
| gtk_file_filter_set_name(filter, fallback_label.c_str()); |
| } |
| |
| gtk_file_chooser_add_filter(chooser, filter); |
| if (i == file_type_index() - 1) |
| gtk_file_chooser_set_filter(chooser, filter); |
| } |
| |
| // Add the *.* filter, but only if we have added other filters (otherwise it |
| // is implied). |
| if (file_types().include_all_files && !file_types().extensions.empty()) { |
| GtkFileFilter* filter = gtk_file_filter_new(); |
| gtk_file_filter_add_pattern(filter, "*"); |
| gtk_file_filter_set_name( |
| filter, l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str()); |
| gtk_file_chooser_add_filter(chooser, filter); |
| } |
| } |
| |
| void SelectFileDialogLinuxGtk::FileSelected(GtkWidget* dialog, |
| const base::FilePath& path) { |
| if (type() == SELECT_SAVEAS_FILE) { |
| set_last_saved_path(path.DirName()); |
| } else if (type() == SELECT_OPEN_FILE || type() == SELECT_FOLDER || |
| type() == SELECT_UPLOAD_FOLDER || |
| type() == SELECT_EXISTING_FOLDER) { |
| set_last_opened_path(path.DirName()); |
| } else { |
| NOTREACHED(); |
| } |
| |
| if (listener_) { |
| listener_->FileSelected(ui::SelectedFileInfo(path), |
| GtkDialogSelectedFilterIndex(dialog) + 1, |
| PopParamsForDialog(dialog)); |
| } |
| GtkWindowDestroy(dialog); |
| } |
| |
| void SelectFileDialogLinuxGtk::MultiFilesSelected( |
| GtkWidget* dialog, |
| const std::vector<base::FilePath>& files) { |
| set_last_opened_path(files[0].DirName()); |
| |
| if (listener_) { |
| listener_->MultiFilesSelected(ui::FilePathListToSelectedFileInfoList(files), |
| PopParamsForDialog(dialog)); |
| } |
| GtkWindowDestroy(dialog); |
| } |
| |
| void SelectFileDialogLinuxGtk::FileNotSelected(GtkWidget* dialog) { |
| void* params = PopParamsForDialog(dialog); |
| if (listener_) { |
| listener_->FileSelectionCanceled(params); |
| } |
| GtkWindowDestroy(dialog); |
| } |
| |
| GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper( |
| const std::string& title, |
| const base::FilePath& default_path, |
| gfx::NativeWindow parent) { |
| GtkWidget* dialog = GtkFileChooserDialogNew( |
| title.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, GetCancelLabel(), |
| GTK_RESPONSE_CANCEL, GetOpenLabel(), GTK_RESPONSE_ACCEPT); |
| SetGtkTransientForAura(dialog, parent); |
| AddFilters(GTK_FILE_CHOOSER(dialog)); |
| |
| if (!default_path.empty()) { |
| if (CallDirectoryExistsOnUIThread(default_path)) { |
| GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog), default_path); |
| } else { |
| // If the file doesn't exist, this will just switch to the correct |
| // directory. That's good enough. |
| GtkFileChooserSetFilename(GTK_FILE_CHOOSER(dialog), default_path); |
| } |
| } else if (!last_opened_path()->empty()) { |
| GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog), |
| *last_opened_path()); |
| } |
| return dialog; |
| } |
| |
| GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog( |
| Type type, |
| const std::string& title, |
| const base::FilePath& default_path, |
| gfx::NativeWindow parent) { |
| std::string title_string = title; |
| if (title_string.empty()) { |
| title_string = |
| (type == SELECT_UPLOAD_FOLDER) |
| ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) |
| : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE); |
| } |
| std::string accept_button_label = |
| (type == SELECT_UPLOAD_FOLDER) |
| ? l10n_util::GetStringUTF8( |
| IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) |
| : GetOpenLabel(); |
| |
| GtkWidget* dialog = GtkFileChooserDialogNew( |
| title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, |
| GetCancelLabel(), GTK_RESPONSE_CANCEL, accept_button_label.c_str(), |
| GTK_RESPONSE_ACCEPT); |
| SetGtkTransientForAura(dialog, parent); |
| GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog); |
| if (type == SELECT_UPLOAD_FOLDER || type == SELECT_EXISTING_FOLDER) |
| gtk_file_chooser_set_create_folders(chooser, FALSE); |
| if (!default_path.empty()) |
| GtkFileChooserSetCurrentFolder(chooser, default_path); |
| else if (!last_opened_path()->empty()) |
| GtkFileChooserSetCurrentFolder(chooser, *last_opened_path()); |
| GtkFileFilter* only_folders = gtk_file_filter_new(); |
| gtk_file_filter_set_name( |
| only_folders, |
| l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE).c_str()); |
| gtk_file_filter_add_mime_type(only_folders, "application/x-directory"); |
| gtk_file_filter_add_mime_type(only_folders, "inode/directory"); |
| gtk_file_filter_add_mime_type(only_folders, "text/directory"); |
| gtk_file_chooser_add_filter(chooser, only_folders); |
| gtk_file_chooser_set_select_multiple(chooser, FALSE); |
| return dialog; |
| } |
| |
| GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenDialog( |
| const std::string& title, |
| const base::FilePath& default_path, |
| gfx::NativeWindow parent) { |
| std::string title_string = |
| !title.empty() ? title |
| : l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE); |
| |
| GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent); |
| gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); |
| return dialog; |
| } |
| |
| GtkWidget* SelectFileDialogLinuxGtk::CreateMultiFileOpenDialog( |
| const std::string& title, |
| const base::FilePath& default_path, |
| gfx::NativeWindow parent) { |
| std::string title_string = |
| !title.empty() ? title |
| : l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE); |
| |
| GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent); |
| gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); |
| return dialog; |
| } |
| |
| GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog( |
| const std::string& title, |
| const base::FilePath& default_path, |
| gfx::NativeWindow parent) { |
| std::string title_string = |
| !title.empty() ? title |
| : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE); |
| |
| GtkWidget* dialog = GtkFileChooserDialogNew( |
| title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SAVE, |
| GetCancelLabel(), GTK_RESPONSE_CANCEL, GetSaveLabel(), |
| GTK_RESPONSE_ACCEPT); |
| SetGtkTransientForAura(dialog, parent); |
| |
| AddFilters(GTK_FILE_CHOOSER(dialog)); |
| if (!default_path.empty()) { |
| if (CallDirectoryExistsOnUIThread(default_path)) { |
| // If this is an existing directory, navigate to that directory, with no |
| // filename. |
| GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog), default_path); |
| } else { |
| // The default path does not exist, or is an existing file. We use |
| // set_current_folder() followed by set_current_name(), as per the |
| // recommendation of the GTK docs. |
| GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog), |
| default_path.DirName()); |
| gtk_file_chooser_set_current_name( |
| GTK_FILE_CHOOSER(dialog), default_path.BaseName().value().c_str()); |
| } |
| } else if (!last_saved_path()->empty()) { |
| GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog), |
| *last_saved_path()); |
| } |
| gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); |
| // Overwrite confirmation is always enabled in GTK4. |
| if (!GtkCheckVersion(4)) { |
| gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), |
| TRUE); |
| } |
| return dialog; |
| } |
| |
| void* SelectFileDialogLinuxGtk::PopParamsForDialog(GtkWidget* dialog) { |
| auto iter = dialogs_.find(dialog); |
| CHECK(iter != dialogs_.end()); |
| void* params = iter->second.params; |
| iter->second.params = nullptr; |
| return params; |
| } |
| |
| bool SelectFileDialogLinuxGtk::IsCancelResponse(gint response_id) { |
| bool is_cancel = response_id == GTK_RESPONSE_CANCEL || |
| response_id == GTK_RESPONSE_DELETE_EVENT; |
| if (is_cancel) |
| return true; |
| |
| DCHECK(response_id == GTK_RESPONSE_ACCEPT); |
| return false; |
| } |
| |
| void SelectFileDialogLinuxGtk::SelectSingleFileHelper(GtkWidget* dialog, |
| gint response_id, |
| bool allow_folder) { |
| if (IsCancelResponse(response_id)) { |
| FileNotSelected(dialog); |
| return; |
| } |
| |
| auto filename = GtkFileChooserGetFilename(dialog); |
| if (filename.empty()) { |
| FileNotSelected(dialog); |
| return; |
| } |
| base::FilePath path(filename); |
| |
| if (allow_folder) { |
| FileSelected(dialog, path); |
| return; |
| } |
| |
| if (CallDirectoryExistsOnUIThread(path)) |
| FileNotSelected(dialog); |
| else |
| FileSelected(dialog, path); |
| } |
| |
| void SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse( |
| GtkWidget* dialog, |
| int response_id) { |
| SelectSingleFileHelper(dialog, response_id, false); |
| } |
| |
| void SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse( |
| GtkWidget* dialog, |
| int response_id) { |
| SelectSingleFileHelper(dialog, response_id, true); |
| } |
| |
| void SelectFileDialogLinuxGtk::OnSelectMultiFileDialogResponse( |
| GtkWidget* dialog, |
| int response_id) { |
| if (IsCancelResponse(response_id)) { |
| FileNotSelected(dialog); |
| return; |
| } |
| |
| auto filenames = GtkFileChooserGetFilenames(dialog); |
| std::erase_if(filenames, [this](const base::FilePath& path) { |
| return CallDirectoryExistsOnUIThread(path); |
| }); |
| if (filenames.empty()) { |
| FileNotSelected(dialog); |
| return; |
| } |
| MultiFilesSelected(dialog, filenames); |
| } |
| |
| void SelectFileDialogLinuxGtk::OnFileChooserDestroy(GtkWidget* dialog) { |
| auto it = dialogs_.find(dialog); |
| if (it == dialogs_.end()) { |
| return; |
| } |
| auto& state = it->second; |
| |
| CHECK_EQ(state.params, nullptr); |
| // `state.parent` can be nullptr when closing the host window |
| // while opening the file-picker. |
| if (state.parent) { |
| ClearAuraTransientParent(dialog, state.parent); |
| state.parent->RemoveObserver(this); |
| } |
| state.signals.clear(); |
| if (state.reenable_parent_events) { |
| std::move(state.reenable_parent_events).Run(); |
| } |
| dialogs_.erase(it); |
| } |
| |
| void SelectFileDialogLinuxGtk::OnUpdatePreview(GtkWidget* chooser) { |
| DCHECK(!GtkCheckVersion(4)); |
| gchar* filename = |
| gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(chooser)); |
| if (!filename) { |
| gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), |
| FALSE); |
| return; |
| } |
| |
| // Don't attempt to open anything which isn't a regular file. If a named pipe, |
| // this may hang. See https://crbug.com/534754. |
| struct stat stat_buf; |
| if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) { |
| g_free(filename); |
| gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), |
| FALSE); |
| return; |
| } |
| |
| // This will preserve the image's aspect ratio. |
| GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, |
| kPreviewHeight, nullptr); |
| g_free(filename); |
| if (pixbuf) { |
| gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf); |
| g_object_unref(pixbuf); |
| } |
| gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), |
| pixbuf ? TRUE : FALSE); |
| } |
| |
| } // namespace gtk |