/* * gedit-commands-file.c * This file is part of gedit * * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi * Copyright (C) 2002-2005 Paolo Maggi * Copyright (C) 2014 Sébastien Wilmet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "config.h" #include "gedit-commands.h" #include "gedit-commands-private.h" #include #include #include "gedit-app.h" #include "gedit-debug.h" #include "gedit-document.h" #include "gedit-document-private.h" #include "gedit-tab.h" #include "gedit-tab-private.h" #include "gedit-window.h" #include "gedit-notebook.h" #include "gedit-statusbar.h" #include "gedit-utils.h" #include "gedit-file-chooser-dialog.h" #include "gedit-file-chooser-open.h" #include "gedit-close-confirmation-dialog.h" /* useful macro */ #define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1)) #define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT(i) == 2) ? TRUE : FALSE)) #define GEDIT_IS_CLOSING_ALL "gedit-is-closing-all" #define GEDIT_NOTEBOOK_TO_CLOSE "gedit-notebook-to-close" #define GEDIT_IS_QUITTING "gedit-is-quitting" #define GEDIT_IS_QUITTING_ALL "gedit-is-quitting-all" void _gedit_cmd_file_new (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); gedit_debug (DEBUG_COMMANDS); gedit_window_create_tab (window, TRUE); } static GeditTab * get_tab_from_file (GList *docs, GFile *file) { GList *l; for (l = docs; l != NULL; l = l->next) { GeditDocument *doc; GtkSourceFile *source_file; GFile *location; doc = l->data; source_file = gedit_document_get_file (doc); location = gtk_source_file_get_location (source_file); if (location != NULL && g_file_equal (location, file)) { return gedit_tab_get_from_document (doc); } } return NULL; } static gboolean is_duplicated_file (GSList *files, GFile *file) { GSList *l; for (l = files; l != NULL; l = l->next) { if (g_file_equal (l->data, file)) { return TRUE; } } return FALSE; } /* File loading */ static GSList * load_file_list (GeditWindow *window, const GSList *files, const GtkSourceEncoding *encoding, gint line_pos, gint column_pos, gboolean create) { GList *win_docs; GSList *files_to_load = NULL; GSList *loaded_files = NULL; GeditTab *tab; gboolean jump_to = TRUE; /* Whether to jump to the new tab */ const GSList *l; gint num_loaded_files = 0; GeditStatusbar *statusbar; gedit_debug (DEBUG_COMMANDS); win_docs = gedit_window_get_documents (window); /* Remove the files corresponding to documents already opened in * "window" and remove duplicates from the "files" list. */ for (l = files; l != NULL; l = l->next) { GFile *file = l->data; if (is_duplicated_file (files_to_load, file)) { continue; } tab = get_tab_from_file (win_docs, file); if (tab == NULL) { files_to_load = g_slist_prepend (files_to_load, file); } else { if (l == files) { TeplView *view; gedit_window_set_active_tab (window, tab); jump_to = FALSE; view = TEPL_VIEW (gedit_tab_get_view (tab)); if (line_pos > 0) { if (column_pos > 0) { tepl_view_goto_line_offset (view, line_pos - 1, column_pos - 1); } else { tepl_view_goto_line (view, line_pos - 1); } } } ++num_loaded_files; loaded_files = g_slist_prepend (loaded_files, gedit_tab_get_document (tab)); } } g_list_free (win_docs); if (files_to_load == NULL) { return g_slist_reverse (loaded_files); } files_to_load = g_slist_reverse (files_to_load); l = files_to_load; tab = gedit_window_get_active_tab (window); if (tab != NULL) { GeditDocument *doc; doc = gedit_tab_get_document (tab); if (tepl_buffer_is_untouched (TEPL_BUFFER (doc)) && gedit_tab_get_state (tab) == GEDIT_TAB_STATE_NORMAL) { _gedit_tab_load (tab, l->data, encoding, line_pos, column_pos, create); /* make sure the view has focus */ gtk_widget_grab_focus (GTK_WIDGET (gedit_tab_get_view (tab))); l = g_slist_next (l); jump_to = FALSE; ++num_loaded_files; loaded_files = g_slist_prepend (loaded_files, gedit_tab_get_document (tab)); } } while (l != NULL) { g_return_val_if_fail (l->data != NULL, NULL); tab = gedit_window_create_tab_from_location (window, l->data, encoding, line_pos, column_pos, create, jump_to); if (tab != NULL) { jump_to = FALSE; ++num_loaded_files; loaded_files = g_slist_prepend (loaded_files, gedit_tab_get_document (tab)); } l = g_slist_next (l); } loaded_files = g_slist_reverse (loaded_files); statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window)); if (num_loaded_files == 1) { GeditDocument *doc; gchar *full_name; g_return_val_if_fail (tab != NULL, loaded_files); doc = gedit_tab_get_document (tab); full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); _gedit_statusbar_flash_generic_message (statusbar, _("Loading file “%s”\342\200\246"), full_name); g_free (full_name); } else { _gedit_statusbar_flash_generic_message (statusbar, ngettext ("Loading %d file\342\200\246", "Loading %d files\342\200\246", num_loaded_files), num_loaded_files); } g_slist_free (files_to_load); return loaded_files; } /** * gedit_commands_load_location: * @window: a #GeditWindow * @location: a #GFile to load * @encoding: (allow-none): the #GtkSourceEncoding of @location * @line_pos: the line position to place the cursor * @column_pos: the line column to place the cursor * * Loads @location. Ignores non-existing locations. */ void gedit_commands_load_location (GeditWindow *window, GFile *location, const GtkSourceEncoding *encoding, gint line_pos, gint column_pos) { GSList *locations = NULL; gchar *uri; GSList *ret; g_return_if_fail (GEDIT_IS_WINDOW (window)); g_return_if_fail (G_IS_FILE (location)); g_return_if_fail (gedit_utils_is_valid_location (location)); uri = g_file_get_uri (location); gedit_debug_message (DEBUG_COMMANDS, "Loading URI '%s'", uri); g_free (uri); locations = g_slist_prepend (locations, location); ret = load_file_list (window, locations, encoding, line_pos, column_pos, FALSE); g_slist_free (ret); g_slist_free (locations); } /** * gedit_commands_load_locations: * @window: a #GeditWindow * @locations: (element-type Gio.File): the locations to load * @encoding: (allow-none): the #GtkSourceEncoding * @line_pos: the line position to place the cursor * @column_pos: the line column to place the cursor * * Loads @locations. Ignore non-existing locations. * * Returns: (element-type Gedit.Document) (transfer container): the locations * that were loaded. */ GSList * gedit_commands_load_locations (GeditWindow *window, const GSList *locations, const GtkSourceEncoding *encoding, gint line_pos, gint column_pos) { g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); g_return_val_if_fail (locations != NULL && locations->data != NULL, NULL); gedit_debug (DEBUG_COMMANDS); return load_file_list (window, locations, encoding, line_pos, column_pos, FALSE); } /* * From the command line we can specify a line position for the * first doc. Beside specifying a non-existing file creates a * titled document. */ GSList * _gedit_cmd_load_files_from_prompt (GeditWindow *window, GSList *files, const GtkSourceEncoding *encoding, gint line_pos, gint column_pos) { gedit_debug (DEBUG_COMMANDS); return load_file_list (window, files, encoding, line_pos, column_pos, TRUE); } static void file_chooser_open_done_cb (GeditFileChooserOpen *file_chooser, gboolean accept, GeditWindow *window) { GSList *files; const GtkSourceEncoding *encoding; gchar *folder_uri; GSList *loaded_documents; gedit_debug (DEBUG_COMMANDS); if (!accept) { g_object_unref (file_chooser); return; } files = _gedit_file_chooser_open_get_files (file_chooser); encoding = _gedit_file_chooser_get_encoding (GEDIT_FILE_CHOOSER (file_chooser)); folder_uri = _gedit_file_chooser_get_current_folder_uri (GEDIT_FILE_CHOOSER (file_chooser)); g_object_unref (file_chooser); if (window == NULL) { window = gedit_app_create_window (GEDIT_APP (g_application_get_default ()), NULL); gtk_widget_show (GTK_WIDGET (window)); gtk_window_present (GTK_WINDOW (window)); } /* Remember the folder we navigated to. */ _gedit_window_set_file_chooser_folder_uri (window, GTK_FILE_CHOOSER_ACTION_OPEN, folder_uri); g_free (folder_uri); loaded_documents = gedit_commands_load_locations (window, files, encoding, 0, 0); g_slist_free (loaded_documents); g_slist_free_full (files, g_object_unref); } void _gedit_cmd_file_open (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = NULL; GeditFileChooserOpen *file_chooser; gedit_debug (DEBUG_COMMANDS); if (user_data != NULL) { window = GEDIT_WINDOW (user_data); } file_chooser = _gedit_file_chooser_open_new (); if (window != NULL) { const gchar *folder_uri; _gedit_file_chooser_set_transient_for (GEDIT_FILE_CHOOSER (file_chooser), GTK_WINDOW (window)); folder_uri = _gedit_window_get_file_chooser_folder_uri (window, GTK_FILE_CHOOSER_ACTION_OPEN); if (folder_uri != NULL) { _gedit_file_chooser_set_current_folder_uri (GEDIT_FILE_CHOOSER (file_chooser), folder_uri); } } g_signal_connect (file_chooser, "done", G_CALLBACK (file_chooser_open_done_cb), window); _gedit_file_chooser_show (GEDIT_FILE_CHOOSER (file_chooser)); } void _gedit_cmd_file_reopen_closed_tab (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); GFile *file; file = _gedit_window_pop_last_closed_doc (window); if (file != NULL) { gedit_commands_load_location (window, file, NULL, 0, 0); } } /* File saving */ /* FIXME: modify this dialog to be similar to the one provided by gtk+ for * already existing files - Paolo (Oct. 11, 2005) */ static gboolean replace_read_only_file (GtkWindow *parent, GFile *file) { GtkWidget *dialog; gint ret; gchar *parse_name; gchar *name_for_display; gedit_debug (DEBUG_COMMANDS); parse_name = g_file_get_parse_name (file); /* Truncate the name so it doesn't get insanely wide. Note that even * though the dialog uses wrapped text, if the name doesn't contain * white space then the text-wrapping code is too stupid to wrap it. */ name_for_display = tepl_utils_str_middle_truncate (parse_name, 50); g_free (parse_name); dialog = gtk_message_dialog_new (parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("The file “%s” is read-only."), name_for_display); g_free (name_for_display); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Do you want to try to replace it " "with the one you are saving?")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Replace"), GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); ret = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); return ret == GTK_RESPONSE_YES; } static gboolean change_compression (GtkWindow *parent, GFile *file, gboolean compressed) { GtkWidget *dialog; gint ret; gchar *parse_name; gchar *name_for_display; const gchar *primary_message; const gchar *button_label; gedit_debug (DEBUG_COMMANDS); parse_name = g_file_get_parse_name (file); /* Truncate the name so it doesn't get insanely wide. Note that even * though the dialog uses wrapped text, if the name doesn't contain * white space then the text-wrapping code is too stupid to wrap it. */ name_for_display = tepl_utils_str_middle_truncate (parse_name, 50); g_free (parse_name); if (compressed) { primary_message = _("Save the file using compression?"); } else { primary_message = _("Save the file as plain text?"); } dialog = gtk_message_dialog_new (parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", primary_message); if (compressed) { gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("The file “%s” was previously saved as plain " "text and will now be saved using compression."), name_for_display); button_label = _("_Save Using Compression"); } else { gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("The file “%s” was previously saved " "using compression and will now be saved as plain text."), name_for_display); button_label = _("_Save As Plain Text"); } g_free (name_for_display); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, button_label, GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); ret = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); return ret == GTK_RESPONSE_YES; } static GtkSourceCompressionType get_compression_type_from_file (GFile *file) { gchar *name; gchar *content_type; GtkSourceCompressionType type; name = g_file_get_basename (file); content_type = g_content_type_guess (name, NULL, 0, NULL); type = gedit_utils_get_compression_type_from_content_type (content_type); g_free (name); g_free (content_type); return type; } static void tab_save_as_ready_cb (GeditTab *tab, GAsyncResult *result, GTask *task) { gboolean success = _gedit_tab_save_finish (tab, result); g_task_return_boolean (task, success); g_object_unref (task); } static void save_dialog_response_cb (GeditFileChooserDialog *dialog, gint response_id, GTask *task) { GeditTab *tab; GeditWindow *window; GeditDocument *doc; GtkSourceFile *file; GFile *location; gchar *parse_name; GtkSourceNewlineType newline_type; GtkSourceCompressionType compression_type; GtkSourceCompressionType current_compression_type; const GtkSourceEncoding *encoding; GeditStatusbar *statusbar; gedit_debug (DEBUG_COMMANDS); tab = g_task_get_source_object (task); window = g_task_get_task_data (task); if (response_id != GTK_RESPONSE_ACCEPT) { gedit_file_chooser_dialog_destroy (dialog); g_task_return_boolean (task, FALSE); g_object_unref (task); return; } doc = gedit_tab_get_document (tab); file = gedit_document_get_file (doc); location = gedit_file_chooser_dialog_get_file (dialog); g_return_if_fail (location != NULL); compression_type = get_compression_type_from_file (location); current_compression_type = gtk_source_file_get_compression_type (file); if ((compression_type == GTK_SOURCE_COMPRESSION_TYPE_NONE) != (current_compression_type == GTK_SOURCE_COMPRESSION_TYPE_NONE)) { GtkWindow *dialog_window = gedit_file_chooser_dialog_get_window (dialog); if (!change_compression (dialog_window, location, compression_type != GTK_SOURCE_COMPRESSION_TYPE_NONE)) { gedit_file_chooser_dialog_destroy (dialog); g_object_unref (location); g_task_return_boolean (task, FALSE); g_object_unref (task); return; } } encoding = gedit_file_chooser_dialog_get_encoding (dialog); newline_type = gedit_file_chooser_dialog_get_newline_type (dialog); gedit_file_chooser_dialog_destroy (dialog); parse_name = g_file_get_parse_name (location); statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window)); _gedit_statusbar_flash_generic_message (statusbar, _("Saving file “%s”\342\200\246"), parse_name); g_free (parse_name); /* Let's remember the dir we navigated to, even if the saving fails... */ { GFile *folder; folder = g_file_get_parent (location); if (folder != NULL) { gchar *folder_uri; folder_uri = g_file_get_uri (folder); _gedit_window_set_file_chooser_folder_uri (window, GTK_FILE_CHOOSER_ACTION_SAVE, folder_uri); g_object_unref (folder); g_free (folder_uri); } } _gedit_tab_save_as_async (tab, location, encoding, newline_type, compression_type, g_task_get_cancellable (task), (GAsyncReadyCallback) tab_save_as_ready_cb, task); g_object_unref (location); } static GtkFileChooserConfirmation confirm_overwrite_callback (GeditFileChooserDialog *dialog, gpointer data) { GtkFileChooserConfirmation res; GFile *file; GFileInfo *info; gedit_debug (DEBUG_COMMANDS); /* fall back to the default confirmation dialog */ res = GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM; file = gedit_file_chooser_dialog_get_file (dialog); info = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info != NULL) { if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) && !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { GtkWindow *win; win = gedit_file_chooser_dialog_get_window (dialog); if (replace_read_only_file (win, file)) { res = GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME; } else { res = GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN; } } g_object_unref (info); } g_object_unref (file); return res; } /* Call save_as_tab_finish() in @callback. */ static void save_as_tab_async (GeditTab *tab, GeditWindow *window, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; GeditFileChooserDialog *save_dialog; GtkWindowGroup *window_group; GtkWindow *dialog_window; GeditDocument *doc; GtkSourceFile *file; GFile *location; const GtkSourceEncoding *encoding; GtkSourceNewlineType newline_type; g_return_if_fail (GEDIT_IS_TAB (tab)); g_return_if_fail (GEDIT_IS_WINDOW (window)); gedit_debug (DEBUG_COMMANDS); task = g_task_new (tab, cancellable, callback, user_data); g_task_set_task_data (task, g_object_ref (window), g_object_unref); /* Translators: "Save As" is the title of the file chooser window. */ save_dialog = gedit_file_chooser_dialog_create (C_("window title", "Save As"), GTK_WINDOW (window), _("_Save"), _("_Cancel")); gedit_file_chooser_dialog_set_do_overwrite_confirmation (save_dialog, TRUE); g_signal_connect (save_dialog, "confirm-overwrite", G_CALLBACK (confirm_overwrite_callback), NULL); window_group = gedit_window_get_group (window); dialog_window = gedit_file_chooser_dialog_get_window (save_dialog); if (dialog_window != NULL) { gtk_window_group_add_window (window_group, dialog_window); } /* Save As dialog is modal to its main window */ gedit_file_chooser_dialog_set_modal (save_dialog, TRUE); /* Set the suggested file name */ doc = gedit_tab_get_document (tab); file = gedit_document_get_file (doc); location = gtk_source_file_get_location (file); if (location != NULL) { gedit_file_chooser_dialog_set_file (save_dialog, location); } else { const gchar *default_folder_uri; GFile *default_folder; gchar *docname; default_folder_uri = _gedit_window_get_file_chooser_folder_uri (window, GTK_FILE_CHOOSER_ACTION_SAVE); if (default_folder_uri != NULL) { default_folder = g_file_new_for_uri (default_folder_uri); } else { /* It's logical to take the home dir by default, and it fixes * a problem on MS Windows (hang in C:\windows\system32). * * FIXME: it would be better to use GtkFileChooserNative * to permanently fix the hang problem on MS Windows. */ default_folder = g_file_new_for_path (g_get_home_dir ()); } gedit_file_chooser_dialog_set_current_folder (save_dialog, default_folder); g_object_unref (default_folder); docname = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); gedit_file_chooser_dialog_set_current_name (save_dialog, docname); g_free (docname); } /* Set suggested encoding and newline type. */ encoding = gtk_source_file_get_encoding (file); if (encoding == NULL) { encoding = gtk_source_encoding_get_utf8 (); } newline_type = gtk_source_file_get_newline_type (file); gedit_file_chooser_dialog_set_encoding (GEDIT_FILE_CHOOSER_DIALOG (save_dialog), encoding); gedit_file_chooser_dialog_set_newline_type (GEDIT_FILE_CHOOSER_DIALOG (save_dialog), newline_type); g_signal_connect (save_dialog, "response", G_CALLBACK (save_dialog_response_cb), task); gedit_file_chooser_dialog_show (save_dialog); } static gboolean save_as_tab_finish (GeditTab *tab, GAsyncResult *result) { g_return_val_if_fail (g_task_is_valid (result, tab), FALSE); return g_task_propagate_boolean (G_TASK (result), NULL); } static void save_as_tab_ready_cb (GeditTab *tab, GAsyncResult *result, GTask *task) { gboolean success = save_as_tab_finish (tab, result); g_task_return_boolean (task, success); g_object_unref (task); } static void tab_save_ready_cb (GeditTab *tab, GAsyncResult *result, GTask *task) { gboolean success = _gedit_tab_save_finish (tab, result); g_task_return_boolean (task, success); g_object_unref (task); } /** * gedit_commands_save_document_async: * @document: the #GeditDocument to save. * @window: a #GeditWindow. * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore. * @callback: (scope async): a #GAsyncReadyCallback to call when the operation * is finished. * @user_data: (closure): the data to pass to the @callback function. * * Asynchronously save the @document. @document must belong to @window. The * source object of the async task is @document (which will be the first * parameter of the #GAsyncReadyCallback). * * When the operation is finished, @callback will be called. You can then call * gedit_commands_save_document_finish() to get the result of the operation. * * Since: 3.14 */ void gedit_commands_save_document_async (GeditDocument *document, GeditWindow *window, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; GeditTab *tab; GtkSourceFile *file; gchar *full_name; GeditStatusbar *statusbar; gedit_debug (DEBUG_COMMANDS); g_return_if_fail (GEDIT_IS_DOCUMENT (document)); g_return_if_fail (GEDIT_IS_WINDOW (window)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); task = g_task_new (document, cancellable, callback, user_data); tab = gedit_tab_get_from_document (document); file = gedit_document_get_file (document); if (_gedit_document_is_untitled (document) || gtk_source_file_is_readonly (file)) { gedit_debug_message (DEBUG_COMMANDS, "Untitled or Readonly"); save_as_tab_async (tab, window, cancellable, (GAsyncReadyCallback) save_as_tab_ready_cb, task); return; } full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (document))); statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window)); _gedit_statusbar_flash_generic_message (statusbar, _("Saving file “%s”\342\200\246"), full_name); g_free (full_name); _gedit_tab_save_async (tab, cancellable, (GAsyncReadyCallback) tab_save_ready_cb, task); } /** * gedit_commands_save_document_finish: * @document: a #GeditDocument. * @result: a #GAsyncResult. * * Finishes an asynchronous document saving operation started with * gedit_commands_save_document_async(). * * Note that there is no error parameter because the errors are already handled * by gedit. * * Returns: %TRUE if the document has been correctly saved, %FALSE otherwise. * Since: 3.14 */ gboolean gedit_commands_save_document_finish (GeditDocument *document, GAsyncResult *result) { g_return_val_if_fail (g_task_is_valid (result, document), FALSE); return g_task_propagate_boolean (G_TASK (result), NULL); } static void save_tab_ready_cb (GeditDocument *doc, GAsyncResult *result, gpointer user_data) { gedit_commands_save_document_finish (doc, result); } /* Save tab asynchronously, but without results. */ static void save_tab (GeditTab *tab, GeditWindow *window) { GeditDocument *doc = gedit_tab_get_document (tab); gedit_commands_save_document_async (doc, window, NULL, (GAsyncReadyCallback) save_tab_ready_cb, NULL); } void _gedit_cmd_file_save (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); GeditTab *tab; gedit_debug (DEBUG_COMMANDS); tab = gedit_window_get_active_tab (window); if (tab != NULL) { save_tab (tab, window); } } static void _gedit_cmd_file_save_as_cb (GeditTab *tab, GAsyncResult *result, gpointer user_data) { save_as_tab_finish (tab, result); } void _gedit_cmd_file_save_as (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); GeditTab *tab; gedit_debug (DEBUG_COMMANDS); tab = gedit_window_get_active_tab (window); if (tab != NULL) { save_as_tab_async (tab, window, NULL, (GAsyncReadyCallback) _gedit_cmd_file_save_as_cb, NULL); } } static void quit_if_needed (GeditWindow *window) { gboolean is_quitting; gboolean is_quitting_all; is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window), GEDIT_IS_QUITTING)); is_quitting_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window), GEDIT_IS_QUITTING_ALL)); if (is_quitting) { gtk_widget_destroy (GTK_WIDGET (window)); } if (is_quitting_all) { GtkApplication *app; app = GTK_APPLICATION (g_application_get_default ()); if (gtk_application_get_windows (app) == NULL) { g_application_quit (G_APPLICATION (app)); } } } static gboolean really_close_tab (GeditTab *tab) { GtkWidget *toplevel; GeditWindow *window; gedit_debug (DEBUG_COMMANDS); g_return_val_if_fail (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_CLOSING, FALSE); toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tab)); g_return_val_if_fail (GEDIT_IS_WINDOW (toplevel), FALSE); window = GEDIT_WINDOW (toplevel); gedit_window_close_tab (window, tab); if (gedit_window_get_active_tab (window) == NULL) { quit_if_needed (window); } return FALSE; } static void close_tab (GeditTab *tab) { GeditDocument *doc; doc = gedit_tab_get_document (tab); g_return_if_fail (doc != NULL); /* If the user has modified again the document, do not close the tab. */ if (_gedit_document_needs_saving (doc)) { return; } /* Close the document only if it has been succesfully saved. * Tab state is set to CLOSING (it is a state without exiting * transitions) and the tab is closed in an idle handler. */ _gedit_tab_mark_for_closing (tab); g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc) really_close_tab, tab, NULL); } typedef struct _SaveAsData SaveAsData; struct _SaveAsData { /* Reffed */ GeditWindow *window; /* List of reffed GeditTab's */ GSList *tabs_to_save_as; guint close_tabs : 1; }; static void save_as_documents_list (SaveAsData *data); static void save_as_documents_list_cb (GeditTab *tab, GAsyncResult *result, SaveAsData *data) { gboolean saved = save_as_tab_finish (tab, result); if (saved && data->close_tabs) { close_tab (tab); } g_return_if_fail (tab == GEDIT_TAB (data->tabs_to_save_as->data)); g_object_unref (data->tabs_to_save_as->data); data->tabs_to_save_as = g_slist_delete_link (data->tabs_to_save_as, data->tabs_to_save_as); if (data->tabs_to_save_as != NULL) { save_as_documents_list (data); } else { g_object_unref (data->window); g_slice_free (SaveAsData, data); } } static void save_as_documents_list (SaveAsData *data) { GeditTab *next_tab = GEDIT_TAB (data->tabs_to_save_as->data); gedit_window_set_active_tab (data->window, next_tab); save_as_tab_async (next_tab, data->window, NULL, (GAsyncReadyCallback) save_as_documents_list_cb, data); } /* * The docs in the list must belong to the same GeditWindow. */ static void save_documents_list (GeditWindow *window, GList *docs) { SaveAsData *data = NULL; GList *l; gedit_debug (DEBUG_COMMANDS); g_return_if_fail ((gedit_window_get_state (window) & GEDIT_WINDOW_STATE_PRINTING) == 0); for (l = docs; l != NULL; l = l->next) { GeditDocument *doc; GeditTab *tab; GeditTabState state; g_return_if_fail (GEDIT_IS_DOCUMENT (l->data)); doc = l->data; tab = gedit_tab_get_from_document (doc); state = gedit_tab_get_state (tab); g_return_if_fail (state != GEDIT_TAB_STATE_PRINTING); g_return_if_fail (state != GEDIT_TAB_STATE_CLOSING); if (state == GEDIT_TAB_STATE_NORMAL || state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) { if (_gedit_document_needs_saving (doc)) { GtkSourceFile *file = gedit_document_get_file (doc); /* FIXME: manage the case of local readonly files owned by the user is running gedit - Paolo (Dec. 8, 2005) */ if (_gedit_document_is_untitled (doc) || gtk_source_file_is_readonly (file)) { if (data == NULL) { data = g_slice_new (SaveAsData); data->window = g_object_ref (window); data->tabs_to_save_as = NULL; data->close_tabs = FALSE; } data->tabs_to_save_as = g_slist_prepend (data->tabs_to_save_as, g_object_ref (tab)); } else { save_tab (tab, window); } } } else { /* If the state is: - GEDIT_TAB_STATE_LOADING: we do not save since we are sure the file is unmodified - GEDIT_TAB_STATE_REVERTING: we do not save since the user wants to return back to the version of the file she previously saved - GEDIT_TAB_STATE_SAVING: well, we are already saving (no need to save again) - GEDIT_TAB_STATE_PRINTING: there is not a real reason for not saving in this case, we do not save to avoid to run two operations using the message area at the same time (may be we can remove this limitation in the future). Note that SaveAll, ClosAll and Quit are unsensitive if the window state is PRINTING. - GEDIT_TAB_STATE_GENERIC_ERROR: we do not save since the document contains errors (I don't think this is a very frequent case, we should probably remove this state) - GEDIT_TAB_STATE_LOADING_ERROR: there is nothing to save - GEDIT_TAB_STATE_REVERTING_ERROR: there is nothing to save and saving the current document will overwrite the copy of the file the user wants to go back to - GEDIT_TAB_STATE_SAVING_ERROR: we do not save since we just failed to save, so there is no reason to automatically retry... we wait for user intervention - GEDIT_TAB_STATE_CLOSING: this state is invalid in this case */ gchar *full_name; full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); gedit_debug_message (DEBUG_COMMANDS, "File '%s' not saved. State: %d", full_name, state); g_free (full_name); } } if (data != NULL) { data->tabs_to_save_as = g_slist_reverse (data->tabs_to_save_as); save_as_documents_list (data); } } /** * gedit_commands_save_all_documents: * @window: a #GeditWindow. * * Asynchronously save all documents belonging to @window. The result of the * operation is not available, so it's difficult to know whether all the * documents are correctly saved. */ void gedit_commands_save_all_documents (GeditWindow *window) { GList *docs; g_return_if_fail (GEDIT_IS_WINDOW (window)); gedit_debug (DEBUG_COMMANDS); docs = gedit_window_get_documents (window); save_documents_list (window, docs); g_list_free (docs); } void _gedit_cmd_file_save_all (GSimpleAction *action, GVariant *parameter, gpointer user_data) { gedit_commands_save_all_documents (GEDIT_WINDOW (user_data)); } /** * gedit_commands_save_document: * @window: a #GeditWindow. * @document: the #GeditDocument to save. * * Asynchronously save @document. @document must belong to @window. If you need * the result of the operation, use gedit_commands_save_document_async(). */ void gedit_commands_save_document (GeditWindow *window, GeditDocument *document) { GeditTab *tab; g_return_if_fail (GEDIT_IS_WINDOW (window)); g_return_if_fail (GEDIT_IS_DOCUMENT (document)); gedit_debug (DEBUG_COMMANDS); tab = gedit_tab_get_from_document (document); save_tab (tab, window); } /* File revert */ static void do_revert (GeditWindow *window, GeditTab *tab) { GeditDocument *doc; gchar *docname; GeditStatusbar *statusbar; gedit_debug (DEBUG_COMMANDS); doc = gedit_tab_get_document (tab); docname = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); statusbar = GEDIT_STATUSBAR (gedit_window_get_statusbar (window)); _gedit_statusbar_flash_generic_message (statusbar, _("Reverting the document “%s”\342\200\246"), docname); g_free (docname); _gedit_tab_revert (tab); } static void revert_dialog_response_cb (GtkDialog *dialog, gint response_id, GeditWindow *window) { GeditTab *tab; gedit_debug (DEBUG_COMMANDS); /* FIXME: we are relying on the fact that the dialog is modal so the active tab can't be changed... not very nice - Paolo (Oct 11, 2005) */ tab = gedit_window_get_active_tab (window); if (tab == NULL) { return; } gtk_widget_destroy (GTK_WIDGET (dialog)); if (response_id == GTK_RESPONSE_OK) { do_revert (window, tab); } } static GtkWidget * revert_dialog (GeditWindow *window, GeditDocument *doc) { GtkWidget *dialog; gchar *docname; gchar *primary_msg; gchar *secondary_msg; glong seconds; gedit_debug (DEBUG_COMMANDS); docname = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); primary_msg = g_strdup_printf (_("Revert unsaved changes to document “%s”?"), docname); g_free (docname); seconds = MAX (1, _gedit_document_get_seconds_since_last_save_or_load (doc)); if (seconds < 55) { secondary_msg = g_strdup_printf ( ngettext ("Changes made to the document in the last %ld second " "will be permanently lost.", "Changes made to the document in the last %ld seconds " "will be permanently lost.", seconds), seconds); } else if (seconds < 75) /* 55 <= seconds < 75 */ { secondary_msg = g_strdup (_("Changes made to the document in the last minute " "will be permanently lost.")); } else if (seconds < 110) /* 75 <= seconds < 110 */ { secondary_msg = g_strdup_printf ( ngettext ("Changes made to the document in the last minute and " "%ld second will be permanently lost.", "Changes made to the document in the last minute and " "%ld seconds will be permanently lost.", seconds - 60 ), seconds - 60); } else if (seconds < 3600) { secondary_msg = g_strdup_printf ( ngettext ("Changes made to the document in the last %ld minute " "will be permanently lost.", "Changes made to the document in the last %ld minutes " "will be permanently lost.", seconds / 60), seconds / 60); } else if (seconds < 7200) { gint minutes; seconds -= 3600; minutes = seconds / 60; if (minutes < 5) { secondary_msg = g_strdup (_("Changes made to the document in the last hour " "will be permanently lost.")); } else { secondary_msg = g_strdup_printf ( ngettext ("Changes made to the document in the last hour and " "%d minute will be permanently lost.", "Changes made to the document in the last hour and " "%d minutes will be permanently lost.", minutes), minutes); } } else { gint hours; hours = seconds / 3600; secondary_msg = g_strdup_printf ( ngettext ("Changes made to the document in the last %d hour " "will be permanently lost.", "Changes made to the document in the last %d hours " "will be permanently lost.", hours), hours); } dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", primary_msg); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", secondary_msg); g_free (primary_msg); g_free (secondary_msg); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Revert"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); return dialog; } void _gedit_cmd_file_revert (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); GeditTab *tab; GeditDocument *doc; GtkWidget *dialog; GtkWindowGroup *window_group; gedit_debug (DEBUG_COMMANDS); tab = gedit_window_get_active_tab (window); g_return_if_fail (tab != NULL); /* If we are already displaying a notification reverting will drop local * modifications or if the document has not been modified, do not bug * the user further. */ if (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION || _gedit_tab_get_can_close (tab)) { do_revert (window, tab); return; } doc = gedit_tab_get_document (tab); g_return_if_fail (doc != NULL); g_return_if_fail (!_gedit_document_is_untitled (doc)); dialog = revert_dialog (window, doc); window_group = gedit_window_get_group (window); gtk_window_group_add_window (window_group, GTK_WINDOW (dialog)); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); g_signal_connect (dialog, "response", G_CALLBACK (revert_dialog_response_cb), window); gtk_widget_show (dialog); } static void tab_state_changed_while_saving (GeditTab *tab, GParamSpec *pspec, GeditWindow *window) { GeditTabState state; state = gedit_tab_get_state (tab); gedit_debug_message (DEBUG_COMMANDS, "State while saving: %d\n", state); /* When the state becomes NORMAL, it means the saving operation is * finished. */ if (state == GEDIT_TAB_STATE_NORMAL) { g_signal_handlers_disconnect_by_func (tab, G_CALLBACK (tab_state_changed_while_saving), window); close_tab (tab); } } static void save_and_close (GeditTab *tab, GeditWindow *window) { gedit_debug (DEBUG_COMMANDS); /* Trace tab state changes */ g_signal_connect (tab, "notify::state", G_CALLBACK (tab_state_changed_while_saving), window); save_tab (tab, window); } static void save_and_close_documents (GList *docs, GeditWindow *window, GeditNotebook *notebook) { GList *tabs; GList *l; GSList *sl; SaveAsData *data = NULL; GSList *tabs_to_save_and_close = NULL; GList *tabs_to_close = NULL; gedit_debug (DEBUG_COMMANDS); g_return_if_fail ((gedit_window_get_state (window) & GEDIT_WINDOW_STATE_PRINTING) == 0); if (notebook != NULL) { tabs = gtk_container_get_children (GTK_CONTAINER (notebook)); } else { tabs = _gedit_window_get_all_tabs (window); } for (l = tabs; l != NULL; l = l->next) { GeditTab *tab = GEDIT_TAB (l->data); GeditTabState state; GeditDocument *doc; state = gedit_tab_get_state (tab); doc = gedit_tab_get_document (tab); /* If the state is: ([*] invalid states) - GEDIT_TAB_STATE_NORMAL: close (and if needed save) - GEDIT_TAB_STATE_LOADING: close, we are sure the file is unmodified - GEDIT_TAB_STATE_REVERTING: since the user wants to return back to the version of the file she previously saved, we can close without saving (CHECK: are we sure this is the right behavior, suppose the case the original file has been deleted) - [*] GEDIT_TAB_STATE_SAVING: invalid, ClosAll and Quit are unsensitive if the window state is SAVING. - [*] GEDIT_TAB_STATE_PRINTING: there is not a real reason for not closing in this case, we do not save to avoid to run two operations using the message area at the same time (may be we can remove this limitation in the future). Note that ClosAll and Quit are unsensitive if the window state is PRINTING. - GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW: close (and if needed save) - GEDIT_TAB_STATE_LOADING_ERROR: close without saving (if the state is LOADING_ERROR then the document is not modified) - GEDIT_TAB_STATE_REVERTING_ERROR: we do not close since the document contains errors - GEDIT_TAB_STATE_SAVING_ERROR: we do not close since the document contains errors - GEDIT_TAB_STATE_GENERIC_ERROR: we do not close since the document contains errors (CHECK: we should problably remove this state) - [*] GEDIT_TAB_STATE_CLOSING: this state is invalid in this case */ g_return_if_fail (state != GEDIT_TAB_STATE_PRINTING); g_return_if_fail (state != GEDIT_TAB_STATE_CLOSING); g_return_if_fail (state != GEDIT_TAB_STATE_SAVING); if (state != GEDIT_TAB_STATE_SAVING_ERROR && state != GEDIT_TAB_STATE_GENERIC_ERROR && state != GEDIT_TAB_STATE_REVERTING_ERROR) { if (g_list_index (docs, doc) >= 0 && state != GEDIT_TAB_STATE_LOADING && state != GEDIT_TAB_STATE_LOADING_ERROR && state != GEDIT_TAB_STATE_REVERTING) /* FIXME: is this the right behavior with REVERTING ?*/ { GtkSourceFile *file = gedit_document_get_file (doc); /* The document must be saved before closing */ g_return_if_fail (_gedit_document_needs_saving (doc)); /* FIXME: manage the case of local readonly files owned by the * user is running gedit - Paolo (Dec. 8, 2005) */ if (_gedit_document_is_untitled (doc) || gtk_source_file_is_readonly (file)) { if (data == NULL) { data = g_slice_new (SaveAsData); data->window = g_object_ref (window); data->tabs_to_save_as = NULL; data->close_tabs = TRUE; } data->tabs_to_save_as = g_slist_prepend (data->tabs_to_save_as, g_object_ref (tab)); } else { tabs_to_save_and_close = g_slist_prepend (tabs_to_save_and_close, tab); } } else { /* The document can be closed without saving */ tabs_to_close = g_list_prepend (tabs_to_close, tab); } } } g_list_free (tabs); /* Close all tabs to close (in a sync way) */ gedit_window_close_tabs (window, tabs_to_close); g_list_free (tabs_to_close); /* Save and close all the files in tabs_to_save_and_close */ for (sl = tabs_to_save_and_close; sl != NULL; sl = sl->next) { save_and_close (GEDIT_TAB (sl->data), window); } g_slist_free (tabs_to_save_and_close); /* Save As and close all the files in data->tabs_to_save_as. */ if (data != NULL) { data->tabs_to_save_as = g_slist_reverse (data->tabs_to_save_as); save_as_documents_list (data); } } static void save_and_close_document (const GList *docs, GeditWindow *window) { GeditTab *tab; gedit_debug (DEBUG_COMMANDS); g_return_if_fail (docs->next == NULL); tab = gedit_tab_get_from_document (GEDIT_DOCUMENT (docs->data)); g_return_if_fail (tab != NULL); save_and_close (tab, window); } static void close_all_tabs (GeditWindow *window) { gedit_debug (DEBUG_COMMANDS); /* There is no document to save -> close all tabs */ gedit_window_close_all_tabs (window); quit_if_needed (window); } static void close_document (GeditWindow *window, GeditDocument *doc) { GeditTab *tab; gedit_debug (DEBUG_COMMANDS); tab = gedit_tab_get_from_document (doc); g_return_if_fail (tab != NULL); gedit_window_close_tab (window, tab); } static void close_confirmation_dialog_response_handler (GeditCloseConfirmationDialog *dlg, gint response_id, GeditWindow *window) { GList *selected_documents; gboolean is_closing_all; GeditNotebook *notebook_to_close; gedit_debug (DEBUG_COMMANDS); is_closing_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window), GEDIT_IS_CLOSING_ALL)); notebook_to_close = g_object_get_data (G_OBJECT (window), GEDIT_NOTEBOOK_TO_CLOSE); gtk_widget_hide (GTK_WIDGET (dlg)); switch (response_id) { /* Save and Close */ case GTK_RESPONSE_YES: selected_documents = gedit_close_confirmation_dialog_get_selected_documents (dlg); if (selected_documents == NULL) { if (is_closing_all) { /* There is no document to save -> close all tabs */ /* We call gtk_widget_destroy before close_all_tabs * because close_all_tabs could destroy the gedit window */ gtk_widget_destroy (GTK_WIDGET (dlg)); close_all_tabs (window); return; } else if (notebook_to_close) { gedit_notebook_remove_all_tabs (notebook_to_close); } else { g_return_if_reached (); } } else { if (is_closing_all || notebook_to_close) { GeditNotebook *notebook = is_closing_all ? NULL : notebook_to_close; save_and_close_documents (selected_documents, window, notebook); } else { save_and_close_document (selected_documents, window); } } g_list_free (selected_documents); break; /* Close without Saving */ case GTK_RESPONSE_NO: if (is_closing_all) { /* We call gtk_widget_destroy before close_all_tabs * because close_all_tabs could destroy the gedit window */ gtk_widget_destroy (GTK_WIDGET (dlg)); close_all_tabs (window); return; } else if (notebook_to_close) { gedit_notebook_remove_all_tabs (notebook_to_close); } else { const GList *unsaved_documents; unsaved_documents = gedit_close_confirmation_dialog_get_unsaved_documents (dlg); g_return_if_fail (unsaved_documents->next == NULL); close_document (window, GEDIT_DOCUMENT (unsaved_documents->data)); } break; /* Do not close */ default: /* Reset is_quitting flag */ g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING, GBOOLEAN_TO_POINTER (FALSE)); g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING_ALL, GBOOLEAN_TO_POINTER (FALSE)); break; } g_object_set_data (G_OBJECT (window), GEDIT_NOTEBOOK_TO_CLOSE, NULL); gtk_widget_destroy (GTK_WIDGET (dlg)); } /* Returns TRUE if the tab can be immediately closed */ static gboolean tab_can_close (GeditTab *tab, GtkWindow *window) { GeditDocument *doc; gedit_debug (DEBUG_COMMANDS); doc = gedit_tab_get_document (tab); if (!_gedit_tab_get_can_close (tab)) { GtkWidget *dlg; dlg = gedit_close_confirmation_dialog_new_single (window, doc); g_signal_connect (dlg, "response", G_CALLBACK (close_confirmation_dialog_response_handler), window); gtk_widget_show (dlg); return FALSE; } return TRUE; } /* FIXME: we probably need this one public for plugins... * maybe even a _list variant. Or maybe it's better make * gedit_window_close_tab always run the confirm dialog? * we should not allow closing a tab without resetting the * GEDIT_IS_CLOSING_ALL flag! */ void _gedit_cmd_file_close_tab (GeditTab *tab, GeditWindow *window) { gedit_debug (DEBUG_COMMANDS); g_return_if_fail (GTK_WIDGET (window) == gtk_widget_get_toplevel (GTK_WIDGET (tab))); g_object_set_data (G_OBJECT (window), GEDIT_IS_CLOSING_ALL, GBOOLEAN_TO_POINTER (FALSE)); g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING, GBOOLEAN_TO_POINTER (FALSE)); g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING_ALL, GBOOLEAN_TO_POINTER (FALSE)); if (tab_can_close (tab, GTK_WINDOW (window))) { gedit_window_close_tab (window, tab); } } void _gedit_cmd_file_close (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); GeditTab *active_tab; gedit_debug (DEBUG_COMMANDS); active_tab = gedit_window_get_active_tab (window); if (active_tab == NULL) { gtk_widget_destroy (GTK_WIDGET (window)); return; } _gedit_cmd_file_close_tab (active_tab, window); } static void file_close_dialog (GeditWindow *window, GList *unsaved_docs) { GtkWidget *dlg; if (unsaved_docs->next == NULL) { /* There is only one unsaved document */ GeditTab *tab; GeditDocument *doc; doc = GEDIT_DOCUMENT (unsaved_docs->data); tab = gedit_tab_get_from_document (doc); g_return_if_fail (tab != NULL); gedit_window_set_active_tab (window, tab); dlg = gedit_close_confirmation_dialog_new_single (GTK_WINDOW (window), doc); } else { dlg = gedit_close_confirmation_dialog_new (GTK_WINDOW (window), unsaved_docs); } g_signal_connect (dlg, "response", G_CALLBACK (close_confirmation_dialog_response_handler), window); gtk_widget_show (dlg); } static GList * notebook_get_unsaved_documents (GeditNotebook *notebook) { GList *children; GList *unsaved_docs = NULL; GList *l; children = gtk_container_get_children (GTK_CONTAINER (notebook)); for (l = children; l != NULL; l = g_list_next (l)) { GeditTab *tab = GEDIT_TAB (l->data); if (!_gedit_tab_get_can_close (tab)) { GeditDocument *doc; doc = gedit_tab_get_document (tab); unsaved_docs = g_list_prepend (unsaved_docs, doc); } } g_list_free (children); return g_list_reverse (unsaved_docs); } /* Close a notebook */ void _gedit_cmd_file_close_notebook (GeditWindow *window, GeditNotebook *notebook) { GList *unsaved_docs; g_object_set_data (G_OBJECT (window), GEDIT_IS_CLOSING_ALL, GBOOLEAN_TO_POINTER (FALSE)); g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING, GBOOLEAN_TO_POINTER (FALSE)); g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING_ALL, GBOOLEAN_TO_POINTER (FALSE)); g_object_set_data (G_OBJECT (window), GEDIT_NOTEBOOK_TO_CLOSE, notebook); unsaved_docs = notebook_get_unsaved_documents (notebook); if (unsaved_docs == NULL) { /* There is no document to save -> close the notebook */ gedit_notebook_remove_all_tabs (GEDIT_NOTEBOOK (notebook)); } else { file_close_dialog (window, unsaved_docs); g_list_free (unsaved_docs); } } /* Close all tabs */ static void file_close_all (GeditWindow *window, gboolean is_quitting) { GList *unsaved_docs; gedit_debug (DEBUG_COMMANDS); g_return_if_fail (!(gedit_window_get_state (window) & (GEDIT_WINDOW_STATE_SAVING | GEDIT_WINDOW_STATE_PRINTING))); g_object_set_data (G_OBJECT (window), GEDIT_IS_CLOSING_ALL, GBOOLEAN_TO_POINTER (TRUE)); g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING, GBOOLEAN_TO_POINTER (is_quitting)); unsaved_docs = gedit_window_get_unsaved_documents (window); if (unsaved_docs != NULL) { file_close_dialog (window, unsaved_docs); g_list_free (unsaved_docs); } else { /* There is no document to save -> close all tabs */ gedit_window_close_all_tabs (window); quit_if_needed (window); } } void _gedit_cmd_file_close_all (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); gedit_debug (DEBUG_COMMANDS); g_return_if_fail (!(gedit_window_get_state (window) & (GEDIT_WINDOW_STATE_SAVING | GEDIT_WINDOW_STATE_PRINTING))); file_close_all (window, FALSE); } /* Quit */ static void quit_all (void) { GList *windows; GList *l; GApplication *app; app = g_application_get_default (); windows = gedit_app_get_main_windows (GEDIT_APP (app)); if (windows == NULL) { g_application_quit (app); return; } for (l = windows; l != NULL; l = g_list_next (l)) { GeditWindow *window = l->data; g_object_set_data (G_OBJECT (window), GEDIT_IS_QUITTING_ALL, GBOOLEAN_TO_POINTER (TRUE)); if (!(gedit_window_get_state (window) & (GEDIT_WINDOW_STATE_SAVING | GEDIT_WINDOW_STATE_PRINTING))) { file_close_all (window, TRUE); } } g_list_free (windows); } void _gedit_cmd_file_quit (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GeditWindow *window = GEDIT_WINDOW (user_data); gedit_debug (DEBUG_COMMANDS); if (window == NULL) { quit_all (); return; } g_return_if_fail (!(gedit_window_get_state (window) & (GEDIT_WINDOW_STATE_SAVING | GEDIT_WINDOW_STATE_PRINTING))); file_close_all (window, TRUE); } /* ex:set ts=8 noet: */