diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2009-11-08 01:44:44 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2009-11-08 03:01:46 +0800 |
commit | 86ecfc50539ddef82205551c11a6a13b135bbab4 (patch) | |
tree | cc25ca582935748885a23d665a1d9e1bbc1d4d9c | |
parent | aa66a17e401d73cbe394ed7f99bf73350e9b938b (diff) | |
download | gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.tar gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.tar.gz gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.tar.bz2 gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.tar.lz gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.tar.xz gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.tar.zst gsoc2013-evolution-86ecfc50539ddef82205551c11a6a13b135bbab4.zip |
Convert some "Save As" actions to run asynchronously.
This introduces e-shell-utils for miscellaneous utility functions
that integrate with the shell or shell settings. First function
is e_shell_run_save_dialog(), which automatically remembers the
selected folder in the file chooser dialog.
Also, kill some redundant save dialog functions, as well as some
write-this-string-to-disk functions that block.
29 files changed, 466 insertions, 396 deletions
diff --git a/addressbook/gui/widgets/e-addressbook-view.c b/addressbook/gui/widgets/e-addressbook-view.c index 1583f26c92..280c4d3820 100644 --- a/addressbook/gui/widgets/e-addressbook-view.c +++ b/addressbook/gui/widgets/e-addressbook-view.c @@ -1289,33 +1289,6 @@ e_addressbook_view_delete_selection(EAddressbookView *view, gboolean is_delete) } void -e_addressbook_view_save_as (EAddressbookView *view, - gboolean all) -{ - GList *list = NULL; - EBook *book; - - g_return_if_fail (E_IS_ADDRESSBOOK_VIEW (view)); - - book = e_addressbook_model_get_book (view->priv->model); - - if (all) { - EBookQuery *query; - - query = e_book_query_any_field_contains (""); - e_book_get_contacts (book, query, &list, NULL); - e_book_query_unref (query); - } else - list = e_addressbook_view_get_selected (view); - - if (list != NULL) { - eab_contact_list_save (_("Save as vCard..."), list, NULL); - g_list_foreach (list, (GFunc) g_object_unref, NULL); - g_list_free (list); - } -} - -void e_addressbook_view_view (EAddressbookView *view) { EAddressbookModel *model; diff --git a/addressbook/gui/widgets/e-addressbook-view.h b/addressbook/gui/widgets/e-addressbook-view.h index 4e0f82eeb0..ca709ff347 100644 --- a/addressbook/gui/widgets/e-addressbook-view.h +++ b/addressbook/gui/widgets/e-addressbook-view.h @@ -97,8 +97,6 @@ ESelectionModel * EShellView * e_addressbook_view_get_shell_view (EAddressbookView *view); ESource * e_addressbook_view_get_source (EAddressbookView *view); -void e_addressbook_view_save_as (EAddressbookView *view, - gboolean all); void e_addressbook_view_view (EAddressbookView *view); void e_addressbook_view_print (EAddressbookView *view, GtkPrintOperationAction action); diff --git a/addressbook/gui/widgets/eab-gui-util.c b/addressbook/gui/widgets/eab-gui-util.c index 0aa63850a2..24fce0a99a 100644 --- a/addressbook/gui/widgets/eab-gui-util.c +++ b/addressbook/gui/widgets/eab-gui-util.c @@ -196,105 +196,6 @@ eab_prompt_save_dialog (GtkWindow *parent) return e_error_run (parent, "addressbook:prompt-save", NULL); } -static gint -file_exists(GtkWindow *window, const gchar *filename) -{ - GtkWidget *dialog; - gint response; - gchar * utf8_filename; - - utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); - dialog = gtk_message_dialog_new (window, - 0, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_NONE, - /* For Translators only: "it" refers to the filename %s. */ - _("%s already exists\nDo you want to overwrite it?"), utf8_filename); - g_free (utf8_filename); - gtk_dialog_add_buttons (GTK_DIALOG (dialog), - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - _("Overwrite"), GTK_RESPONSE_ACCEPT, - NULL); - - response = gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); - return response; -} - -typedef struct { - GtkWidget *filesel; - gchar *vcard; - gboolean has_multiple_contacts; -} SaveAsInfo; - -static void -save_it(GtkWidget *widget, SaveAsInfo *info) -{ - const gchar *filename; - gchar *uri; - gint response = 0; - - filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (info->filesel)); - uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (info->filesel)); - - if (filename && g_file_test (filename, G_FILE_TEST_EXISTS)) { - response = file_exists(GTK_WINDOW (info->filesel), filename); - switch (response) { - case GTK_RESPONSE_ACCEPT : /* Overwrite */ - break; - case GTK_RESPONSE_CANCEL : /* cancel */ - return; - } - } - - if (!e_write_file_uri (uri, info->vcard)) { - gchar *err_str_ext; - if (info->has_multiple_contacts) { - /* more than one, finding the total number of contacts might - * hit performance while saving large number of contacts - */ - err_str_ext = ngettext ("contact", "contacts", 2); - } - else { - err_str_ext = ngettext ("contact", "contacts", 1); - } - - /* translators: Arguments, err_str_ext (item to be saved: "contact"/"contacts"), - * destination file name, and error code will fill the placeholders - * {0}, {1} and {2}, respectively in the error message formed - */ - e_error_run (GTK_WINDOW (info->filesel), "addressbook:save-error", - err_str_ext, filename, g_strerror (errno), NULL); - gtk_widget_destroy(GTK_WIDGET(info->filesel)); - return; - } - - gtk_widget_destroy(GTK_WIDGET(info->filesel)); -} - -static void -close_it(GtkWidget *widget, SaveAsInfo *info) -{ - gtk_widget_destroy (GTK_WIDGET (info->filesel)); -} - -static void -destroy_it(gpointer data, GObject *where_the_object_was) -{ - SaveAsInfo *info = data; - g_free (info->vcard); - g_free (info); -} - -static void -filechooser_response (GtkWidget *widget, gint response_id, SaveAsInfo *info) -{ - if (response_id == GTK_RESPONSE_ACCEPT) - save_it (widget, info); - else - close_it (widget, info); -} - static gchar * make_safe_filename (gchar *name) { @@ -381,99 +282,32 @@ eab_select_source (const gchar *title, const gchar *message, const gchar *select } void -eab_contact_save (gchar *title, EContact *contact, GtkWindow *parent_window) +eab_suggest_filename (GtkFileChooser *file_chooser, + GList *contact_list) { - GtkWidget *filesel; - gchar *file; - gchar *name; - SaveAsInfo *info = g_new(SaveAsInfo, 1); - - name = e_contact_get (contact, E_CONTACT_FILE_AS); - file = make_safe_filename (name); - - info->has_multiple_contacts = FALSE; - - filesel = gtk_file_chooser_dialog_new (title, - parent_window, - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_dialog_set_default_response (GTK_DIALOG (filesel), GTK_RESPONSE_ACCEPT); - - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filesel), g_get_home_dir ()); - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filesel), file); - gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (filesel), FALSE); - - info->filesel = filesel; - info->vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30); - - g_signal_connect (G_OBJECT (filesel), "response", - G_CALLBACK (filechooser_response), info); - g_object_weak_ref (G_OBJECT (filesel), destroy_it, info); - - if (parent_window) { - gtk_window_set_transient_for (GTK_WINDOW (filesel), - parent_window); - gtk_window_set_modal (GTK_WINDOW (filesel), TRUE); - } + gchar *current_name = NULL; - gtk_widget_show(GTK_WIDGET(filesel)); - g_free (file); -} + g_return_if_fail (GTK_IS_FILE_CHOOSER (file_chooser)); + g_return_if_fail (contact_list != NULL); -void -eab_contact_list_save (gchar *title, GList *list, GtkWindow *parent_window) -{ - GtkWidget *filesel; - SaveAsInfo *info = g_new(SaveAsInfo, 1); - gchar *file; - - filesel = gtk_file_chooser_dialog_new (title, - parent_window, - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_dialog_set_default_response (GTK_DIALOG (filesel), GTK_RESPONSE_ACCEPT); - gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (filesel), FALSE); - - /* Check if the list has more than one contact */ - if (g_list_next (list)) - info->has_multiple_contacts = TRUE; - else - info->has_multiple_contacts = FALSE; + if (g_list_length (contact_list) == 1) { + EContact *contact = E_CONTACT (contact_list->data); + gchar *string; - /* This is a filename. Translators take note. */ - if (list && list->data && list->next == NULL) { - gchar *name; - name = e_contact_get (E_CONTACT (list->data), E_CONTACT_FILE_AS); - if (!name) - name = e_contact_get (E_CONTACT (list->data), E_CONTACT_FULL_NAME); - - file = make_safe_filename (name); - } else { - file = make_safe_filename (_("list")); + string = e_contact_get (contact, E_CONTACT_FILE_AS); + if (string == NULL) + string = e_contact_get (contact, E_CONTACT_FULL_NAME); + if (string != NULL) + current_name = make_safe_filename (string); + g_free (string); } - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filesel), g_get_home_dir ()); - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filesel), file); - - info->filesel = filesel; - info->vcard = eab_contact_list_to_string (list); + if (current_name == NULL) + current_name = make_safe_filename (_("list")); - g_signal_connect (G_OBJECT (filesel), "response", - G_CALLBACK (filechooser_response), info); - g_object_weak_ref (G_OBJECT (filesel), destroy_it, info); - - if (parent_window) { - gtk_window_set_transient_for (GTK_WINDOW (filesel), - parent_window); - gtk_window_set_modal (GTK_WINDOW (filesel), TRUE); - } + gtk_file_chooser_set_current_name (file_chooser, current_name); - gtk_widget_show(GTK_WIDGET(filesel)); - g_free (file); + g_free (current_name); } typedef struct ContactCopyProcess_ ContactCopyProcess; diff --git a/addressbook/gui/widgets/eab-gui-util.h b/addressbook/gui/widgets/eab-gui-util.h index 7422eabb65..838343a357 100644 --- a/addressbook/gui/widgets/eab-gui-util.h +++ b/addressbook/gui/widgets/eab-gui-util.h @@ -43,13 +43,8 @@ void eab_transfer_contacts (EBook *source, gboolean delete_from_source, GtkWindow *parent_window); -void eab_contact_save (gchar *title, - EContact *contact, - GtkWindow *parent_window); - -void eab_contact_list_save (gchar *title, - GList *list, - GtkWindow *parent_window); +void eab_suggest_filename (GtkFileChooser *file_chooser, + GList *contact_list); GtkWidget *eab_create_image_chooser_widget (gchar *name, gchar *string1, gchar *string2, gint int1, gint int2); diff --git a/doc/reference/shell/eshell-sections.txt b/doc/reference/shell/eshell-sections.txt index b69e65f3fd..ae9ecaf0f6 100644 --- a/doc/reference/shell/eshell-sections.txt +++ b/doc/reference/shell/eshell-sections.txt @@ -360,7 +360,6 @@ e_datetime_format_format_tm <FILE>e-dialog-utils</FILE> <TITLE>Dialog Window Functions (Legacy)</TITLE> e_notice -e_file_dialog_save e_file_get_save_filesel e_file_can_save e_file_check_local @@ -554,7 +553,6 @@ e_str_compare e_str_case_compare e_collate_compare e_int_compare -e_write_file_uri e_color_to_value e_format_number ESortCompareFunc diff --git a/doc/reference/shell/tmpl/e-dialog-utils.sgml b/doc/reference/shell/tmpl/e-dialog-utils.sgml index 55b595a51f..233bac2f49 100644 --- a/doc/reference/shell/tmpl/e-dialog-utils.sgml +++ b/doc/reference/shell/tmpl/e-dialog-utils.sgml @@ -28,17 +28,6 @@ Dialog Window Functions (Legacy) @Varargs: -<!-- ##### FUNCTION e_file_dialog_save ##### --> -<para> - -</para> - -@parent: -@title: -@fname: -@Returns: - - <!-- ##### FUNCTION e_file_get_save_filesel ##### --> <para> diff --git a/doc/reference/shell/tmpl/e-util.sgml b/doc/reference/shell/tmpl/e-util.sgml index 50af7459be..15d7d5f85c 100644 --- a/doc/reference/shell/tmpl/e-util.sgml +++ b/doc/reference/shell/tmpl/e-util.sgml @@ -176,16 +176,6 @@ Miscellaneous Utility Functions @Returns: -<!-- ##### FUNCTION e_write_file_uri ##### --> -<para> - -</para> - -@filename: -@data: -@Returns: - - <!-- ##### FUNCTION e_color_to_value ##### --> <para> diff --git a/doc/reference/shell/tmpl/eshell-unused.sgml b/doc/reference/shell/tmpl/eshell-unused.sgml index 99ad9d4067..2f908a761f 100644 --- a/doc/reference/shell/tmpl/eshell-unused.sgml +++ b/doc/reference/shell/tmpl/eshell-unused.sgml @@ -1850,6 +1850,16 @@ intelligent @revision: @Returns: +<!-- ##### FUNCTION e_file_dialog_save ##### --> +<para> + +</para> + +@parent: +@title: +@fname: +@Returns: + <!-- ##### FUNCTION e_shell_backend_get_filename ##### --> <para> @@ -2254,6 +2264,15 @@ intelligent @uri: @Returns: +<!-- ##### FUNCTION e_write_file_uri ##### --> +<para> + +</para> + +@filename: +@data: +@Returns: + <!-- ##### FUNCTION es_event_get_type ##### --> <para> diff --git a/e-util/e-dialog-utils.c b/e-util/e-dialog-utils.c index 952d73f303..df0d8f5129 100644 --- a/e-util/e-dialog-utils.c +++ b/e-util/e-dialog-utils.c @@ -72,38 +72,6 @@ e_notice (gpointer parent, GtkMessageType type, const gchar *format, ...) gtk_widget_destroy (dialog); } -gchar * -e_file_dialog_save (GtkWindow *parent, - const gchar *title, - const gchar *fname) -{ - GtkWidget *dialog; - gchar *filename = NULL; - gchar *uri; - - g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL); - - dialog = e_file_get_save_filesel ( - parent, title, fname, GTK_FILE_CHOOSER_ACTION_SAVE); - - if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) - goto exit; - - uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); - - if (e_file_can_save (GTK_WINDOW (dialog), uri)) { - e_file_update_save_path ( - gtk_file_chooser_get_current_folder_uri ( - GTK_FILE_CHOOSER (dialog)), TRUE); - filename = uri; /* FIXME This looks wrong. */ - } - -exit: - gtk_widget_destroy (dialog); - - return filename; -} - /** * e_file_get_save_filesel: * @parent: parent window diff --git a/e-util/e-dialog-utils.h b/e-util/e-dialog-utils.h index b4f305bdd5..2457f1c37a 100644 --- a/e-util/e-dialog-utils.h +++ b/e-util/e-dialog-utils.h @@ -29,9 +29,6 @@ void e_notice (gpointer parent, GtkMessageType type, const gchar *format, ...); -gchar * e_file_dialog_save (GtkWindow *parent, - const gchar *title, - const gchar *fname); GtkWidget * e_file_get_save_filesel (GtkWindow *parent, const gchar *title, const gchar *name, diff --git a/e-util/e-file-utils.c b/e-util/e-file-utils.c index f8adcc7a60..015da072f3 100644 --- a/e-util/e-file-utils.c +++ b/e-util/e-file-utils.c @@ -86,6 +86,24 @@ file_replace_contents_cb (GFile *file, g_object_unref (activity); } +/** + * e_file_replace_contents_async: + * @file: input #GFile + * @contents: string of contents to replace the file with + * @length: the length of @contents in bytes + * @etag: a new entity tag for the @file, or %NULL + * @make_backup: %TRUE if a backup should be created + * @flags: a set of #GFileCreateFlags + * @callback: a #GAsyncReadyCallback to call when the request is satisfied + * @user_data: the data to pass to the callback function + * + * This is a wrapper for g_file_replace_contents_async() that also returns + * an #EActivity to track the file operation. Cancelling the activity will + * cancel the file operation. See g_file_replace_contents_async() for more + * details. + * + * Returns: an #EActivity for the file operation + **/ EActivity * e_file_replace_contents_async (GFile *file, const gchar *contents, @@ -111,9 +129,12 @@ e_file_replace_contents_async (GFile *file, uri = g_file_get_uri (file); filename = g_filename_from_uri (uri, &hostname, NULL); - basename = g_filename_display_basename (filename); + if (filename != NULL) + basename = g_filename_display_basename (filename); + else + basename = g_strdup (_("(Unknown Filename)")); - if (hostname != NULL) { + if (hostname == NULL) { /* Translators: The string value is the basename of a file. */ format = _("Writing \"%s\""); description = g_strdup_printf (format, basename); @@ -151,6 +172,20 @@ e_file_replace_contents_async (GFile *file, return activity; } +/** + * e_file_replace_contents_finish: + * @file: input #GFile + * @result: a #GAsyncResult + * @new_etag: return location for a new entity tag + * @error: return location for a #GError, or %NULL + * + * Finishes an asynchronous replace of the given @file. See + * e_file_replace_contents_async(). Sets @new_etag to the new entity + * tag for the document, if present. Free it with g_free() when it is + * no longer needed. + * + * Returns: %TRUE on success, %FALSE on failure + **/ gboolean e_file_replace_contents_finish (GFile *file, GAsyncResult *result, diff --git a/e-util/e-util.c b/e-util/e-util.c index bf1f387d15..9ad252a37d 100644 --- a/e-util/e-util.c +++ b/e-util/e-util.c @@ -618,65 +618,6 @@ e_int_compare (gconstpointer x, gconstpointer y) return (nx == ny) ? 0 : (nx < ny) ? -1 : 1; } -gboolean -e_write_file_uri (const gchar *filename, - const gchar *data) -{ - gboolean res; - gsize length; - GFile *file; - GOutputStream *stream; - GError *error = NULL; - - g_return_val_if_fail (filename != NULL, FALSE); - g_return_val_if_fail (data != NULL, FALSE); - - length = strlen (data); - - /* if it is uri, then create file for uri, otherwise for path */ - if (strstr (filename, "://")) - file = g_file_new_for_uri (filename); - else - file = g_file_new_for_path (filename); - - if (!file) { - g_warning ("Couldn't save item"); - return FALSE; - } - - stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error)); - g_object_unref (file); - - if (!stream || error) { - g_warning ("Couldn't save item%s%s", error ? ": " : "", error ? error->message : ""); - - if (stream) - g_object_unref (stream); - - if (error) - g_error_free (error); - - return FALSE; - } - - res = g_output_stream_write_all (stream, data, length, NULL, NULL, &error); - - if (error) { - g_warning ("Couldn't save item: %s", error->message); - g_clear_error (&error); - } - - g_output_stream_close (stream, NULL, &error); - g_object_unref (stream); - - if (error) { - g_warning ("Couldn't close output stream: %s", error->message); - g_error_free (error); - } - - return res; -} - /** * e_color_to_value: * color: a #GdkColor diff --git a/e-util/e-util.h b/e-util/e-util.h index 88ebaa2af7..81ca05327c 100644 --- a/e-util/e-util.h +++ b/e-util/e-util.h @@ -81,8 +81,6 @@ gint e_collate_compare (gconstpointer x, gconstpointer y); gint e_int_compare (gconstpointer x, gconstpointer y); -gboolean e_write_file_uri (const gchar *filename, - const gchar *data); guint32 e_color_to_value (GdkColor *color); /* This only makes a filename safe for usage as a filename. diff --git a/modules/addressbook/e-book-shell-view-actions.c b/modules/addressbook/e-book-shell-view-actions.c index c680c64ede..6882f29e6e 100644 --- a/modules/addressbook/e-book-shell-view-actions.c +++ b/modules/addressbook/e-book-shell-view-actions.c @@ -192,14 +192,75 @@ static void action_address_book_save_as_cb (GtkAction *action, EBookShellView *book_shell_view) { + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; EBookShellContent *book_shell_content; + EAddressbookModel *model; EAddressbookView *view; + EActivity *activity; + EBookQuery *query; + EBook *book; + GList *list = NULL; + GFile *file; + gchar *string; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); book_shell_content = book_shell_view->priv->book_shell_content; view = e_book_shell_content_get_current_view (book_shell_content); g_return_if_fail (view != NULL); - e_addressbook_view_save_as (view, TRUE); + model = e_addressbook_view_get_model (view); + book = e_addressbook_model_get_book (model); + + query = e_book_query_any_field_contains (""); + e_book_get_contacts (book, query, &list, NULL); + e_book_query_unref (query); + + if (list == NULL) + goto exit; + + file = e_shell_run_save_dialog ( + shell, _("Save as vCard"), + (GtkCallback) eab_suggest_filename, list); + if (file == NULL) + goto exit; + + string = eab_contact_list_to_string (list); + if (string == NULL) { + g_warning ("Could not convert contact list to a string"); + g_object_unref (file); + goto exit; + } + + /* XXX No callback means errors are discarded. + * + * There's an EError for this which I'm not using + * until I figure out a better way to display errors: + * + * "addressbook:save-error" + */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + + g_object_unref (file); + +exit: + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); } static void @@ -454,14 +515,67 @@ static void action_contact_save_as_cb (GtkAction *action, EBookShellView *book_shell_view) { + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; EBookShellContent *book_shell_content; EAddressbookView *view; + EActivity *activity; + GList *list; + GFile *file; + gchar *string; + + shell_view = E_SHELL_VIEW (book_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); book_shell_content = book_shell_view->priv->book_shell_content; view = e_book_shell_content_get_current_view (book_shell_content); g_return_if_fail (view != NULL); - e_addressbook_view_save_as (view, FALSE); + list = e_addressbook_view_get_selected (view); + + if (list == NULL) + goto exit; + + file = e_shell_run_save_dialog ( + shell, _("Save as vCard"), + (GtkCallback) eab_suggest_filename, list); + if (file == NULL) + goto exit; + + string = eab_contact_list_to_string (list); + if (string == NULL) { + g_warning ("Could not convert contact list to a string"); + g_object_unref (file); + goto exit; + } + + /* XXX No callback means errors are discarded. + * + * There an EError for this which I'm not using + * until I figure out a better way to display errors: + * + * "addressbook:save-error" + */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + + g_object_unref (file); + +exit: + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); } static void diff --git a/modules/addressbook/e-book-shell-view-private.h b/modules/addressbook/e-book-shell-view-private.h index fa8624692a..c1aa300739 100644 --- a/modules/addressbook/e-book-shell-view-private.h +++ b/modules/addressbook/e-book-shell-view-private.h @@ -34,11 +34,14 @@ #include "e-util/e-util.h" #include "e-util/e-binding.h" +#include "e-util/e-file-utils.h" #include "e-util/gconf-bridge.h" #include "shell/e-shell-content.h" #include "shell/e-shell-sidebar.h" +#include "shell/e-shell-utils.h" #include "misc/e-popup-action.h" +#include "addressbook/util/eab-book-util.h" #include "addressbook/gui/contact-editor/e-contact-editor.h" #include "addressbook/gui/contact-list-editor/e-contact-list-editor.h" #include "addressbook/gui/widgets/eab-gui-util.h" diff --git a/modules/calendar/e-cal-shell-view-actions.c b/modules/calendar/e-cal-shell-view-actions.c index 408d02ffc3..4fa8b07eae 100644 --- a/modules/calendar/e-cal-shell-view-actions.c +++ b/modules/calendar/e-cal-shell-view-actions.c @@ -1065,8 +1065,10 @@ static void action_event_save_as_cb (GtkAction *action, ECalShellView *cal_shell_view) { + EShell *shell; EShellView *shell_view; EShellWindow *shell_window; + EShellBackend *shell_backend; ECalShellContent *cal_shell_content; GnomeCalendarViewType view_type; GnomeCalendar *calendar; @@ -1074,12 +1076,15 @@ action_event_save_as_cb (GtkAction *action, ECalendarViewEvent *event; ECal *client; icalcomponent *icalcomp; + EActivity *activity; GList *selected; - gchar *filename = NULL; + GFile *file; gchar *string = NULL; shell_view = E_SHELL_VIEW (cal_shell_view); shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); cal_shell_content = cal_shell_view->priv->cal_shell_content; calendar = e_cal_shell_content_get_calendar (cal_shell_content); @@ -1093,10 +1098,10 @@ action_event_save_as_cb (GtkAction *action, client = event->comp_data->client; icalcomp = event->comp_data->icalcomp; - filename = e_file_dialog_save ( - GTK_WINDOW (shell_window), _("Save As..."), NULL); - if (filename == NULL) - goto exit; + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), NULL, NULL); + if (file == NULL) + return; string = e_cal_get_component_as_string (client, icalcomp); if (string == NULL) { @@ -1104,11 +1109,20 @@ action_event_save_as_cb (GtkAction *action, goto exit; } - e_write_file_uri (filename, string); + /* XXX No callbacks means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); exit: - g_free (filename); - g_free (string); + g_object_unref (file); g_list_free (selected); } @@ -1372,7 +1386,7 @@ static GtkActionEntry calendar_entries[] = { { "event-save-as", GTK_STOCK_SAVE_AS, - NULL, + N_("Save as iCalendar..."), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_event_save_as_cb) }, diff --git a/modules/calendar/e-cal-shell-view-memopad.c b/modules/calendar/e-cal-shell-view-memopad.c index 7d4c1a501f..88cb53ff54 100644 --- a/modules/calendar/e-cal-shell-view-memopad.c +++ b/modules/calendar/e-cal-shell-view-memopad.c @@ -233,17 +233,22 @@ static void action_calendar_memopad_save_as_cb (GtkAction *action, ECalShellView *cal_shell_view) { + EShell *shell; EShellView *shell_view; EShellWindow *shell_window; + EShellBackend *shell_backend; ECalShellContent *cal_shell_content; EMemoTable *memo_table; ECalModelComponent *comp_data; + EActivity *activity; GSList *list; - gchar *filename; + GFile *file; gchar *string; shell_view = E_SHELL_VIEW (cal_shell_view); shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); cal_shell_content = cal_shell_view->priv->cal_shell_content; memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); @@ -253,9 +258,9 @@ action_calendar_memopad_save_as_cb (GtkAction *action, comp_data = list->data; g_slist_free (list); - filename = e_file_dialog_save ( - GTK_WINDOW (shell_window), _("Save as..."), NULL); - if (filename == NULL) + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), NULL, NULL); + if (file == NULL) return; /* XXX We only save the first selected memo. */ @@ -263,13 +268,23 @@ action_calendar_memopad_save_as_cb (GtkAction *action, comp_data->client, comp_data->icalcomp); if (string == NULL) { g_warning ("Could not convert memo to a string."); + g_object_unref (file); return; } - e_write_file_uri (filename, string); + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); - g_free (filename); - g_free (string); + g_object_unref (file); } static GtkActionEntry calendar_memopad_entries[] = { @@ -332,7 +347,7 @@ static GtkActionEntry calendar_memopad_entries[] = { { "calendar-memopad-save-as", GTK_STOCK_SAVE_AS, - NULL, + N_("Save as iCalendar..."), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_calendar_memopad_save_as_cb) } diff --git a/modules/calendar/e-cal-shell-view-private.h b/modules/calendar/e-cal-shell-view-private.h index 690031198e..2ee0cff6a4 100644 --- a/modules/calendar/e-cal-shell-view-private.h +++ b/modules/calendar/e-cal-shell-view-private.h @@ -33,8 +33,10 @@ #include "e-util/e-binding.h" #include "e-util/e-dialog-utils.h" +#include "e-util/e-file-utils.h" #include "e-util/e-error.h" #include "e-util/e-util.h" +#include "shell/e-shell-utils.h" #include "widgets/misc/e-popup-action.h" #include "calendar/common/authentication.h" diff --git a/modules/calendar/e-cal-shell-view-taskpad.c b/modules/calendar/e-cal-shell-view-taskpad.c index 3e83e04fcc..5f2fd3ad0f 100644 --- a/modules/calendar/e-cal-shell-view-taskpad.c +++ b/modules/calendar/e-cal-shell-view-taskpad.c @@ -301,17 +301,22 @@ static void action_calendar_taskpad_save_as_cb (GtkAction *action, ECalShellView *cal_shell_view) { + EShell *shell; EShellView *shell_view; EShellWindow *shell_window; + EShellBackend *shell_backend; ECalShellContent *cal_shell_content; ECalendarTable *task_table; ECalModelComponent *comp_data; + EActivity *activity; GSList *list; - gchar *filename; + GFile *file; gchar *string; shell_view = E_SHELL_VIEW (cal_shell_view); shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); cal_shell_content = cal_shell_view->priv->cal_shell_content; task_table = e_cal_shell_content_get_task_table (cal_shell_content); @@ -321,22 +326,32 @@ action_calendar_taskpad_save_as_cb (GtkAction *action, comp_data = list->data; g_slist_free (list); - filename = e_file_dialog_save ( - GTK_WINDOW (shell_window), _("Save as..."), NULL); - if (filename == NULL) + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), NULL, NULL); + if (file == NULL) return; string = e_cal_get_component_as_string ( comp_data->client, comp_data->icalcomp); if (string == NULL) { g_warning ("Could not convert task to a string"); + g_object_unref (file); return; } - e_write_file_uri (filename, string); + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); - g_free (filename); - g_free (string); + g_object_unref (file); } static GtkActionEntry calendar_taskpad_entries[] = { diff --git a/modules/calendar/e-memo-shell-view-actions.c b/modules/calendar/e-memo-shell-view-actions.c index ab8be53d04..31f12b3962 100644 --- a/modules/calendar/e-memo-shell-view-actions.c +++ b/modules/calendar/e-memo-shell-view-actions.c @@ -471,17 +471,22 @@ static void action_memo_save_as_cb (GtkAction *action, EMemoShellView *memo_shell_view) { + EShell *shell; EShellView *shell_view; EShellWindow *shell_window; + EShellBackend *shell_backend; EMemoShellContent *memo_shell_content; EMemoTable *memo_table; ECalModelComponent *comp_data; + EActivity *activity; GSList *list; - gchar *filename; + GFile *file; gchar *string; shell_view = E_SHELL_VIEW (memo_shell_view); shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); memo_shell_content = memo_shell_view->priv->memo_shell_content; memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); @@ -491,9 +496,9 @@ action_memo_save_as_cb (GtkAction *action, comp_data = list->data; g_slist_free (list); - filename = e_file_dialog_save ( - GTK_WINDOW (shell_window), _("Save as..."), NULL); - if (filename == NULL) + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), NULL, NULL); + if (file == NULL) return; /* XXX We only save the first selected memo. */ @@ -501,13 +506,23 @@ action_memo_save_as_cb (GtkAction *action, comp_data->client, comp_data->icalcomp); if (string == NULL) { g_warning ("Could not convert memo to a string"); + g_object_unref (file); return; } - e_write_file_uri (filename, string); + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); - g_free (filename); - g_free (string); + g_object_unref (file); } static void diff --git a/modules/calendar/e-memo-shell-view-private.h b/modules/calendar/e-memo-shell-view-private.h index 276e6e5d30..37556943f2 100644 --- a/modules/calendar/e-memo-shell-view-private.h +++ b/modules/calendar/e-memo-shell-view-private.h @@ -31,9 +31,11 @@ #include "e-util/e-binding.h" #include "e-util/e-dialog-utils.h" +#include "e-util/e-file-utils.h" #include "e-util/e-error.h" #include "e-util/e-util.h" #include "e-util/gconf-bridge.h" +#include "shell/e-shell-utils.h" #include "widgets/misc/e-popup-action.h" #include "calendar/gui/comp-util.h" diff --git a/modules/calendar/e-task-shell-view-actions.c b/modules/calendar/e-task-shell-view-actions.c index fb7b89b049..282d5a3e75 100644 --- a/modules/calendar/e-task-shell-view-actions.c +++ b/modules/calendar/e-task-shell-view-actions.c @@ -598,17 +598,22 @@ static void action_task_save_as_cb (GtkAction *action, ETaskShellView *task_shell_view) { + EShell *shell; EShellView *shell_view; EShellWindow *shell_window; + EShellBackend *shell_backend; ETaskShellContent *task_shell_content; ECalendarTable *task_table; ECalModelComponent *comp_data; + EActivity *activity; GSList *list; - gchar *filename; + GFile *file; gchar *string; shell_view = E_SHELL_VIEW (task_shell_view); shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); task_shell_content = task_shell_view->priv->task_shell_content; task_table = e_task_shell_content_get_task_table (task_shell_content); @@ -618,22 +623,33 @@ action_task_save_as_cb (GtkAction *action, comp_data = list->data; g_slist_free (list); - filename = e_file_dialog_save ( - GTK_WINDOW (shell_window), _("Save as..."), NULL); - if (filename == NULL) + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), NULL, NULL); + if (file == NULL) return; + /* XXX We only save the first selected task. */ string = e_cal_get_component_as_string ( comp_data->client, comp_data->icalcomp); if (string == NULL) { g_warning ("Could not convert task to a string"); + g_object_unref (file); return; } - e_write_file_uri (filename, string); + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); - g_free (filename); - g_free (string); + g_object_unref (file); } static void diff --git a/modules/calendar/e-task-shell-view-private.h b/modules/calendar/e-task-shell-view-private.h index b369b4c948..38b03ae359 100644 --- a/modules/calendar/e-task-shell-view-private.h +++ b/modules/calendar/e-task-shell-view-private.h @@ -32,9 +32,11 @@ #include "e-util/e-binding.h" #include "e-util/e-dialog-utils.h" +#include "e-util/e-file-utils.h" #include "e-util/e-error.h" #include "e-util/e-util.h" #include "e-util/gconf-bridge.h" +#include "shell/e-shell-utils.h" #include "widgets/misc/e-popup-action.h" #include "calendar/common/authentication.h" diff --git a/shell/Makefile.am b/shell/Makefile.am index d8eea3c5e3..608ecd314d 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -26,6 +26,7 @@ eshellinclude_HEADERS = \ e-shell-sidebar.h \ e-shell-switcher.h \ e-shell-taskbar.h \ + e-shell-utils.h \ e-shell-view.h \ e-shell-window.h \ e-shell-window-actions.h \ @@ -59,7 +60,7 @@ libeshell_la_CPPFLAGS = \ libeshell_la_SOURCES = \ $(NM_SUPPORT_FILES) \ - $(IDL_GENERATED) \ + $(eshellinclude_HEADERS) \ e-shell.c \ e-shell-backend.c \ e-shell-content.c \ @@ -67,11 +68,11 @@ libeshell_la_SOURCES = \ e-shell-sidebar.c \ e-shell-switcher.c \ e-shell-taskbar.c \ + e-shell-utils.c \ e-shell-view.c \ e-shell-window.c \ e-shell-window-private.c \ e-shell-window-private.h \ - $(eshellinclude_HEADERS) \ e-shell-migrate.c \ e-shell-migrate.h \ e-shell-window-actions.c \ diff --git a/shell/apps_evolution_shell.schemas.in b/shell/apps_evolution_shell.schemas.in index 157df8cb9e..b5aa3e6931 100644 --- a/shell/apps_evolution_shell.schemas.in +++ b/shell/apps_evolution_shell.schemas.in @@ -59,8 +59,8 @@ <!-- Initial GtkFileChooser Folder --> <schema> - <key>/schemas/apps/evolution/shell/current_folder</key> - <applyto>/apps/evolution/shell/current_folder</applyto> + <key>/schemas/apps/evolution/shell/file_chooser_folder</key> + <applyto>/apps/evolution/shell/file_chooser_folder</applyto> <owner>evolution</owner> <type>string</type> <locale name="C"> diff --git a/shell/e-shell-utils.c b/shell/e-shell-utils.c new file mode 100644 index 0000000000..9d3d6f5fb4 --- /dev/null +++ b/shell/e-shell-utils.c @@ -0,0 +1,100 @@ +/* + * e-shell-utils.c + * + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-shell-utils.h" + +#include <glib/gi18n-lib.h> + +/** + * e_shell_run_save_dialog: + * @shell: an #EShell + * @title: file chooser dialog title + * @customize_func: optional dialog customization function + * @customize_data: optional data to pass to @customize_func + * + * Runs a #GtkFileChooserDialog in save mode with the given title and + * returns the selected #GFile. It automatically remembers the selected + * folder. If @customize_func is provided, the function is called just + * prior to running the dialog (the file chooser is the first argument, + * @customize_data is the second). If the user cancels the dialog the + * function will return %NULL. + * + * Returns: the #GFile to save to, or %NULL + **/ +GFile * +e_shell_run_save_dialog (EShell *shell, + const gchar *title, + GtkCallback customize_func, + gpointer customize_data) +{ + EShellSettings *shell_settings; + GtkFileChooser *file_chooser; + GFile *chosen_file = NULL; + GtkWidget *dialog; + GtkWindow *parent; + const gchar *property_name; + gchar *uri; + + g_return_val_if_fail (E_IS_SHELL (shell), NULL); + + property_name = "file-chooser-folder"; + shell_settings = e_shell_get_shell_settings (shell); + + parent = e_shell_get_active_window (shell); + + dialog = gtk_file_chooser_dialog_new ( + title, parent, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL); + + file_chooser = GTK_FILE_CHOOSER (dialog); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_file_chooser_set_local_only (file_chooser, FALSE); + gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE); + + /* Restore the current folder from the previous file chooser. */ + uri = e_shell_settings_get_string (shell_settings, property_name); + if (uri != NULL) + gtk_file_chooser_set_current_folder_uri (file_chooser, uri); + g_free (uri); + + /* Allow further customizations before running the dialog. */ + if (customize_func != NULL) + customize_func (dialog, customize_data); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) + goto exit; + + chosen_file = gtk_file_chooser_get_file (file_chooser); + + /* Save the current folder for subsequent file choosers. */ + uri = gtk_file_chooser_get_current_folder_uri (file_chooser); + e_shell_settings_set_string (shell_settings, property_name, uri); + g_free (uri); + +exit: + gtk_widget_destroy (dialog); + + return chosen_file; +} diff --git a/shell/e-shell-utils.h b/shell/e-shell-utils.h new file mode 100644 index 0000000000..6fedca8a4a --- /dev/null +++ b/shell/e-shell-utils.h @@ -0,0 +1,42 @@ +/* + * e-shell-utils.h + * + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +/** + * SECTION: e-shell-utils + * @short_description: high-level utilities with shell integration + * @include: shell/e-shell-utils.h + **/ + +#ifndef E_SHELL_UTILS_H +#define E_SHELL_UTILS_H + +#include <shell/e-shell.h> + +G_BEGIN_DECLS + +GFile * e_shell_run_save_dialog (EShell *shell, + const gchar *title, + GtkCallback customize_func, + gpointer customize_data); + +G_END_DECLS + +#endif /* E_SHELL_UTILS_H */ diff --git a/shell/e-shell.c b/shell/e-shell.c index 61ce8d7c02..b0a1490148 100644 --- a/shell/e-shell.c +++ b/shell/e-shell.c @@ -903,16 +903,6 @@ shell_class_init (EShellClass *class) 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); - - /* Install some application-wide settings. */ - - e_shell_settings_install_property ( - g_param_spec_string ( - "file-chooser-folder", - NULL, - NULL, - NULL, - G_PARAM_READWRITE)); } static void @@ -950,6 +940,10 @@ shell_init (EShell *shell) * otherwise the GConf bindings will not get set up. */ e_shell_settings_install_property_for_key ( + "file-chooser-folder", + "/apps/evolution/shell/file_chooser_folder"); + + e_shell_settings_install_property_for_key ( "start-offline", "/apps/evolution/shell/start_offline"); diff --git a/widgets/misc/e-attachment-store.c b/widgets/misc/e-attachment-store.c index c99bdeab3f..4c87d65dd9 100644 --- a/widgets/misc/e-attachment-store.c +++ b/widgets/misc/e-attachment-store.c @@ -142,7 +142,7 @@ attachment_store_constructed (GObject *object) bridge = gconf_bridge_get (); - key = "/apps/evolution/shell/current_folder"; + key = "/apps/evolution/shell/file_chooser_folder"; gconf_bridge_bind_property (bridge, key, object, "current-folder"); } |