From 6ed2d2b232d0750861d036d076397c4d55fb34a4 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 24 Oct 2022 06:54:01 -0400 Subject: [PATCH] Add GtkFileDialog Add an async API for picking a file, together with a builder object for it. This is meant to replace direct use of GtkFileChooserDialog. --- gtk/gtk.h | 1 + gtk/gtkfiledialog.c | 1110 +++++++++++++++++++++++++++++++++++++++++++ gtk/gtkfiledialog.h | 151 ++++++ gtk/meson.build | 2 + 4 files changed, 1264 insertions(+) create mode 100644 gtk/gtkfiledialog.c create mode 100644 gtk/gtkfiledialog.h diff --git a/gtk/gtk.h b/gtk/gtk.h index 51f30cefdc..edc98bf086 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -128,6 +128,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkfiledialog.c b/gtk/gtkfiledialog.c new file mode 100644 index 0000000000..51554978ca --- /dev/null +++ b/gtk/gtkfiledialog.c @@ -0,0 +1,1110 @@ +/* + * GTK - The GIMP Toolkit + * Copyright (C) 2022 Red Hat, Inc. + * All rights reserved. + * + * This Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtkfiledialog.h" + +#include "gtk/gtkdialog.h" +#include "gtkfilechoosernative.h" +#include "gtkdialogerror.h" +#include + +/** + * GtkFileDialog: + * + * A `GtkFileDialog` object collects the arguments that + * are needed to present a file chooser dialog to the + * user, such as a title for the dialog and whether it + * should be modal. + * + * The dialog is shown with [method@Gtk.FileDialog.open], + * [method@Gtk.FileDialog.save], etc. These APIs follow the + * GIO async pattern, and the result can be obtained by calling + * the corresponding finish function, for example + * [method@Gtk.FileDialog.open_finish]. + * + * `GtkFileDialog was added in GTK 4.10. + */ + +/* {{{ GObject implementation */ + +struct _GtkFileDialog +{ + GObject parent_instance; + + char *title; + unsigned int modal : 1; + + GListModel *filters; + GListModel *shortcut_folders; + GtkFileFilter *current_filter; + GFile *current_folder; +}; + +enum +{ + PROP_TITLE = 1, + PROP_MODAL, + PROP_FILTERS, + PROP_SHORTCUT_FOLDERS, + PROP_CURRENT_FILTER, + PROP_CURRENT_FOLDER, + + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +G_DEFINE_TYPE (GtkFileDialog, gtk_file_dialog, G_TYPE_OBJECT) + +static void +gtk_file_dialog_init (GtkFileDialog *self) +{ + self->modal = TRUE; +} + +static void +gtk_file_dialog_finalize (GObject *object) +{ + GtkFileDialog *self = GTK_FILE_DIALOG (object); + + g_free (self->title); + g_clear_object (&self->filters); + g_clear_object (&self->shortcut_folders); + g_clear_object (&self->current_filter); + g_clear_object (&self->current_folder); + + G_OBJECT_CLASS (gtk_file_dialog_parent_class)->finalize (object); +} + +static void +gtk_file_dialog_get_property (GObject *object, + unsigned int property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkFileDialog *self = GTK_FILE_DIALOG (object); + + switch (property_id) + { + case PROP_TITLE: + g_value_set_string (value, self->title); + break; + + case PROP_MODAL: + g_value_set_boolean (value, self->modal); + break; + + case PROP_FILTERS: + g_value_set_object (value, self->filters); + break; + + case PROP_SHORTCUT_FOLDERS: + g_value_set_object (value, self->shortcut_folders); + break; + + case PROP_CURRENT_FILTER: + g_value_set_object (value, self->current_filter); + break; + + case PROP_CURRENT_FOLDER: + g_value_set_object (value, self->current_folder); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_file_dialog_set_property (GObject *object, + unsigned int prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkFileDialog *self = GTK_FILE_DIALOG (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_file_dialog_set_title (self, g_value_get_string (value)); + break; + + case PROP_MODAL: + gtk_file_dialog_set_modal (self, g_value_get_boolean (value)); + break; + + case PROP_FILTERS: + gtk_file_dialog_set_filters (self, g_value_get_object (value)); + break; + + case PROP_SHORTCUT_FOLDERS: + gtk_file_dialog_set_shortcut_folders (self, g_value_get_object (value)); + break; + + case PROP_CURRENT_FILTER: + gtk_file_dialog_set_current_filter (self, g_value_get_object (value)); + break; + + case PROP_CURRENT_FOLDER: + gtk_file_dialog_set_current_folder (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_file_dialog_class_init (GtkFileDialogClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = gtk_file_dialog_finalize; + object_class->get_property = gtk_file_dialog_get_property; + object_class->set_property = gtk_file_dialog_set_property; + + /** + * GtkFileDialog:title: (attributes org.gtk.Property.get=gtk_file_dialog_get_title org.gtk.Property.set=gtk_file_dialog_set_title) + * + * A title that may be shown on the file chooser dialog. + * + * Since: 4.10 + */ + properties[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFileDialog:modal: (attributes org.gtk.Property.get=gtk_file_dialog_get_modal org.gtk.Property.set=gtk_file_dialog_set_modal) + * + * Whether the file chooser dialog is modal. + * + * Since: 4.10 + */ + properties[PROP_MODAL] = + g_param_spec_boolean ("modal", NULL, NULL, + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFileDialog:filters: (attributes org.gtk.Property.get=gtk_file_dialog_get_filters org.gtk.Property.set=gtk_file_dialog_set_filters) + * + * The list of filters. + * + * Since: 4.10 + */ + properties[PROP_FILTERS] = + g_param_spec_object ("filters", NULL, NULL, + G_TYPE_LIST_MODEL, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFileDialog:shortcut-folders: (attributes org.gtk.Property.get=gtk_file_dialog_get_shortcut_folders org.gtk.Property.set=gtk_file_dialog_set_shortcut_folders) + * + * The list of shortcut folders. + * + * Since: 4.10 + */ + properties[PROP_SHORTCUT_FOLDERS] = + g_param_spec_object ("shortcut-folders", NULL, NULL, + G_TYPE_LIST_MODEL, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFileDialog:current-filter: (attributes org.gtk.Property.get=gtk_file_dialog_get_current_filter org.gtk.Property.set=gtk_file_dialog_set_current_filter) + * + * The current filter, that is, the filter that is initially + * active in the file chooser dialog. + * + * Since: 4.10 + */ + properties[PROP_CURRENT_FILTER] = + g_param_spec_object ("current-filter", NULL, NULL, + GTK_TYPE_FILE_FILTER, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFileDialog:current-folder: (attributes org.gtk.Property.get=gtk_file_dialog_get_current_folder org.gtk.Property.set=gtk_file_dialog_set_current_folder) + * + * The current folder, that is, the directory that is initially + * opened in the file chooser dialog, unless overridden by parameters + * of the async call. + * + * Since: 4.10 + */ + properties[PROP_CURRENT_FOLDER] = + g_param_spec_object ("current-folder", NULL, NULL, + G_TYPE_FILE, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +/* }}} */ +/* {{{ Utilities */ + +static void +file_chooser_set_filters (GtkFileChooser *chooser, + GListModel *filters) +{ + if (!filters) + return; + + for (unsigned int i = 0; i < g_list_model_get_n_items (filters); i++) + { + GtkFileFilter *filter = g_list_model_get_item (filters, i); + gtk_file_chooser_add_filter (chooser, filter); + g_object_unref (filter); + } +} + +static void +file_chooser_set_shortcut_folders (GtkFileChooser *chooser, + GListModel *shortcut_folders) +{ + if (!shortcut_folders) + return; + + for (unsigned int i = 0; i < g_list_model_get_n_items (shortcut_folders); i++) + { + GFile *folder = g_list_model_get_item (shortcut_folders, i); + GError *error = NULL; + + if (!gtk_file_chooser_add_shortcut_folder (chooser, folder, &error)) + { + g_critical ("%s", error->message); + g_clear_error (&error); + } + + g_object_unref (folder); + } +} + +/* }}} */ +/* {{{ API: Constructor */ + +/** + * gtk_file_dialog_new: + * + * Creates a new `GtkFileDialog` object. + * + * Returns: the new `GtkFileDialog` + * + * Since: 4.10 + */ +GtkFileDialog * +gtk_file_dialog_new (void) +{ + return g_object_new (GTK_TYPE_FILE_DIALOG, NULL); +} + +/* }}} */ +/* {{{ API: Getters and setters */ + +/** + * gtk_file_dialog_get_title: + * @self: a `GtkFileDialog` + * + * Returns the title that will be shown on the + * file chooser dialog. + * + * Returns: the title + * + * Since: 4.10 + */ +const char * +gtk_file_dialog_get_title (GtkFileDialog *self) +{ + g_return_val_if_fail (GTK_IS_FILE_DIALOG (self), NULL); + + return self->title; +} + +/** + * gtk_file_dialog_set_title: + * @self: a `GtkFileDialog` + * @title: the new title + * + * Sets the title that will be shown on the + * file chooser dialog. + * + * Since: 4.10 + */ +void +gtk_file_dialog_set_title (GtkFileDialog *self, + const char *title) +{ + char *new_title; + + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + g_return_if_fail (title != NULL); + + if (g_strcmp0 (self->title, title) == 0) + return; + + new_title = g_strdup (title); + g_free (self->title); + self->title = new_title; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +/** + * gtk_file_dialog_get_modal: + * @self: a `GtkFileDialog` + * + * Returns whether the file chooser dialog + * blocks interaction with the parent window + * while it is presented. + * + * Returns: `TRUE` if the file chooser dialog is modal + * + * Since: 4.10 + */ +gboolean +gtk_file_dialog_get_modal (GtkFileDialog *self) +{ + g_return_val_if_fail (GTK_IS_FILE_DIALOG (self), TRUE); + + return self->modal; +} + +/** + * gtk_file_dialog_set_modal: + * @self: a `GtkFileDialog` + * @modal: the new value + * + * Sets whether the file chooser dialog + * blocks interaction with the parent window + * while it is presented. + * + * Since: 4.10 + */ +void +gtk_file_dialog_set_modal (GtkFileDialog *self, + gboolean modal) +{ + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + + if (self->modal == modal) + return; + + self->modal = modal; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODAL]); +} + +/** + * gtk_file_dialog_get_filters: + * @self: a `GtkFileDialog` + * + * Gets the filters that will be offered to the user + * in the file chooser dialog. + * + * Returns: (transfer none) (nullable): the filters, as + * a `GListModel` of `GtkFileFilters` + * + * Since: 4.10 + */ +GListModel * +gtk_file_dialog_get_filters (GtkFileDialog *self) +{ + g_return_val_if_fail (GTK_IS_FILE_DIALOG (self), NULL); + + return self->filters; +} + +/** + * gtk_file_dialog_set_filters: + * @self: a `GtkFileDialog` + * @filters: a `GListModel` of `GtkFileFilters` + * + * Sets the filters that will be offered to the user + * in the file chooser dialog. + * + * Since: 4.10 + */ +void +gtk_file_dialog_set_filters (GtkFileDialog *self, + GListModel *filters) +{ + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + g_return_if_fail (G_IS_LIST_MODEL (filters)); + + if (!g_set_object (&self->filters, filters)) + return; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILTERS]); +} + +/** + * gtk_file_dialog_get_shortcut_folders: + * @self: a `GtkFileDialog` + * + * Gets the shortcut folders that will be available to + * the user in the file chooser dialog. + * + * Returns: (nullable) (transfer none): the shortcut + * folders, as a `GListModel` of `GFiles` + * + * Since: 4.10 + */ +GListModel * +gtk_file_dialog_get_shortcut_folders (GtkFileDialog *self) +{ + g_return_val_if_fail (GTK_IS_FILE_DIALOG (self), NULL); + + return self->shortcut_folders; +} + +/** + * gtk_file_dialog_set_shortcut_folders: + * @self: a `GtkFileDialog` + * @shortcut_folders: a `GListModel` of `GFiles` + * + * Sets the shortcut folders that will be available to + * the user in the file chooser dialog. + * + * Since: 4.10 + */ +void +gtk_file_dialog_set_shortcut_folders (GtkFileDialog *self, + GListModel *shortcut_folders) +{ + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + g_return_if_fail (G_IS_LIST_MODEL (shortcut_folders)); + + if (!g_set_object (&self->shortcut_folders, shortcut_folders)) + return; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHORTCUT_FOLDERS]); +} + +/** + * gtk_file_dialog_get_current_filter: + * @self: a `GtkFileDialog` + * + * Gets the filter that will be selected by default + * in the file chooser dialog. + * + * Returns: (transfer none) (nullable): the current filter + * + * Since: 4.10 + */ +GtkFileFilter * +gtk_file_dialog_get_current_filter (GtkFileDialog *self) +{ + g_return_val_if_fail (GTK_IS_FILE_DIALOG (self), NULL); + + return self->current_filter; +} + +/** + * gtk_file_dialog_set_current_filter: + * @self: a `GtkFileDialog` + * @filter: (nullable): a `GtkFileFilter` + * + * Sets the filters that will be selected by default + * in the file chooser dialog. + * + * Since: 4.10 + */ +void +gtk_file_dialog_set_current_filter (GtkFileDialog *self, + GtkFileFilter *filter) +{ + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + g_return_if_fail (filter == NULL || GTK_IS_FILE_FILTER (filter)); + + if (!g_set_object (&self->current_filter, filter)) + return; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CURRENT_FILTER]); +} + +/** + * gtk_file_dialog_get_current_folder: + * @self: a `GtkFileDialog` + * + * Gets the folder that will be set as the + * initial folder in the file chooser dialog. + * + * Returns: (nullable) (transfer none): the folder + * + * Since: 4.10 + */ +GFile * +gtk_file_dialog_get_current_folder (GtkFileDialog *self) +{ + g_return_val_if_fail (GTK_IS_FILE_DIALOG (self), NULL); + + return self->current_folder; +} + +/** + * gtk_file_dialog_set_current_folder: + * @self: a `GtkFileDialog` + * @folder: (nullable): a `GFile` + * + * Sets the folder that will be set as the + * initial folder in the file chooser dialog, + * unless overridden by parameters of the async + * call. + * + * Since: 4.10 + */ +void +gtk_file_dialog_set_current_folder (GtkFileDialog *self, + GFile *folder) +{ + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + g_return_if_fail (folder == NULL || G_IS_FILE (folder)); + + if (!g_set_object (&self->current_folder, folder)) + return; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CURRENT_FOLDER]); +} + +/* }}} */ +/* {{{ Async implementation */ + +static void response_cb (GTask *task, + int response); + +static void +cancelled_cb (GCancellable *cancellable, + GTask *task) +{ + response_cb (task, GTK_RESPONSE_CLOSE); +} + +static void +response_cb (GTask *task, + int response) +{ + GCancellable *cancellable; + + cancellable = g_task_get_cancellable (task); + + if (cancellable) + g_signal_handlers_disconnect_by_func (cancellable, cancelled_cb, task); + + if (response == GTK_RESPONSE_ACCEPT) + { + GtkFileChooser *chooser; + GListModel *files; + + chooser = GTK_FILE_CHOOSER (g_task_get_task_data (task)); + files = gtk_file_chooser_get_files (chooser); + g_task_return_pointer (task, files, g_object_unref); + } + else if (response == GTK_RESPONSE_CLOSE) + g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_ABORTED, "Aborted by application"); + else if (response == GTK_RESPONSE_CANCEL) + g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_CANCELLED, "Cancelled by user"); + else + g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED, "Unknown failure (%d)", response); + + g_object_unref (task); +} + +static void +dialog_response (GtkDialog *dialog, + int response, + GTask *task) +{ + response_cb (task, response); +} + +static GtkFileChooserNative * +create_file_chooser (GtkFileDialog *self, + GtkWindow *parent, + GtkFileChooserAction action, + gboolean select_multiple) +{ + GtkFileChooserNative *chooser; + const char *accept; + const char *title; + + switch (action) + { + case GTK_FILE_CHOOSER_ACTION_OPEN: + accept = _("_Open"); + title = select_multiple ? _("Pick Files") : _("Pick a File"); + break; + + case GTK_FILE_CHOOSER_ACTION_SAVE: + accept = _("_Save"); + title = _("Save a File"); + break; + + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + accept = _("_Select"); + title = select_multiple ? _("Select Folders") : _("Select a Folder"); + break; + + default: + g_assert_not_reached (); + } + + chooser = gtk_file_chooser_native_new (title, parent, action, accept, _("_Cancel")); + + gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (chooser), self->modal); + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (chooser), select_multiple); + + file_chooser_set_filters (GTK_FILE_CHOOSER (chooser), self->filters); + if (self->current_filter) + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), self->current_filter); + file_chooser_set_shortcut_folders (GTK_FILE_CHOOSER (chooser), self->shortcut_folders); + if (self->current_folder) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), self->current_folder, NULL); + + return chooser; +} + +static GFile * +finish_file_op (GtkFileDialog *self, + GTask *task, + GError **error) +{ + GListModel *files; + + files = g_task_propagate_pointer (task, error); + if (files) + { + GFile *file; + + g_assert (g_list_model_get_n_items (files) == 1); + + file = g_list_model_get_item (files, 0); + g_object_unref (files); + + return file; + } + + return NULL; +} + +static GListModel * +finish_multiple_files_op (GtkFileDialog *self, + GTask *task, + GError **error) +{ + return G_LIST_MODEL (g_task_propagate_pointer (task, error)); +} + +/* }}} */ +/* {{{ Async API */ + +/** + * gtk_file_dialog_open: + * @self: a `GtkFileDialog` + * @parent: (nullable): the parent `GtkWindow` + * @current_file: (nullable): the file to select initially + * @cancellable: (nullable): a `GCancellable` to cancel the operation + * @callback: (scope async): a callback to call when the operation is complete + * @user_data: (closure callback): data to pass to @callback + * + * This function initiates a file selection operation by + * presenting a file chooser dialog to the user. + * + * If you pass @current_file, the file chooser will initially be + * opened in the parent directory of that file, otherwise, it + * will be in the directory [property@Gtk.FileDialog:current-folder]. + * + * The @callback will be called when the dialog is dismissed. + * It should call [method@Gtk.FileDialog.open_finish] + * to obtain the result. + * + * Since: 4.10 + */ +void +gtk_file_dialog_open (GtkFileDialog *self, + GtkWindow *parent, + GFile *current_file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GtkFileChooserNative *chooser; + GTask *task; + + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + + chooser = create_file_chooser (self, parent, GTK_FILE_CHOOSER_ACTION_OPEN, FALSE); + + if (current_file) + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (chooser), current_file, NULL); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gtk_file_dialog_open); + g_task_set_task_data (task, chooser, (GDestroyNotify) gtk_native_dialog_destroy); + + if (cancellable) + g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), task); + + g_signal_connect (chooser, "response", G_CALLBACK (dialog_response), task); + + gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser)); +} + +/** + * gtk_file_dialog_open_finish: + * @self: a `GtkFileDialog` + * @result: a `GAsyncResult` + * @error: return location for a [enum@Gtk.DialogError] error + * + * Finishes the [method@Gtk.FileDialog.open] call and + * returns the resulting file. + * + * Returns: (nullable) (transfer full): the file that was selected. + * Otherwise, `NULL` is returned and @error is set + * + * Since: 4.10 + */ +GFile * +gtk_file_dialog_open_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_get_source_tag (task) == gtk_file_dialog_open, NULL); + + return finish_file_op (self, task, error); +} + +/** + * gtk_file_dialog_select_folder: + * @self: a `GtkFileDialog` + * @parent: (nullable): the parent `GtkWindow` + * @current_folder: (nullable): the folder to select initially + * @cancellable: (nullable): a `GCancellable` to cancel the operation + * @callback: (scope async): a callback to call when the operation is complete + * @user_data: (closure callback): data to pass to @callback + * + * This function initiates a directory selection operation by + * presenting a file chooser dialog to the user. + * + * If you pass @current_folder, the file chooser will initially be + * opened in the parent directory of that folder, otherwise, it + * will be in the directory [property@Gtk.FileDialog:current-folder]. + * + * The @callback will be called when the dialog is dismissed. + * It should call [method@Gtk.FileDialog.select_folder_finish] + * to obtain the result. + * + * Since: 4.10 + */ +void +gtk_file_dialog_select_folder (GtkFileDialog *self, + GtkWindow *parent, + GFile *current_folder, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GtkFileChooserNative *chooser; + GTask *task; + + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + + chooser = create_file_chooser (self, parent, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, FALSE); + + if (current_folder) + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (chooser), current_folder, NULL); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gtk_file_dialog_select_folder); + g_task_set_task_data (task, chooser, (GDestroyNotify) gtk_native_dialog_destroy); + + if (cancellable) + g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), task); + + g_signal_connect (chooser, "response", G_CALLBACK (dialog_response), task); + + gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser)); +} + +/** + * gtk_file_dialog_select_folder_finish: + * @self: a `GtkFileDialog` + * @result: a `GAsyncResult` + * @error: return location for a [enum@Gtk.DialogError] error + * + * Finishes the [method@Gtk.FileDialog.select_folder] call and + * returns the resulting file. + * + * Returns: (nullable) (transfer full): the file that was selected. + * Otherwise, `NULL` is returned and @error is set + * + * Since: 4.10 + */ +GFile * +gtk_file_dialog_select_folder_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_get_source_tag (task) == gtk_file_dialog_select_folder, NULL); + + return finish_file_op (self, task, error); +} + +/** + * gtk_file_dialog_save: + * @self: a `GtkFileDialog` + * @parent: (nullable): the parent `GtkWindow` + * @current_file: (nullable): the initial file + * @current_name: (nullable): the initial filename to offer + * @cancellable: (nullable): a `GCancellable` to cancel the operation + * @callback: (scope async): a callback to call when the operation is complete + * @user_data: (closure callback): data to pass to @callback + * + * This function initiates a file save operation by + * presenting a file chooser dialog to the user. + * + * You should pass either @current_file if you have a file to + * save to, or @current_name, if you are creating a new file. + * + * If you pass @current_file, the file chooser will initially be + * opened in the parent directory of that file, otherwise, it + * will be in the directory [property@Gtk.FileDialog:current-folder]. + * + * The @callback will be called when the dialog is dismissed. + * It should call [method@Gtk.FileDialog.save_finish] + * to obtain the result. + * + * Since: 4.10 + */ +void +gtk_file_dialog_save (GtkFileDialog *self, + GtkWindow *parent, + GFile *current_file, + const char *current_name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GtkFileChooserNative *chooser; + GTask *task; + + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + + chooser = create_file_chooser (self, parent, GTK_FILE_CHOOSER_ACTION_SAVE, FALSE); + + if (current_file) + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (chooser), current_file, NULL); + else if (current_name) + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (chooser), current_name); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gtk_file_dialog_save); + g_task_set_task_data (task, chooser, (GDestroyNotify) gtk_native_dialog_destroy); + + if (cancellable) + g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), task); + + g_signal_connect (chooser, "response", G_CALLBACK (dialog_response), task); + + gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser)); +} + +/** + * gtk_file_dialog_save_finish: + * @self: a `GtkFileDialog` + * @result: a `GAsyncResult` + * @error: return location for a [enum@Gtk.DialogError] error + * + * Finishes the [method@Gtk.FileDialog.save] call and + * returns the resulting file. + * + * Returns: (nullable) (transfer full): the file that was selected. + * Otherwise, `NULL` is returned and @error is set + * + * Since: 4.10 + */ +GFile * +gtk_file_dialog_save_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_get_source_tag (task) == gtk_file_dialog_save, NULL); + + return finish_file_op (self, task, error); +} + +/** + * gtk_file_dialog_open_multiple: + * @self: a `GtkFileDialog` + * @parent: (nullable): the parent `GtkWindow` + * @cancellable: (nullable): a `GCancellable` to cancel the operation + * @callback: (scope async): a callback to call when the operation is complete + * @user_data: (closure callback): data to pass to @callback + * + * This function initiates a multi-file selection operation by + * presenting a file chooser dialog to the user. + * + * The file chooser will initially be opened in the directory + * [property@Gtk.FileDialog:current-folder]. + * + * The @callback will be called when the dialog is dismissed. + * It should call [method@Gtk.FileDialog.open_multiple_finish] + * to obtain the result. + * + * Since: 4.10 + */ +void +gtk_file_dialog_open_multiple (GtkFileDialog *self, + GtkWindow *parent, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GtkFileChooserNative *chooser; + GTask *task; + + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + + chooser = create_file_chooser (self, parent, GTK_FILE_CHOOSER_ACTION_OPEN, TRUE); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gtk_file_dialog_open_multiple); + g_task_set_task_data (task, chooser, (GDestroyNotify) gtk_native_dialog_destroy); + + if (cancellable) + g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), task); + + g_signal_connect (chooser, "response", G_CALLBACK (dialog_response), task); + + gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser)); +} + +/** + * gtk_file_dialog_open_multiple_finish: + * @self: a `GtkFileDialog` + * @result: a `GAsyncResult` + * @error: return location for a [enum@Gtk.DialogError] error + * + * Finishes the [method@Gtk.FileDialog.open] call and + * returns the resulting files in a `GListModel`. + * + * Returns: (nullable) (transfer full): the file that was selected, + * as a `GListModel` of `GFiles`. Otherwise, `NULL` is returned + * and @error is set + * + * Since: 4.10 + */ +GListModel * +gtk_file_dialog_open_multiple_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_get_source_tag (task) == gtk_file_dialog_open_multiple, NULL); + + return finish_multiple_files_op (self, task, error); +} + +/** + * gtk_file_dialog_select_multiple_folders: + * @self: a `GtkFileDialog` + * @parent: (nullable): the parent `GtkWindow` + * @cancellable: (nullable): a `GCancellable` to cancel the operation + * @callback: (scope async): a callback to call when the operation is complete + * @user_data: (closure callback): data to pass to @callback + * + * This function initiates a multi-directory selection operation by + * presenting a file chooser dialog to the user. + * + * The file chooser will initially be opened in the directory + * [property@Gtk.FileDialog:current-folder]. + * + * The @callback will be called when the dialog is dismissed. + * It should call [method@Gtk.FileDialog.select_multiple_folders_finish] + * to obtain the result. + * + * Since: 4.10 + */ +void +gtk_file_dialog_select_multiple_folders (GtkFileDialog *self, + GtkWindow *parent, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GtkFileChooserNative *chooser; + GTask *task; + + g_return_if_fail (GTK_IS_FILE_DIALOG (self)); + + chooser = create_file_chooser (self, parent, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, TRUE); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gtk_file_dialog_select_multiple_folders); + g_task_set_task_data (task, chooser, (GDestroyNotify) gtk_native_dialog_destroy); + + if (cancellable) + g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), task); + + g_signal_connect (chooser, "response", G_CALLBACK (dialog_response), task); + + gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser)); +} + +/** + * gtk_file_dialog_select_multiple_folders_finish: + * @self: a `GtkFileDialog` + * @result: a `GAsyncResult` + * @error: return location for a [enum@Gtk.DialogError] error + * + * Finishes the [method@Gtk.FileDialog.select_multiple_folders] + * call and returns the resulting files in a `GListModel`. + * + * Returns: (nullable) (transfer full): the file that was selected, + * as a `GListModel` of `GFiles`. Otherwise, `NULL` is returned + * and @error is set + * + * Since: 4.10 + */ +GListModel * +gtk_file_dialog_select_multiple_folders_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_get_source_tag (task) == gtk_file_dialog_select_multiple_folders, NULL); + + return finish_multiple_files_op (self, task, error); +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gtk/gtkfiledialog.h b/gtk/gtkfiledialog.h new file mode 100644 index 0000000000..34664d646b --- /dev/null +++ b/gtk/gtkfiledialog.h @@ -0,0 +1,151 @@ +/* GTK - The GIMP Toolkit + * + * Copyright (C) 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_FILE_DIALOG (gtk_file_dialog_get_type ()) + +GDK_AVAILABLE_IN_4_10 +G_DECLARE_FINAL_TYPE (GtkFileDialog, gtk_file_dialog, GTK, FILE_DIALOG, GObject) + +GDK_AVAILABLE_IN_4_10 +GtkFileDialog * gtk_file_dialog_new (void); + +GDK_AVAILABLE_IN_4_10 +const char * gtk_file_dialog_get_title (GtkFileDialog *self); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_set_title (GtkFileDialog *self, + const char *title); + +GDK_AVAILABLE_IN_4_10 +gboolean gtk_file_dialog_get_modal (GtkFileDialog *self); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_set_modal (GtkFileDialog *self, + gboolean modal); + +GDK_AVAILABLE_IN_4_10 +GListModel * gtk_file_dialog_get_filters (GtkFileDialog *self); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_set_filters (GtkFileDialog *self, + GListModel *filters); + +GDK_AVAILABLE_IN_4_10 +GtkFileFilter * gtk_file_dialog_get_current_filter (GtkFileDialog *self); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_set_current_filter (GtkFileDialog *self, + GtkFileFilter *filter); + +GDK_AVAILABLE_IN_4_10 +GListModel * gtk_file_dialog_get_shortcut_folders + (GtkFileDialog *self); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_set_shortcut_folders + (GtkFileDialog *self, + GListModel *shortcut_folders); + +GDK_AVAILABLE_IN_4_10 +GFile * gtk_file_dialog_get_current_folder (GtkFileDialog *self); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_set_current_folder (GtkFileDialog *self, + GFile *folder); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_open (GtkFileDialog *self, + GtkWindow *parent, + GFile *current_file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GDK_AVAILABLE_IN_4_10 +GFile * gtk_file_dialog_open_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_select_folder (GtkFileDialog *self, + GtkWindow *parent, + GFile *current_folder, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GDK_AVAILABLE_IN_4_10 +GFile * gtk_file_dialog_select_folder_finish + (GtkFileDialog *self, + GAsyncResult *result, + GError **error); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_save (GtkFileDialog *self, + GtkWindow *parent, + GFile *current_file, + const char *current_name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GDK_AVAILABLE_IN_4_10 +GFile * gtk_file_dialog_save_finish (GtkFileDialog *self, + GAsyncResult *result, + GError **error); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_open_multiple (GtkFileDialog *self, + GtkWindow *parent, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GDK_AVAILABLE_IN_4_10 +GListModel * gtk_file_dialog_open_multiple_finish + (GtkFileDialog *self, + GAsyncResult *result, + GError **error); + +GDK_AVAILABLE_IN_4_10 +void gtk_file_dialog_select_multiple_folders + (GtkFileDialog *self, + GtkWindow *parent, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GDK_AVAILABLE_IN_4_10 +GListModel * gtk_file_dialog_select_multiple_folders_finish + (GtkFileDialog *self, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 06cf4cec31..1d575fe3b6 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -229,6 +229,7 @@ gtk_public_sources = files([ 'gtkfilechooserdialog.c', 'gtkfilechoosernative.c', 'gtkfilechooserwidget.c', + 'gtkfiledialog.c', 'gtkfilefilter.c', 'gtkfilter.c', 'gtkfilterlistmodel.c', @@ -491,6 +492,7 @@ gtk_public_headers = files([ 'gtkfilechooserdialog.h', 'gtkfilechoosernative.h', 'gtkfilechooserwidget.h', + 'gtkfiledialog.h', 'gtkfilefilter.h', 'gtkfilter.h', 'gtkfilterlistmodel.h', -- 2.30.2