diff options
Diffstat (limited to '')
-rw-r--r-- | src/nautilus-operations-ui-manager.c | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/src/nautilus-operations-ui-manager.c b/src/nautilus-operations-ui-manager.c new file mode 100644 index 0000000..2371339 --- /dev/null +++ b/src/nautilus-operations-ui-manager.c @@ -0,0 +1,688 @@ +#include <glib/gi18n.h> + +#include "nautilus-operations-ui-manager.h" + +#include "nautilus-file.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-conflict-dialog.h" +#include "nautilus-mime-actions.h" +#include "nautilus-program-choosing.h" + +typedef struct +{ + GSourceFunc source_func; + GMutex mutex; + GCond cond; + gboolean completed; +} ContextInvokeData; + +G_LOCK_DEFINE_STATIC (main_context_sync); + +static gboolean +invoke_main_context_source_func_wrapper (gpointer user_data) +{ + ContextInvokeData *data = (ContextInvokeData *) user_data; + + g_mutex_lock (&data->mutex); + + while (data->source_func (user_data)) + { + } + + return G_SOURCE_REMOVE; +} + +static void +invoke_main_context_completed (gpointer user_data) +{ + ContextInvokeData *data = (ContextInvokeData *) user_data; + + data->completed = TRUE; + + g_cond_signal (&data->cond); + g_mutex_unlock (&data->mutex); +} + +/* This function is used to run UI on the main thread in order to ask the user + * for an action during an operation. Since the operation cannot progress until + * an action is provided by the user, the current thread needs to be blocked. + * For this we wait on a condition on the shared data. We proceed further + * unblocking the thread when invoke_main_context_completed() is called in the + * UI thread. The user_data pointer must reference a struct whose first member + * is of type ContextInvokeData. + */ +static void +invoke_main_context_sync (GMainContext *main_context, + GSourceFunc source_func, + gpointer user_data) +{ + ContextInvokeData *data = (ContextInvokeData *) user_data; + /* Allow only one thread at a time to invoke the main context so we + * don't get race conditions which could lead to multiple dialogs being + * displayed at the same time + */ + G_LOCK (main_context_sync); + + data->source_func = source_func; + + g_mutex_init (&data->mutex); + g_cond_init (&data->cond); + data->completed = FALSE; + + g_mutex_lock (&data->mutex); + + g_main_context_invoke (main_context, + invoke_main_context_source_func_wrapper, + user_data); + + while (!data->completed) + { + g_cond_wait (&data->cond, &data->mutex); + } + + g_mutex_unlock (&data->mutex); + + G_UNLOCK (main_context_sync); + + g_mutex_clear (&data->mutex); + g_cond_clear (&data->cond); +} + +typedef struct +{ + ContextInvokeData parent_type; + + GFile *source_name; + GFile *destination_name; + GFile *destination_directory_name; + + gchar *suggestion; + + GtkWindow *parent; + + gboolean should_start_inactive; + + FileConflictResponse *response; + + NautilusFile *source; + NautilusFile *destination; + NautilusFile *destination_directory_file; + + NautilusFileConflictDialog *dialog; + + NautilusFileListCallback on_file_list_ready; + NautilusFileListHandle *handle; + gulong source_handler_id; + gulong destination_handler_id; +} FileConflictDialogData; + +void +file_conflict_response_free (FileConflictResponse *response) +{ + g_free (response->new_name); + g_slice_free (FileConflictResponse, response); +} + +static void +set_copy_move_dialog_text (FileConflictDialogData *data) +{ + g_autofree gchar *primary_text = NULL; + g_autofree gchar *secondary_text = NULL; + const gchar *message_extra; + time_t source_mtime; + time_t destination_mtime; + g_autofree gchar *message = NULL; + g_autofree gchar *destination_name = NULL; + g_autofree gchar *destination_directory_name = NULL; + gboolean source_is_directory; + gboolean destination_is_directory; + + source_mtime = nautilus_file_get_mtime (data->source); + destination_mtime = nautilus_file_get_mtime (data->destination); + + destination_name = nautilus_file_get_display_name (data->destination); + destination_directory_name = nautilus_file_get_display_name (data->destination_directory_file); + + source_is_directory = nautilus_file_is_directory (data->source); + destination_is_directory = nautilus_file_is_directory (data->destination); + + if (destination_is_directory) + { + if (nautilus_file_is_symbolic_link (data->source) + && !nautilus_file_is_symbolic_link (data->destination)) + { + primary_text = g_strdup_printf (_("You are trying to replace the destination folder “%s” with a symbolic link."), + destination_name); + message = g_strdup_printf (_("This is not allowed in order to avoid the deletion of the destination folder’s contents.")); + message_extra = _("Please rename the symbolic link or press the skip button."); + } + else if (source_is_directory) + { + primary_text = g_strdup_printf (_("Merge folder “%s”?"), + destination_name); + + message_extra = _("Merging will ask for confirmation before replacing any files in " + "the folder that conflict with the files being copied."); + + if (source_mtime > destination_mtime) + { + message = g_strdup_printf (_("An older folder with the same name already exists in “%s”."), + destination_directory_name); + } + else if (source_mtime < destination_mtime) + { + message = g_strdup_printf (_("A newer folder with the same name already exists in “%s”."), + destination_directory_name); + } + else + { + message = g_strdup_printf (_("Another folder with the same name already exists in “%s”."), + destination_directory_name); + } + } + else + { + primary_text = g_strdup_printf (_("Replace folder “%s”?"), + destination_name); + message_extra = _("Replacing it will remove all files in the folder."); + message = g_strdup_printf (_("A folder with the same name already exists in “%s”."), + destination_directory_name); + } + } + else + { + primary_text = g_strdup_printf (_("Replace file “%s”?"), + destination_name); + + message_extra = _("Replacing it will overwrite its content."); + + if (source_mtime > destination_mtime) + { + message = g_strdup_printf (_("An older file with the same name already exists in “%s”."), + destination_directory_name); + } + else if (source_mtime < destination_mtime) + { + message = g_strdup_printf (_("A newer file with the same name already exists in “%s”."), + destination_directory_name); + } + else + { + message = g_strdup_printf (_("Another file with the same name already exists in “%s”."), + destination_directory_name); + } + } + + secondary_text = g_strdup_printf ("%s\n%s", message, message_extra); + + nautilus_file_conflict_dialog_set_text (data->dialog, + primary_text, + secondary_text); +} + +static void +set_images (FileConflictDialogData *data) +{ + GdkPaintable *source_paintable; + GdkPaintable *destination_paintable; + + destination_paintable = nautilus_file_get_icon_paintable (data->destination, + NAUTILUS_GRID_ICON_SIZE_SMALL, + 1, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS); + + source_paintable = nautilus_file_get_icon_paintable (data->source, + NAUTILUS_GRID_ICON_SIZE_SMALL, + 1, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS); + + nautilus_file_conflict_dialog_set_images (data->dialog, + destination_paintable, + source_paintable); + + g_object_unref (destination_paintable); + g_object_unref (source_paintable); +} + +static void +set_file_labels (FileConflictDialogData *data) +{ + GString *destination_label; + GString *source_label; + gboolean source_is_directory; + gboolean destination_is_directory; + gboolean should_show_type; + g_autofree char *destination_mime_type = NULL; + g_autofree char *destination_date = NULL; + g_autofree char *destination_size = NULL; + g_autofree char *destination_type = NULL; + g_autofree char *source_date = NULL; + g_autofree char *source_size = NULL; + g_autofree char *source_type = NULL; + + source_is_directory = nautilus_file_is_directory (data->source); + destination_is_directory = nautilus_file_is_directory (data->destination); + + destination_mime_type = nautilus_file_get_mime_type (data->destination); + should_show_type = !nautilus_file_is_mime_type (data->source, + destination_mime_type); + + destination_date = nautilus_file_get_string_attribute_with_default (data->destination, + "date_modified"); + destination_size = nautilus_file_get_string_attribute_with_default (data->destination, + "size"); + + if (should_show_type) + { + destination_type = nautilus_file_get_string_attribute_with_default (data->destination, + "type"); + } + + destination_label = g_string_new (NULL); + if (destination_is_directory) + { + g_string_append_printf (destination_label, "<b>%s</b>\n", _("Original folder")); + g_string_append_printf (destination_label, "%s %s\n", _("Contents:"), destination_size); + } + else + { + g_string_append_printf (destination_label, "<b>%s</b>\n", _("Original file")); + g_string_append_printf (destination_label, "%s %s\n", _("Size:"), destination_size); + } + + if (should_show_type) + { + g_string_append_printf (destination_label, "%s %s\n", _("Type:"), destination_type); + } + + g_string_append_printf (destination_label, "%s %s", _("Last modified:"), destination_date); + + source_date = nautilus_file_get_string_attribute_with_default (data->source, + "date_modified"); + source_size = nautilus_file_get_string_attribute_with_default (data->source, + "size"); + + if (should_show_type) + { + source_type = nautilus_file_get_string_attribute_with_default (data->source, + "type"); + } + + source_label = g_string_new (NULL); + if (source_is_directory) + { + g_string_append_printf (source_label, "<b>%s</b>\n", + destination_is_directory ? + _("Merge with") : _("Replace with")); + g_string_append_printf (source_label, "%s %s\n", _("Contents:"), source_size); + } + else + { + g_string_append_printf (source_label, "<b>%s</b>\n", _("Replace with")); + g_string_append_printf (source_label, "%s %s\n", _("Size:"), source_size); + } + + if (should_show_type) + { + g_string_append_printf (source_label, "%s %s\n", _("Type:"), source_type); + } + + g_string_append_printf (source_label, "%s %s", _("Last modified:"), source_date); + + nautilus_file_conflict_dialog_set_file_labels (data->dialog, + destination_label->str, + source_label->str); + + g_string_free (destination_label, TRUE); + g_string_free (source_label, TRUE); +} + +static void +set_conflict_and_suggested_names (FileConflictDialogData *data) +{ + g_autofree gchar *conflict_name = NULL; + + conflict_name = nautilus_file_get_edit_name (data->destination); + + nautilus_file_conflict_dialog_set_conflict_name (data->dialog, + conflict_name); + + nautilus_file_conflict_dialog_set_suggested_name (data->dialog, + data->suggestion); +} + +static void +set_replace_button_label (FileConflictDialogData *data) +{ + gboolean source_is_directory, destination_is_directory; + + source_is_directory = nautilus_file_is_directory (data->source); + destination_is_directory = nautilus_file_is_directory (data->destination); + + if (destination_is_directory) + { + if (nautilus_file_is_symbolic_link (data->source) + && !nautilus_file_is_symbolic_link (data->destination)) + { + nautilus_file_conflict_dialog_disable_replace (data->dialog); + nautilus_file_conflict_dialog_disable_apply_to_all (data->dialog); + } + else if (source_is_directory) + { + nautilus_file_conflict_dialog_set_replace_button_label (data->dialog, + _("Merge")); + } + } +} + +static void +file_icons_changed (NautilusFile *file, + FileConflictDialogData *data) +{ + set_images (data); +} + +static void +copy_move_conflict_on_file_list_ready (GList *files, + gpointer user_data) +{ + FileConflictDialogData *data = user_data; + g_autofree gchar *title = NULL; + + data->handle = NULL; + + if (nautilus_file_is_directory (data->source)) + { + title = g_strdup (nautilus_file_is_directory (data->destination) ? + _("Merge Folder") : + _("File and Folder conflict")); + } + else + { + title = g_strdup (nautilus_file_is_directory (data->destination) ? + _("File and Folder conflict") : + _("File conflict")); + } + + gtk_window_set_title (GTK_WINDOW (data->dialog), title); + + set_copy_move_dialog_text (data); + + set_images (data); + + set_file_labels (data); + + set_conflict_and_suggested_names (data); + + set_replace_button_label (data); + + nautilus_file_monitor_add (data->source, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); + nautilus_file_monitor_add (data->destination, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); + + data->source_handler_id = g_signal_connect (data->source, "changed", + G_CALLBACK (file_icons_changed), data); + data->destination_handler_id = g_signal_connect (data->destination, "changed", + G_CALLBACK (file_icons_changed), data); +} + +static void +on_conflict_dialog_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + FileConflictDialogData *data = user_data; + + if (data->handle != NULL) + { + nautilus_file_list_cancel_call_when_ready (data->handle); + } + + if (data->source_handler_id) + { + g_signal_handler_disconnect (data->source, data->source_handler_id); + nautilus_file_monitor_remove (data->source, data); + } + + if (data->destination_handler_id) + { + g_signal_handler_disconnect (data->destination, data->destination_handler_id); + nautilus_file_monitor_remove (data->destination, data); + } + + if (response_id == CONFLICT_RESPONSE_RENAME) + { + data->response->new_name = + nautilus_file_conflict_dialog_get_new_name (data->dialog); + } + else if (response_id != GTK_RESPONSE_CANCEL && + response_id != GTK_RESPONSE_NONE) + { + data->response->apply_to_all = + nautilus_file_conflict_dialog_get_apply_to_all (data->dialog); + } + + data->response->id = response_id; + + gtk_window_destroy (GTK_WINDOW (data->dialog)); + + nautilus_file_unref (data->source); + nautilus_file_unref (data->destination); + nautilus_file_unref (data->destination_directory_file); + + invoke_main_context_completed (user_data); +} + +static gboolean +run_file_conflict_dialog (gpointer user_data) +{ + FileConflictDialogData *data = user_data; + GList *files = NULL; + + data->source = nautilus_file_get (data->source_name); + data->destination = nautilus_file_get (data->destination_name); + data->destination_directory_file = nautilus_file_get (data->destination_directory_name); + + data->dialog = nautilus_file_conflict_dialog_new (data->parent); + + if (data->should_start_inactive) + { + nautilus_file_conflict_dialog_delay_buttons_activation (data->dialog); + } + + files = g_list_prepend (files, data->source); + files = g_list_prepend (files, data->destination); + files = g_list_prepend (files, data->destination_directory_file); + + nautilus_file_list_call_when_ready (files, + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT, + &data->handle, + data->on_file_list_ready, + data); + + g_signal_connect (data->dialog, "response", G_CALLBACK (on_conflict_dialog_response), data); + gtk_widget_show (GTK_WIDGET (data->dialog)); + + g_list_free (files); + + return G_SOURCE_REMOVE; +} + +FileConflictResponse * +copy_move_conflict_ask_user_action (GtkWindow *parent_window, + gboolean should_start_inactive, + GFile *source_name, + GFile *destination_name, + GFile *destination_directory_name, + gchar *suggestion) +{ + FileConflictDialogData *data; + FileConflictResponse *response; + + data = g_slice_new0 (FileConflictDialogData); + data->parent = parent_window; + data->should_start_inactive = should_start_inactive; + data->source_name = source_name; + data->destination_name = destination_name; + data->destination_directory_name = destination_directory_name; + data->suggestion = suggestion; + + data->response = g_slice_new0 (FileConflictResponse); + data->response->new_name = NULL; + + data->on_file_list_ready = copy_move_conflict_on_file_list_ready; + + invoke_main_context_sync (NULL, + run_file_conflict_dialog, + data); + + response = g_steal_pointer (&data->response); + g_slice_free (FileConflictDialogData, data); + + return response; +} + +typedef struct +{ + ContextInvokeData parent_type; + GtkWindow *parent_window; + NautilusFile *file; +} HandleUnsupportedFileData; + +static void +on_app_chooser_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + HandleUnsupportedFileData *data = user_data; + g_autoptr (GAppInfo) application = NULL; + + if (response_id == GTK_RESPONSE_OK) + { + application = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + + if (application != NULL) + { + GList files = {data->file, NULL, NULL}; + nautilus_launch_application (application, &files, data->parent_window); + } + + invoke_main_context_completed (user_data); +} + +static gboolean +open_file_in_application (gpointer user_data) +{ + HandleUnsupportedFileData *data; + g_autofree gchar *mime_type = NULL; + GtkWidget *dialog; + const char *heading; + + data = user_data; + mime_type = nautilus_file_get_mime_type (data->file); + dialog = gtk_app_chooser_dialog_new_for_content_type (data->parent_window, + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_USE_HEADER_BAR, + mime_type); + heading = _("Password-protected archives are not yet supported. " + "This list contains applications that can open the archive."); + + gtk_app_chooser_dialog_set_heading (GTK_APP_CHOOSER_DIALOG (dialog), heading); + + g_signal_connect (dialog, "response", G_CALLBACK (on_app_chooser_response), data); + gtk_widget_show (dialog); + + return G_SOURCE_REMOVE; +} + +/* This is used to open compressed files that are not supported by gnome-autoar + * in another application + */ +void +handle_unsupported_compressed_file (GtkWindow *parent_window, + GFile *compressed_file) +{ + HandleUnsupportedFileData *data; + + data = g_slice_new0 (HandleUnsupportedFileData); + data->parent_window = parent_window; + data->file = nautilus_file_get (compressed_file); + + invoke_main_context_sync (NULL, open_file_in_application, data); + + nautilus_file_unref (data->file); + g_slice_free (HandleUnsupportedFileData, data); + + return; +} + +typedef struct +{ + ContextInvokeData parent_type; + GtkWindow *parent_window; + const gchar *basename; + GtkEntry *passphrase_entry; + gchar *passphrase; +} PassphraseRequestData; + +static void +on_request_passphrase_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + PassphraseRequestData *data = user_data; + + if (response_id != GTK_RESPONSE_CANCEL && + response_id != GTK_RESPONSE_DELETE_EVENT) + { + data->passphrase = g_strdup (gtk_editable_get_text (GTK_EDITABLE (data->passphrase_entry))); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + invoke_main_context_completed (data); +} + +static gboolean +run_passphrase_dialog (gpointer user_data) +{ + PassphraseRequestData *data = user_data; + g_autofree gchar *label_str = NULL; + g_autoptr (GtkBuilder) builder = NULL; + GObject *dialog; + GObject *label; + + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-operations-ui-manager-request-passphrase.ui"); + dialog = gtk_builder_get_object (builder, "request_passphrase_dialog"); + label = gtk_builder_get_object (builder, "label"); + data->passphrase_entry = GTK_ENTRY (gtk_builder_get_object (builder, "entry")); + + label_str = g_strdup_printf (_("“%s” is password-protected."), data->basename); + gtk_label_set_text (GTK_LABEL (label), label_str); + + g_signal_connect (dialog, "response", G_CALLBACK (on_request_passphrase_cb), data); + gtk_window_set_transient_for (GTK_WINDOW (dialog), data->parent_window); + gtk_widget_show (GTK_WIDGET (dialog)); + + return G_SOURCE_REMOVE; +} + +gchar * +extract_ask_passphrase (GtkWindow *parent_window, + const gchar *archive_basename) +{ + PassphraseRequestData *data; + gchar *passphrase; + + data = g_new0 (PassphraseRequestData, 1); + data->parent_window = parent_window; + data->basename = archive_basename; + invoke_main_context_sync (NULL, run_passphrase_dialog, data); + + passphrase = g_steal_pointer (&data->passphrase); + g_free (data); + + return passphrase; +} |