From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- gedit/gedit-tab.c | 3057 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3057 insertions(+) create mode 100644 gedit/gedit-tab.c (limited to 'gedit/gedit-tab.c') diff --git a/gedit/gedit-tab.c b/gedit/gedit-tab.c new file mode 100644 index 0000000..3e3fc5b --- /dev/null +++ b/gedit/gedit-tab.c @@ -0,0 +1,3057 @@ +/* + * gedit-tab.c + * This file is part of gedit + * + * Copyright (C) 2005 - Paolo Maggi + * Copyright (C) 2014, 2015 - 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 "gedit-tab.h" +#include "gedit-tab-private.h" + +#include +#include +#include + +#include "gedit-app.h" +#include "gedit-app-private.h" +#include "gedit-recent.h" +#include "gedit-utils.h" +#include "gedit-io-error-info-bar.h" +#include "gedit-print-job.h" +#include "gedit-print-preview.h" +#include "gedit-debug.h" +#include "gedit-document.h" +#include "gedit-document-private.h" +#include "gedit-enum-types.h" +#include "gedit-settings.h" +#include "gedit-view-frame.h" + +#define GEDIT_TAB_KEY "GEDIT_TAB_KEY" + +struct _GeditTab +{ + GtkBox parent_instance; + + GeditTabState state; + + GSettings *editor_settings; + + GeditViewFrame *frame; + + GtkWidget *info_bar; + + GeditPrintJob *print_job; + GtkWidget *print_preview; + + GtkSourceFileSaverFlags save_flags; + + guint scroll_timeout; + guint scroll_idle; + + gint auto_save_interval; + guint auto_save_timeout; + + GCancellable *cancellable; + + guint editable : 1; + guint auto_save : 1; + + guint ask_if_externally_modified : 1; +}; + +typedef struct _SaverData SaverData; +typedef struct _LoaderData LoaderData; + +struct _SaverData +{ + GtkSourceFileSaver *saver; + + GTimer *timer; + + /* Notes about the create_backup saver flag: + * - At the beginning of a new file saving, force_no_backup is FALSE. + * The create_backup flag is set to the saver if it is enabled in + * GSettings and if it isn't an auto-save. + * - If creating the backup gives an error, and if the user wants to + * save the file without the backup, force_no_backup is set to TRUE + * and the create_backup flag is removed from the saver. + * force_no_backup as TRUE means that the create_backup flag should + * never be added again to the saver (for the current file saving). + * - When another error occurs and if the user explicitly retry again + * the file saving, the create_backup flag is added to the saver if + * (1) it is enabled in GSettings, (2) if force_no_backup is FALSE. + * - The create_backup flag is added when the user expressed his or her + * willing to save the file, by pressing a button for example. For an + * auto-save, the create_backup flag is thus not added initially, but + * can be added later when an error occurs and the user clicks on a + * button in the info bar to retry the file saving. + */ + guint force_no_backup : 1; +}; + +struct _LoaderData +{ + GeditTab *tab; + GtkSourceFileLoader *loader; + GTimer *timer; + gint line_pos; + gint column_pos; + guint user_requested_encoding : 1; +}; + +G_DEFINE_TYPE (GeditTab, gedit_tab, GTK_TYPE_BOX) + +enum +{ + PROP_0, + PROP_NAME, + PROP_STATE, + PROP_AUTO_SAVE, + PROP_AUTO_SAVE_INTERVAL, + PROP_CAN_CLOSE, + LAST_PROP +}; + +static GParamSpec *properties[LAST_PROP]; + +enum +{ + DROP_URIS, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static gboolean gedit_tab_auto_save (GeditTab *tab); + +static void launch_loader (GTask *loading_task, + const GtkSourceEncoding *encoding); + +static void launch_saver (GTask *saving_task); + +static SaverData * +saver_data_new (void) +{ + return g_slice_new0 (SaverData); +} + +static void +saver_data_free (SaverData *data) +{ + if (data != NULL) + { + if (data->saver != NULL) + { + g_object_unref (data->saver); + } + + if (data->timer != NULL) + { + g_timer_destroy (data->timer); + } + + g_slice_free (SaverData, data); + } +} + +static LoaderData * +loader_data_new (void) +{ + return g_slice_new0 (LoaderData); +} + +static void +loader_data_free (LoaderData *data) +{ + if (data != NULL) + { + if (data->loader != NULL) + { + g_object_unref (data->loader); + } + + if (data->timer != NULL) + { + g_timer_destroy (data->timer); + } + + g_slice_free (LoaderData, data); + } +} + +static void +set_editable (GeditTab *tab, + gboolean editable) +{ + GeditView *view; + gboolean val; + + tab->editable = editable != FALSE; + + view = gedit_tab_get_view (tab); + + val = (tab->state == GEDIT_TAB_STATE_NORMAL && + tab->editable); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (view), val); +} + +static void +install_auto_save_timeout (GeditTab *tab) +{ + if (tab->auto_save_timeout == 0) + { + g_return_if_fail (tab->auto_save_interval > 0); + + tab->auto_save_timeout = g_timeout_add_seconds (tab->auto_save_interval * 60, + (GSourceFunc) gedit_tab_auto_save, + tab); + } +} + +static void +remove_auto_save_timeout (GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + if (tab->auto_save_timeout > 0) + { + g_source_remove (tab->auto_save_timeout); + tab->auto_save_timeout = 0; + } +} + +static void +update_auto_save_timeout (GeditTab *tab) +{ + GeditDocument *doc; + GtkSourceFile *file; + + gedit_debug (DEBUG_TAB); + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + + if (tab->state == GEDIT_TAB_STATE_NORMAL && + tab->auto_save && + !_gedit_document_is_untitled (doc) && + !gtk_source_file_is_readonly (file)) + { + install_auto_save_timeout (tab); + } + else + { + remove_auto_save_timeout (tab); + } +} + +static void +gedit_tab_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditTab *tab = GEDIT_TAB (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_take_string (value, _gedit_tab_get_name (tab)); + break; + + case PROP_STATE: + g_value_set_enum (value, gedit_tab_get_state (tab)); + break; + + case PROP_AUTO_SAVE: + g_value_set_boolean (value, gedit_tab_get_auto_save_enabled (tab)); + break; + + case PROP_AUTO_SAVE_INTERVAL: + g_value_set_int (value, gedit_tab_get_auto_save_interval (tab)); + break; + + case PROP_CAN_CLOSE: + g_value_set_boolean (value, _gedit_tab_get_can_close (tab)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_tab_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditTab *tab = GEDIT_TAB (object); + + switch (prop_id) + { + case PROP_AUTO_SAVE: + gedit_tab_set_auto_save_enabled (tab, g_value_get_boolean (value)); + break; + + case PROP_AUTO_SAVE_INTERVAL: + gedit_tab_set_auto_save_interval (tab, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_tab_dispose (GObject *object) +{ + GeditTab *tab = GEDIT_TAB (object); + + g_clear_object (&tab->editor_settings); + g_clear_object (&tab->print_job); + g_clear_object (&tab->print_preview); + + remove_auto_save_timeout (tab); + + if (tab->scroll_timeout != 0) + { + g_source_remove (tab->scroll_timeout); + tab->scroll_timeout = 0; + } + + if (tab->scroll_idle != 0) + { + g_source_remove (tab->scroll_idle); + tab->scroll_idle = 0; + } + + if (tab->cancellable != NULL) + { + g_cancellable_cancel (tab->cancellable); + g_clear_object (&tab->cancellable); + } + + G_OBJECT_CLASS (gedit_tab_parent_class)->dispose (object); +} + +static void +gedit_tab_grab_focus (GtkWidget *widget) +{ + GeditTab *tab = GEDIT_TAB (widget); + + GTK_WIDGET_CLASS (gedit_tab_parent_class)->grab_focus (widget); + + if (tab->info_bar != NULL) + { + gtk_widget_grab_focus (tab->info_bar); + } + else + { + GeditView *view = gedit_tab_get_view (tab); + gtk_widget_grab_focus (GTK_WIDGET (view)); + } +} + +static void +gedit_tab_drop_uris (GeditTab *tab, + gchar **uri_list) +{ + gedit_debug (DEBUG_TAB); +} + +static void +gedit_tab_class_init (GeditTabClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gedit_tab_dispose; + object_class->get_property = gedit_tab_get_property; + object_class->set_property = gedit_tab_set_property; + + gtkwidget_class->grab_focus = gedit_tab_grab_focus; + + properties[PROP_NAME] = + g_param_spec_string ("name", + "Name", + "The tab's name", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_STATE] = + g_param_spec_enum ("state", + "State", + "The tab's state", + GEDIT_TYPE_TAB_STATE, + GEDIT_TAB_STATE_NORMAL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_AUTO_SAVE] = + g_param_spec_boolean ("autosave", + "Autosave", + "Autosave feature", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_AUTO_SAVE_INTERVAL] = + g_param_spec_int ("autosave-interval", + "AutosaveInterval", + "Time between two autosaves", + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_CAN_CLOSE] = + g_param_spec_boolean ("can-close", + "Can close", + "Whether the tab can be closed", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + signals[DROP_URIS] = + g_signal_new_class_handler ("drop-uris", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gedit_tab_drop_uris), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRV); +} + +/** + * gedit_tab_get_state: + * @tab: a #GeditTab + * + * Gets the #GeditTabState of @tab. + * + * Returns: the #GeditTabState of @tab + */ +GeditTabState +gedit_tab_get_state (GeditTab *tab) +{ + g_return_val_if_fail (GEDIT_IS_TAB (tab), GEDIT_TAB_STATE_NORMAL); + + return tab->state; +} + +static void +set_cursor_according_to_state (GtkTextView *view, + GeditTabState state) +{ + GdkDisplay *display; + GdkCursor *cursor; + GdkWindow *text_window; + GdkWindow *left_window; + + display = gtk_widget_get_display (GTK_WIDGET (view)); + + text_window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_TEXT); + left_window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_LEFT); + + if ((state == GEDIT_TAB_STATE_LOADING) || + (state == GEDIT_TAB_STATE_REVERTING) || + (state == GEDIT_TAB_STATE_SAVING) || + (state == GEDIT_TAB_STATE_PRINTING) || + (state == GEDIT_TAB_STATE_CLOSING)) + { + cursor = gdk_cursor_new_from_name (display, "progress"); + + if (text_window != NULL) + gdk_window_set_cursor (text_window, cursor); + if (left_window != NULL) + gdk_window_set_cursor (left_window, cursor); + + g_clear_object (&cursor); + } + else + { + cursor = gdk_cursor_new_from_name (display, "text"); + + if (text_window != NULL) + gdk_window_set_cursor (text_window, cursor); + if (left_window != NULL) + gdk_window_set_cursor (left_window, NULL); + + g_clear_object (&cursor); + } +} + +static void +view_realized (GtkTextView *view, + GeditTab *tab) +{ + set_cursor_according_to_state (view, tab->state); +} + +static void +set_view_properties_according_to_state (GeditTab *tab, + GeditTabState state) +{ + GeditView *view; + gboolean val; + gboolean hl_current_line; + + hl_current_line = g_settings_get_boolean (tab->editor_settings, + GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE); + + view = gedit_tab_get_view (tab); + + val = ((state == GEDIT_TAB_STATE_NORMAL) && + tab->editable); + gtk_text_view_set_editable (GTK_TEXT_VIEW (view), val); + + val = ((state != GEDIT_TAB_STATE_LOADING) && + (state != GEDIT_TAB_STATE_CLOSING)); + gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), val); + + val = ((state != GEDIT_TAB_STATE_LOADING) && + (state != GEDIT_TAB_STATE_CLOSING) && + (hl_current_line)); + gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (view), val); +} + +static void +gedit_tab_set_state (GeditTab *tab, + GeditTabState state) +{ + g_return_if_fail ((state >= 0) && (state < GEDIT_TAB_NUM_OF_STATES)); + + if (tab->state == state) + { + return; + } + + tab->state = state; + + set_view_properties_according_to_state (tab, state); + + /* Hide or show the document. + * For GEDIT_TAB_STATE_LOADING_ERROR, tab->frame is either shown or + * hidden, depending on the error. + */ + if (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + gtk_widget_hide (GTK_WIDGET (tab->frame)); + } + else if (state != GEDIT_TAB_STATE_LOADING_ERROR) + { + gtk_widget_show (GTK_WIDGET (tab->frame)); + } + + set_cursor_according_to_state (GTK_TEXT_VIEW (gedit_tab_get_view (tab)), + state); + + update_auto_save_timeout (tab); + + g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_STATE]); + g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_CAN_CLOSE]); +} + +static void +document_location_notify_handler (GtkSourceFile *file, + GParamSpec *pspec, + GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + /* Notify the change in the location */ + g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_NAME]); +} + +static void +document_shortname_notify_handler (TeplFile *file, + GParamSpec *pspec, + GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + /* Notify the change in the shortname */ + g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_NAME]); +} + +static void +document_modified_changed (GtkTextBuffer *document, + GeditTab *tab) +{ + g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_NAME]); + g_object_notify_by_pspec (G_OBJECT (tab), properties[PROP_CAN_CLOSE]); +} + +/* This function must be used carefully, and should be replaced by + * tepl_tab_add_info_bar() (note the *add*, not *set*). + * When certain infobars are set, it also configures GeditTab to be in a certain + * state (e.g. non-editable) and it waits a response from the infobar to restore + * the GeditTab state. If another infobar is set in the meantime, there will be + * a bug. + */ +static void +set_info_bar (GeditTab *tab, + GtkWidget *info_bar) +{ + if (tab->info_bar == info_bar) + { + return; + } + + if (tab->info_bar != NULL) + { + gtk_widget_destroy (tab->info_bar); + tab->info_bar = NULL; + } + + tab->info_bar = info_bar; + + if (info_bar != NULL) + { + gtk_box_pack_start (GTK_BOX (tab), info_bar, FALSE, FALSE, 0); + gtk_widget_show (info_bar); + } +} + +static void +remove_tab (GeditTab *tab) +{ + GtkWidget *notebook; + + notebook = gtk_widget_get_parent (GTK_WIDGET (tab)); + gtk_container_remove (GTK_CONTAINER (notebook), GTK_WIDGET (tab)); +} + +static void +io_loading_error_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + GFile *location; + const GtkSourceEncoding *encoding; + + location = gtk_source_file_loader_get_location (data->loader); + + switch (response_id) + { + case GTK_RESPONSE_OK: + encoding = gedit_conversion_error_info_bar_get_encoding (GTK_WIDGET (info_bar)); + + set_info_bar (data->tab, NULL); + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_LOADING); + + launch_loader (loading_task, encoding); + break; + + case GTK_RESPONSE_YES: + /* This means that we want to edit the document anyway */ + set_editable (data->tab, TRUE); + set_info_bar (data->tab, NULL); + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_NORMAL); + + g_task_return_boolean (loading_task, TRUE); + g_object_unref (loading_task); + break; + + default: + if (location != NULL) + { + gedit_recent_remove_if_local (location); + } + + remove_tab (data->tab); + + g_task_return_boolean (loading_task, FALSE); + g_object_unref (loading_task); + break; + } +} + +static void +file_already_open_warning_info_bar_response (GtkWidget *info_bar, + gint response_id, + GeditTab *tab) +{ + GeditView *view = gedit_tab_get_view (tab); + + if (response_id == GTK_RESPONSE_YES) + { + set_editable (tab, TRUE); + } + + set_info_bar (tab, NULL); + + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +load_cancelled (GtkWidget *bar, + gint response_id, + GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + + g_return_if_fail (TEPL_IS_PROGRESS_INFO_BAR (data->tab->info_bar)); + + g_cancellable_cancel (g_task_get_cancellable (loading_task)); + remove_tab (data->tab); +} + +static void +unrecoverable_reverting_error_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + GeditView *view; + + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_NORMAL); + + set_info_bar (data->tab, NULL); + + view = gedit_tab_get_view (data->tab); + gtk_widget_grab_focus (GTK_WIDGET (view)); + + g_task_return_boolean (loading_task, FALSE); + g_object_unref (loading_task); +} + +#define MAX_MSG_LENGTH 100 + +static void +show_loading_info_bar (GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + TeplProgressInfoBar *bar; + GeditDocument *doc; + gchar *name; + gchar *dirname = NULL; + gchar *msg = NULL; + gchar *name_markup; + gchar *dirname_markup; + gint len; + + if (data->tab->info_bar != NULL) + { + return; + } + + gedit_debug (DEBUG_TAB); + + doc = gedit_tab_get_document (data->tab); + + name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + len = g_utf8_strlen (name, -1); + + /* if the name is awfully long, truncate it and be done with it, + * otherwise also show the directory (ellipsized if needed) + */ + if (len > MAX_MSG_LENGTH) + { + gchar *str; + + str = tepl_utils_str_middle_truncate (name, MAX_MSG_LENGTH); + g_free (name); + name = str; + } + else + { + GtkSourceFile *file = gedit_document_get_file (doc); + GFile *location = gtk_source_file_get_location (file); + + if (location != NULL) + { + gchar *str = gedit_utils_location_get_dirname_for_display (location); + + /* use the remaining space for the dir, but use a min of 20 chars + * so that we do not end up with a dirname like "(a...b)". + * This means that in the worst case when the filename is long 99 + * we have a title long 99 + 20, but I think it's a rare enough + * case to be acceptable. It's justa darn title afterall :) + */ + dirname = tepl_utils_str_middle_truncate (str, + MAX (20, MAX_MSG_LENGTH - len)); + g_free (str); + } + } + + name_markup = g_markup_printf_escaped ("%s", name); + + if (data->tab->state == GEDIT_TAB_STATE_REVERTING) + { + if (dirname != NULL) + { + dirname_markup = g_markup_printf_escaped ("%s", dirname); + + /* Translators: the first %s is a file name (e.g. test.txt) the second one + is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */ + msg = g_strdup_printf (_("Reverting %s from %s"), + name_markup, + dirname_markup); + g_free (dirname_markup); + } + else + { + msg = g_strdup_printf (_("Reverting %s"), name_markup); + } + + bar = tepl_progress_info_bar_new ("document-revert", msg, TRUE); + } + else + { + if (dirname != NULL) + { + dirname_markup = g_markup_printf_escaped ("%s", dirname); + + /* Translators: the first %s is a file name (e.g. test.txt) the second one + is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */ + msg = g_strdup_printf (_("Loading %s from %s"), + name_markup, + dirname_markup); + g_free (dirname_markup); + } + else + { + msg = g_strdup_printf (_("Loading %s"), name_markup); + } + + bar = tepl_progress_info_bar_new ("document-open", msg, TRUE); + } + + g_signal_connect_object (bar, + "response", + G_CALLBACK (load_cancelled), + loading_task, + 0); + + set_info_bar (data->tab, GTK_WIDGET (bar)); + + g_free (msg); + g_free (name); + g_free (name_markup); + g_free (dirname); +} + +static void +show_saving_info_bar (GTask *saving_task) +{ + GeditTab *tab = g_task_get_source_object (saving_task); + TeplProgressInfoBar *bar; + GeditDocument *doc; + gchar *short_name; + gchar *from; + gchar *to = NULL; + gchar *from_markup; + gchar *to_markup; + gchar *msg = NULL; + gint len; + + if (tab->info_bar != NULL) + { + return; + } + + gedit_debug (DEBUG_TAB); + + doc = gedit_tab_get_document (tab); + short_name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + len = g_utf8_strlen (short_name, -1); + + /* if the name is awfully long, truncate it and be done with it, + * otherwise also show the directory (ellipsized if needed) + */ + if (len > MAX_MSG_LENGTH) + { + from = tepl_utils_str_middle_truncate (short_name, MAX_MSG_LENGTH); + g_free (short_name); + } + else + { + gchar *str; + SaverData *data; + GFile *location; + + data = g_task_get_task_data (saving_task); + location = gtk_source_file_saver_get_location (data->saver); + + from = short_name; + to = g_file_get_parse_name (location); + str = tepl_utils_str_middle_truncate (to, MAX (20, MAX_MSG_LENGTH - len)); + g_free (to); + + to = str; + } + + from_markup = g_markup_printf_escaped ("%s", from); + + if (to != NULL) + { + to_markup = g_markup_printf_escaped ("%s", to); + + /* Translators: the first %s is a file name (e.g. test.txt) the second one + is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */ + msg = g_strdup_printf (_("Saving %s to %s"), from_markup, to_markup); + g_free (to_markup); + } + else + { + msg = g_strdup_printf (_("Saving %s"), from_markup); + } + + bar = tepl_progress_info_bar_new ("document-save", msg, FALSE); + + set_info_bar (tab, GTK_WIDGET (bar)); + + g_free (msg); + g_free (to); + g_free (from); + g_free (from_markup); +} + +static void +info_bar_set_progress (GeditTab *tab, + goffset size, + goffset total_size) +{ + TeplProgressInfoBar *progress_info_bar; + + if (tab->info_bar == NULL) + { + return; + } + + gedit_debug_message (DEBUG_TAB, "%" G_GOFFSET_FORMAT "/%" G_GOFFSET_FORMAT, size, total_size); + + g_return_if_fail (TEPL_IS_PROGRESS_INFO_BAR (tab->info_bar)); + + progress_info_bar = TEPL_PROGRESS_INFO_BAR (tab->info_bar); + + if (total_size != 0) + { + gdouble frac = (gdouble)size / (gdouble)total_size; + + tepl_progress_info_bar_set_fraction (progress_info_bar, frac); + } + else if (size != 0) + { + tepl_progress_info_bar_pulse (progress_info_bar); + } + else + { + tepl_progress_info_bar_set_fraction (progress_info_bar, 0); + } +} + +/* Returns whether progress info should be shown. */ +static gboolean +should_show_progress_info (GTimer **timer, + goffset size, + goffset total_size) +{ + gdouble elapsed_time; + gdouble total_time; + gdouble remaining_time; + + g_assert (timer != NULL); + + if (*timer == NULL) + { + return TRUE; + } + + elapsed_time = g_timer_elapsed (*timer, NULL); + + /* Wait a little, because at the very beginning it's maybe not very + * accurate (it takes initially more time for the first bytes, the + * following chunks should arrive more quickly, as a rough guess). + */ + if (elapsed_time < 0.5) + { + return FALSE; + } + + /* elapsed_time / total_time = size / total_size */ + total_time = (elapsed_time * total_size) / size; + + remaining_time = total_time - elapsed_time; + + /* Approximately more than 3 seconds remaining. */ + if (remaining_time > 3.0) + { + /* Once the progress info bar is shown, it must remain + * shown until the end, so we don't need the timer + * anymore. + */ + g_timer_destroy (*timer); + *timer = NULL; + + return TRUE; + } + + return FALSE; +} + +static gboolean +scroll_timeout_cb (GeditTab *tab) +{ + GeditView *view; + + view = gedit_tab_get_view (tab); + tepl_view_scroll_to_cursor (TEPL_VIEW (view)); + + tab->scroll_timeout = 0; + return G_SOURCE_REMOVE; +} + +static gboolean +scroll_idle_cb (GeditTab *tab) +{ + /* The idle is not enough, for a detailed analysis of this, see: + * https://wiki.gnome.org/Apps/Gedit/FixingTextCutOffBug + * or the commit message that changed this. + * (here it's a hack, a proper solution in GTK/GtkTextView should be + * found). + */ + if (tab->scroll_timeout == 0) + { + /* Same number of ms as GtkSearchEntry::search-changed delay. + * Small enough to not be noticeable, but needs to be at least a + * few frames from the GdkFrameClock (during app startup). + */ + tab->scroll_timeout = g_timeout_add (150, (GSourceFunc)scroll_timeout_cb, tab); + } + + tab->scroll_idle = 0; + return G_SOURCE_REMOVE; +} + +static void +unrecoverable_saving_error_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *saving_task) +{ + GeditTab *tab = g_task_get_source_object (saving_task); + GeditView *view; + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + + set_info_bar (tab, NULL); + + view = gedit_tab_get_view (tab); + gtk_widget_grab_focus (GTK_WIDGET (view)); + + g_task_return_boolean (saving_task, FALSE); + g_object_unref (saving_task); +} + +/* Sets the save flags after an info bar response. */ +static void +response_set_save_flags (GTask *saving_task, + GtkSourceFileSaverFlags save_flags) +{ + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + gboolean create_backup; + + create_backup = g_settings_get_boolean (tab->editor_settings, + GEDIT_SETTINGS_CREATE_BACKUP_COPY); + + /* If we are here, it means that the user expressed his or her willing + * to save the file, by pressing a button in the info bar. So even if + * the file saving was initially an auto-save, we set the create_backup + * flag (if the conditions are met). + */ + if (create_backup && !data->force_no_backup) + { + save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP; + } + else + { + save_flags &= ~GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP; + } + + gtk_source_file_saver_set_flags (data->saver, save_flags); +} + +static void +invalid_character_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *saving_task) +{ + if (response_id == GTK_RESPONSE_YES) + { + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + GtkSourceFileSaverFlags save_flags; + + set_info_bar (tab, NULL); + + /* Don't bug the user again with this... */ + tab->save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS; + + save_flags = gtk_source_file_saver_get_flags (data->saver); + save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS; + response_set_save_flags (saving_task, save_flags); + + /* Force saving */ + launch_saver (saving_task); + } + else + { + unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task); + } +} + +static void +cant_create_backup_error_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *saving_task) +{ + if (response_id == GTK_RESPONSE_YES) + { + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + GtkSourceFileSaverFlags save_flags; + + set_info_bar (tab, NULL); + + data->force_no_backup = TRUE; + save_flags = gtk_source_file_saver_get_flags (data->saver); + response_set_save_flags (saving_task, save_flags); + + /* Force saving */ + launch_saver (saving_task); + } + else + { + unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task); + } +} + +static void +externally_modified_error_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *saving_task) +{ + if (response_id == GTK_RESPONSE_YES) + { + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + GtkSourceFileSaverFlags save_flags; + + set_info_bar (tab, NULL); + + /* ignore_modification_time should not be persisted in save + * flags across saves (i.e. tab->save_flags is not modified). + */ + save_flags = gtk_source_file_saver_get_flags (data->saver); + save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME; + response_set_save_flags (saving_task, save_flags); + + /* Force saving */ + launch_saver (saving_task); + } + else + { + unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task); + } +} + +static void +recoverable_saving_error_info_bar_response (GtkWidget *info_bar, + gint response_id, + GTask *saving_task) +{ + if (response_id == GTK_RESPONSE_OK) + { + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + const GtkSourceEncoding *encoding; + + set_info_bar (tab, NULL); + + encoding = gedit_conversion_error_info_bar_get_encoding (GTK_WIDGET (info_bar)); + g_return_if_fail (encoding != NULL); + + gtk_source_file_saver_set_encoding (data->saver, encoding); + launch_saver (saving_task); + } + else + { + unrecoverable_saving_error_info_bar_response (info_bar, response_id, saving_task); + } +} + +static void +externally_modified_notification_info_bar_response (GtkWidget *info_bar, + gint response_id, + GeditTab *tab) +{ + GeditView *view; + + set_info_bar (tab, NULL); + + view = gedit_tab_get_view (tab); + + if (response_id == GTK_RESPONSE_OK) + { + _gedit_tab_revert (tab); + } + else + { + tab->ask_if_externally_modified = FALSE; + + /* go back to normal state */ + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + } + + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +display_externally_modified_notification (GeditTab *tab) +{ + TeplInfoBar *info_bar; + GeditDocument *doc; + GtkSourceFile *file; + GFile *location; + gboolean document_modified; + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + + /* we're here because the file we're editing changed on disk */ + location = gtk_source_file_get_location (file); + g_return_if_fail (location != NULL); + + document_modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)); + info_bar = tepl_io_error_info_bar_externally_modified (location, document_modified); + + set_info_bar (tab, GTK_WIDGET (info_bar)); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (externally_modified_notification_info_bar_response), + tab); +} + +static gboolean +view_focused_in (GtkWidget *widget, + GdkEventFocus *event, + GeditTab *tab) +{ + GeditDocument *doc; + GtkSourceFile *file; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), GDK_EVENT_PROPAGATE); + + /* we try to detect file changes only in the normal state */ + if (tab->state != GEDIT_TAB_STATE_NORMAL) + { + return GDK_EVENT_PROPAGATE; + } + + /* we already asked, don't bug the user again */ + if (!tab->ask_if_externally_modified) + { + return GDK_EVENT_PROPAGATE; + } + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + + /* If file was never saved or is remote we do not check */ + if (gtk_source_file_is_local (file)) + { + gtk_source_file_check_file_on_disk (file); + + if (gtk_source_file_is_externally_modified (file)) + { + gedit_tab_set_state (tab, GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION); + + display_externally_modified_notification (tab); + } + } + + return GDK_EVENT_PROPAGATE; +} + +static void +on_drop_uris (GeditView *view, + gchar **uri_list, + GeditTab *tab) +{ + g_signal_emit (G_OBJECT (tab), signals[DROP_URIS], 0, uri_list); +} + +static void +gedit_tab_init (GeditTab *tab) +{ + gboolean auto_save; + gint auto_save_interval; + GeditDocument *doc; + GeditView *view; + GtkSourceFile *file; + TeplFile *tepl_file; + + tab->state = GEDIT_TAB_STATE_NORMAL; + + tab->editor_settings = g_settings_new ("org.gnome.gedit.preferences.editor"); + + tab->editable = TRUE; + + tab->ask_if_externally_modified = TRUE; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (tab), + GTK_ORIENTATION_VERTICAL); + + /* Manage auto save data */ + auto_save = g_settings_get_boolean (tab->editor_settings, + GEDIT_SETTINGS_AUTO_SAVE); + g_settings_get (tab->editor_settings, GEDIT_SETTINGS_AUTO_SAVE_INTERVAL, + "u", &auto_save_interval); + tab->auto_save = auto_save != FALSE; + tab->auto_save_interval = auto_save_interval; + + /* Create the frame */ + tab->frame = gedit_view_frame_new (); + gtk_widget_show (GTK_WIDGET (tab->frame)); + + gtk_box_pack_end (GTK_BOX (tab), GTK_WIDGET (tab->frame), TRUE, TRUE, 0); + + doc = gedit_tab_get_document (tab); + g_object_set_data (G_OBJECT (doc), GEDIT_TAB_KEY, tab); + + file = gedit_document_get_file (doc); + tepl_file = tepl_buffer_get_file (TEPL_BUFFER (doc)); + + g_signal_connect_object (file, + "notify::location", + G_CALLBACK (document_location_notify_handler), + tab, + 0); + + g_signal_connect_object (tepl_file, + "notify::short-name", + G_CALLBACK (document_shortname_notify_handler), + tab, + 0); + + g_signal_connect (doc, + "modified_changed", + G_CALLBACK (document_modified_changed), + tab); + + view = gedit_tab_get_view (tab); + + g_signal_connect_after (view, + "focus-in-event", + G_CALLBACK (view_focused_in), + tab); + + g_signal_connect_after (view, + "realize", + G_CALLBACK (view_realized), + tab); + + g_signal_connect (view, + "drop-uris", + G_CALLBACK (on_drop_uris), + tab); +} + +GeditTab * +_gedit_tab_new (void) +{ + return g_object_new (GEDIT_TYPE_TAB, NULL); +} + +/** + * gedit_tab_get_view: + * @tab: a #GeditTab + * + * Gets the #GeditView inside @tab. + * + * Returns: (transfer none): the #GeditView inside @tab + */ +GeditView * +gedit_tab_get_view (GeditTab *tab) +{ + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + return gedit_view_frame_get_view (tab->frame); +} + +/** + * gedit_tab_get_document: + * @tab: a #GeditTab + * + * Gets the #GeditDocument associated to @tab. + * + * Returns: (transfer none): the #GeditDocument associated to @tab + */ +GeditDocument * +gedit_tab_get_document (GeditTab *tab) +{ + GeditView *view; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + view = gedit_view_frame_get_view (tab->frame); + + return GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); +} + +#define MAX_DOC_NAME_LENGTH 40 + +gchar * +_gedit_tab_get_name (GeditTab *tab) +{ + GeditDocument *doc; + gchar *name; + gchar *docname; + gchar *tab_name; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + doc = gedit_tab_get_document (tab); + name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + + /* Truncate the name so it doesn't get insanely wide. */ + docname = tepl_utils_str_middle_truncate (name, MAX_DOC_NAME_LENGTH); + + if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc))) + { + tab_name = g_strdup_printf ("*%s", docname); + } + else + { + tab_name = g_strdup (docname); + } + + g_free (docname); + g_free (name); + + return tab_name; +} + +gchar * +_gedit_tab_get_tooltip (GeditTab *tab) +{ + GeditDocument *doc; + gchar *full_name; + gchar *full_name_markup; + gchar *tip; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + doc = gedit_tab_get_document (tab); + full_name = tepl_file_get_full_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + full_name_markup = g_markup_printf_escaped ("%s", full_name); + + switch (tab->state) + { + gchar *content_type; + gchar *mime_type; + gchar *content_description; + gchar *content_full_description; + gchar *encoding; + GtkSourceFile *file; + const GtkSourceEncoding *enc; + + case GEDIT_TAB_STATE_LOADING_ERROR: + tip = g_strdup_printf (_("Error opening file %s"), full_name_markup); + break; + + case GEDIT_TAB_STATE_REVERTING_ERROR: + tip = g_strdup_printf (_("Error reverting file %s"), full_name_markup); + break; + + case GEDIT_TAB_STATE_SAVING_ERROR: + tip = g_strdup_printf (_("Error saving file %s"), full_name_markup); + break; + + default: + content_type = gedit_document_get_content_type (doc); + mime_type = gedit_document_get_mime_type (doc); + content_description = g_content_type_get_description (content_type); + + if (content_description == NULL) + content_full_description = g_strdup (mime_type); + else + content_full_description = g_strdup_printf ("%s (%s)", + content_description, mime_type); + + g_free (content_type); + g_free (mime_type); + g_free (content_description); + + file = gedit_document_get_file (doc); + enc = gtk_source_file_get_encoding (file); + + if (enc == NULL) + { + enc = gtk_source_encoding_get_utf8 (); + } + + encoding = gtk_source_encoding_to_string (enc); + + tip = g_markup_printf_escaped ("%s %s\n\n" + "%s %s\n" + "%s %s", + _("Name:"), full_name, + _("MIME Type:"), content_full_description, + _("Encoding:"), encoding); + + g_free (encoding); + g_free (content_full_description); + break; + } + + g_free (full_name); + g_free (full_name_markup); + return tip; +} + +GdkPixbuf * +_gedit_tab_get_icon (GeditTab *tab) +{ + const gchar *icon_name; + GdkPixbuf *pixbuf = NULL; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + switch (tab->state) + { + case GEDIT_TAB_STATE_PRINTING: + icon_name = "printer-printing-symbolic"; + break; + + case GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW: + icon_name = "printer-symbolic"; + break; + + case GEDIT_TAB_STATE_LOADING_ERROR: + case GEDIT_TAB_STATE_REVERTING_ERROR: + case GEDIT_TAB_STATE_SAVING_ERROR: + case GEDIT_TAB_STATE_GENERIC_ERROR: + icon_name = "dialog-error-symbolic"; + break; + + case GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION: + icon_name = "dialog-warning-symbolic"; + break; + + default: + icon_name = NULL; + } + + if (icon_name != NULL) + { + GdkScreen *screen; + GtkIconTheme *theme; + gint icon_size; + + screen = gtk_widget_get_screen (GTK_WIDGET (tab)); + theme = gtk_icon_theme_get_for_screen (screen); + g_return_val_if_fail (theme != NULL, NULL); + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size); + + pixbuf = gtk_icon_theme_load_icon (theme, icon_name, icon_size, 0, NULL); + } + + return pixbuf; +} + +/** + * gedit_tab_get_from_document: + * @doc: a #GeditDocument + * + * Gets the #GeditTab associated with @doc. + * + * Returns: (transfer none): the #GeditTab associated with @doc + */ +GeditTab * +gedit_tab_get_from_document (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return g_object_get_data (G_OBJECT (doc), GEDIT_TAB_KEY); +} + +static void +loader_progress_cb (goffset size, + goffset total_size, + GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + + g_return_if_fail (data->tab->state == GEDIT_TAB_STATE_LOADING || + data->tab->state == GEDIT_TAB_STATE_REVERTING); + + if (should_show_progress_info (&data->timer, size, total_size)) + { + show_loading_info_bar (loading_task); + info_bar_set_progress (data->tab, size, total_size); + } +} + +static void +goto_line (GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + GeditDocument *doc = gedit_tab_get_document (data->tab); + gboolean check_is_cursor_position = FALSE; + GtkTextIter iter; + + /* To the top by default. */ + gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (doc), &iter); + + /* At the requested line/column if set. */ + if (data->line_pos > 0) + { + gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (doc), + &iter, + data->line_pos - 1, + MAX (0, data->column_pos - 1)); + check_is_cursor_position = TRUE; + } + + /* From metadata. */ + else if (g_settings_get_boolean (data->tab->editor_settings, + GEDIT_SETTINGS_RESTORE_CURSOR_POSITION)) + { + gchar *position_str; + guint64 offset = 0; + + position_str = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_POSITION); + + if (position_str != NULL && + g_ascii_string_to_unsigned (position_str, + 10, + 0, + G_MAXINT, + &offset, + NULL)) + { + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), + &iter, + (gint) offset); + check_is_cursor_position = TRUE; + } + + g_free (position_str); + } + + /* Make sure it's a valid position, to not end up in the middle of a + * utf8 character cluster. + */ + if (check_is_cursor_position && + !gtk_text_iter_is_cursor_position (&iter)) + { + gtk_text_iter_set_line_offset (&iter, 0); + } + + gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter); + + /* Scroll to the cursor when the document is loaded, we need to do it in + * an idle as after the document is loaded the textview is still + * redrawing and relocating its internals. + */ + if (data->tab->scroll_idle == 0 && + !gtk_text_iter_is_start (&iter)) + { + data->tab->scroll_idle = g_idle_add ((GSourceFunc)scroll_idle_cb, data->tab); + } +} + +static gboolean +file_already_opened (GeditDocument *doc, + GFile *location) +{ + GList *all_documents; + GList *l; + gboolean already_opened = FALSE; + + if (location == NULL) + { + return FALSE; + } + + all_documents = gedit_app_get_documents (GEDIT_APP (g_application_get_default ())); + + for (l = all_documents; l != NULL; l = l->next) + { + GeditDocument *cur_doc = l->data; + GtkSourceFile *cur_file; + GFile *cur_location; + + if (cur_doc == doc) + { + continue; + } + + cur_file = gedit_document_get_file (cur_doc); + cur_location = gtk_source_file_get_location (cur_file); + + if (cur_location != NULL && + g_file_equal (location, cur_location)) + { + already_opened = TRUE; + break; + } + } + + g_list_free (all_documents); + + return already_opened; +} + +static void +successful_load (GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + GeditDocument *doc = gedit_tab_get_document (data->tab); + GtkSourceFile *file = gedit_document_get_file (doc); + GFile *location; + + if (data->user_requested_encoding) + { + const GtkSourceEncoding *encoding = gtk_source_file_loader_get_encoding (data->loader); + const gchar *charset = gtk_source_encoding_get_charset (encoding); + + gedit_document_set_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_ENCODING, charset, + NULL); + } + + goto_line (loading_task); + + location = gtk_source_file_loader_get_location (data->loader); + + /* If the document is readonly we don't care how many times the file + * is opened. + */ + if (!gtk_source_file_is_readonly (file) && + file_already_opened (doc, location)) + { + TeplInfoBar *info_bar; + + set_editable (data->tab, FALSE); + + info_bar = tepl_io_error_info_bar_file_already_open (location); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (file_already_open_warning_info_bar_response), + data->tab); + + set_info_bar (data->tab, GTK_WIDGET (info_bar)); + } + + /* When loading from stdin, the contents may not be saved, so set the + * buffer as modified. + */ + if (location == NULL) + { + gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (doc), TRUE); + } + + data->tab->ask_if_externally_modified = TRUE; + + g_signal_emit_by_name (doc, "loaded"); +} + +static void +load_cb (GtkSourceFileLoader *loader, + GAsyncResult *result, + GTask *loading_task) +{ + LoaderData *data = g_task_get_task_data (loading_task); + GeditDocument *doc; + GFile *location = gtk_source_file_loader_get_location (loader); + gboolean create_named_new_doc; + GError *error = NULL; + + g_clear_pointer (&data->timer, g_timer_destroy); + + gtk_source_file_loader_load_finish (loader, result, &error); + + if (error != NULL) + { + gedit_debug_message (DEBUG_TAB, "File loading error: %s", error->message); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_task_return_boolean (loading_task, FALSE); + g_object_unref (loading_task); + + g_error_free (error); + return; + } + } + + doc = gedit_tab_get_document (data->tab); + + g_return_if_fail (data->tab->state == GEDIT_TAB_STATE_LOADING || + data->tab->state == GEDIT_TAB_STATE_REVERTING); + + set_info_bar (data->tab, NULL); + + /* Special case creating a named new doc. */ + create_named_new_doc = (_gedit_document_get_create (doc) && + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && + g_file_has_uri_scheme (location, "file")); + + if (create_named_new_doc) + { + g_error_free (error); + error = NULL; + } + + if (g_error_matches (error, + GTK_SOURCE_FILE_LOADER_ERROR, + GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK)) + { + GtkWidget *info_bar; + const GtkSourceEncoding *encoding; + + /* Set the tab as not editable as we have an error, the user can + * decide to make it editable again. + */ + set_editable (data->tab, FALSE); + + encoding = gtk_source_file_loader_get_encoding (loader); + + info_bar = gedit_io_loading_error_info_bar_new (location, encoding, error); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (io_loading_error_info_bar_response), + loading_task); + + set_info_bar (data->tab, info_bar); + + if (data->tab->state == GEDIT_TAB_STATE_LOADING) + { + gtk_widget_show (GTK_WIDGET (data->tab->frame)); + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_LOADING_ERROR); + } + else + { + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_REVERTING_ERROR); + } + + /* The loading was successful, despite some invalid characters. */ + successful_load (loading_task); + gedit_recent_add_document (doc); + + g_error_free (error); + return; + } + + if (error != NULL) + { + GtkWidget *info_bar; + + if (data->tab->state == GEDIT_TAB_STATE_LOADING) + { + gtk_widget_hide (GTK_WIDGET (data->tab->frame)); + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_LOADING_ERROR); + } + else + { + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_REVERTING_ERROR); + } + + if (location != NULL) + { + gedit_recent_remove_if_local (location); + } + + if (data->tab->state == GEDIT_TAB_STATE_LOADING_ERROR) + { + const GtkSourceEncoding *encoding; + + encoding = gtk_source_file_loader_get_encoding (loader); + + info_bar = gedit_io_loading_error_info_bar_new (location, encoding, error); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (io_loading_error_info_bar_response), + loading_task); + } + else + { + g_return_if_fail (data->tab->state == GEDIT_TAB_STATE_REVERTING_ERROR); + + info_bar = gedit_unrecoverable_reverting_error_info_bar_new (location, error); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (unrecoverable_reverting_error_info_bar_response), + loading_task); + } + + set_info_bar (data->tab, info_bar); + + g_error_free (error); + return; + } + + g_assert (error == NULL); + + gedit_tab_set_state (data->tab, GEDIT_TAB_STATE_NORMAL); + successful_load (loading_task); + + if (!create_named_new_doc) + { + gedit_recent_add_document (doc); + } + + g_task_return_boolean (loading_task, TRUE); + g_object_unref (loading_task); +} + +/* The returned list may contain duplicated encodings. Only the first occurrence + * of a duplicated encoding should be kept, like it is done by + * gtk_source_file_loader_set_candidate_encodings(). + */ +static GSList * +get_candidate_encodings (GeditTab *tab) +{ + GSList *candidates = NULL; + GeditDocument *doc; + GtkSourceFile *file; + gchar *metadata_charset; + const GtkSourceEncoding *file_encoding; + + candidates = gedit_settings_get_candidate_encodings (NULL); + + /* Prepend the encoding stored in the metadata. */ + doc = gedit_tab_get_document (tab); + metadata_charset = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_ENCODING); + + if (metadata_charset != NULL) + { + const GtkSourceEncoding *metadata_enc; + + metadata_enc = gtk_source_encoding_get_from_charset (metadata_charset); + + if (metadata_enc != NULL) + { + candidates = g_slist_prepend (candidates, (gpointer)metadata_enc); + } + } + + /* Finally prepend the GtkSourceFile's encoding, if previously set by a + * file loader or file saver. + */ + file = gedit_document_get_file (doc); + file_encoding = gtk_source_file_get_encoding (file); + + if (file_encoding != NULL) + { + candidates = g_slist_prepend (candidates, (gpointer)file_encoding); + } + + g_free (metadata_charset); + return candidates; +} + +static void +launch_loader (GTask *loading_task, + const GtkSourceEncoding *encoding) +{ + LoaderData *data = g_task_get_task_data (loading_task); + GSList *candidate_encodings = NULL; + GeditDocument *doc; + + if (encoding != NULL) + { + data->user_requested_encoding = TRUE; + candidate_encodings = g_slist_append (NULL, (gpointer) encoding); + } + else + { + data->user_requested_encoding = FALSE; + candidate_encodings = get_candidate_encodings (data->tab); + } + + gtk_source_file_loader_set_candidate_encodings (data->loader, candidate_encodings); + g_slist_free (candidate_encodings); + + doc = gedit_tab_get_document (data->tab); + g_signal_emit_by_name (doc, "load"); + + if (data->timer != NULL) + { + g_timer_destroy (data->timer); + } + + data->timer = g_timer_new (); + + gtk_source_file_loader_load_async (data->loader, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (loading_task), + (GFileProgressCallback) loader_progress_cb, + loading_task, + NULL, + (GAsyncReadyCallback) load_cb, + loading_task); +} + +static void +load_async (GeditTab *tab, + GFile *location, + const GtkSourceEncoding *encoding, + gint line_pos, + gint column_pos, + gboolean create, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GeditDocument *doc; + GtkSourceFile *file; + GTask *loading_task; + LoaderData *data; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (G_IS_FILE (location)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING); + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + gtk_source_file_set_location (file, location); + + loading_task = g_task_new (NULL, cancellable, callback, user_data); + + data = loader_data_new (); + g_task_set_task_data (loading_task, data, (GDestroyNotify) loader_data_free); + + data->tab = tab; + data->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (doc), file); + data->line_pos = line_pos; + data->column_pos = column_pos; + + _gedit_document_set_create (doc, create); + + launch_loader (loading_task, encoding); +} + +static gboolean +load_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); +} + +void +_gedit_tab_load (GeditTab *tab, + GFile *location, + const GtkSourceEncoding *encoding, + gint line_pos, + gint column_pos, + gboolean create) +{ + if (tab->cancellable != NULL) + { + g_cancellable_cancel (tab->cancellable); + g_object_unref (tab->cancellable); + } + + tab->cancellable = g_cancellable_new (); + + load_async (tab, + location, + encoding, + line_pos, + column_pos, + create, + tab->cancellable, + (GAsyncReadyCallback) load_finish, + NULL); +} + +static void +load_stream_async (GeditTab *tab, + GInputStream *stream, + const GtkSourceEncoding *encoding, + gint line_pos, + gint column_pos, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GeditDocument *doc; + GtkSourceFile *file; + GTask *loading_task; + LoaderData *data; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING); + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + + gtk_source_file_set_location (file, NULL); + + loading_task = g_task_new (NULL, cancellable, callback, user_data); + + data = loader_data_new (); + g_task_set_task_data (loading_task, data, (GDestroyNotify) loader_data_free); + + data->tab = tab; + data->loader = gtk_source_file_loader_new_from_stream (GTK_SOURCE_BUFFER (doc), + file, + stream); + data->line_pos = line_pos; + data->column_pos = column_pos; + + _gedit_document_set_create (doc, FALSE); + + launch_loader (loading_task, encoding); +} + +void +_gedit_tab_load_stream (GeditTab *tab, + GInputStream *stream, + const GtkSourceEncoding *encoding, + gint line_pos, + gint column_pos) +{ + if (tab->cancellable != NULL) + { + g_cancellable_cancel (tab->cancellable); + g_object_unref (tab->cancellable); + } + + tab->cancellable = g_cancellable_new (); + + load_stream_async (tab, + stream, + encoding, + line_pos, + column_pos, + tab->cancellable, + (GAsyncReadyCallback) load_finish, + NULL); +} + +static void +revert_async (GeditTab *tab, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GeditDocument *doc; + GtkSourceFile *file; + GFile *location; + GTask *loading_task; + LoaderData *data; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL || + tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION); + + if (tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) + { + set_info_bar (tab, NULL); + } + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + location = gtk_source_file_get_location (file); + g_return_if_fail (location != NULL); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_REVERTING); + + loading_task = g_task_new (NULL, cancellable, callback, user_data); + + data = loader_data_new (); + g_task_set_task_data (loading_task, data, (GDestroyNotify) loader_data_free); + + data->tab = tab; + data->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (doc), file); + data->line_pos = 0; + data->column_pos = 0; + + launch_loader (loading_task, NULL); +} + +void +_gedit_tab_revert (GeditTab *tab) +{ + if (tab->cancellable != NULL) + { + g_cancellable_cancel (tab->cancellable); + g_object_unref (tab->cancellable); + } + + tab->cancellable = g_cancellable_new (); + + revert_async (tab, + tab->cancellable, + (GAsyncReadyCallback) load_finish, + NULL); +} + +static void +close_printing (GeditTab *tab) +{ + if (tab->print_preview != NULL) + { + gtk_widget_destroy (tab->print_preview); + } + + g_clear_object (&tab->print_job); + g_clear_object (&tab->print_preview); + + /* destroy the info bar */ + set_info_bar (tab, NULL); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); +} + +static void +saver_progress_cb (goffset size, + goffset total_size, + GTask *saving_task) +{ + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + + g_return_if_fail (tab->state == GEDIT_TAB_STATE_SAVING); + + if (should_show_progress_info (&data->timer, size, total_size)) + { + show_saving_info_bar (saving_task); + info_bar_set_progress (tab, size, total_size); + } +} + +static void +save_cb (GtkSourceFileSaver *saver, + GAsyncResult *result, + GTask *saving_task) +{ + GeditTab *tab = g_task_get_source_object (saving_task); + SaverData *data = g_task_get_task_data (saving_task); + GeditDocument *doc = gedit_tab_get_document (tab); + GFile *location = gtk_source_file_saver_get_location (saver); + GError *error = NULL; + + g_return_if_fail (tab->state == GEDIT_TAB_STATE_SAVING); + + gtk_source_file_saver_save_finish (saver, result, &error); + + if (error != NULL) + { + gedit_debug_message (DEBUG_TAB, "File saving error: %s", error->message); + } + + if (data->timer != NULL) + { + g_timer_destroy (data->timer); + data->timer = NULL; + } + + set_info_bar (tab, NULL); + + if (error != NULL) + { + GtkWidget *info_bar; + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING_ERROR); + + if (error->domain == GTK_SOURCE_FILE_SAVER_ERROR && + error->code == GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED) + { + /* This error is recoverable */ + info_bar = GTK_WIDGET (tepl_io_error_info_bar_saving_externally_modified (location)); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (externally_modified_error_info_bar_response), + saving_task); + } + else if (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANT_CREATE_BACKUP) + { + /* This error is recoverable */ + info_bar = GTK_WIDGET (tepl_io_error_info_bar_cant_create_backup (location, error)); + g_return_if_fail (info_bar != NULL); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (cant_create_backup_error_info_bar_response), + saving_task); + } + else if (error->domain == GTK_SOURCE_FILE_SAVER_ERROR && + error->code == GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS) + { + /* If we have any invalid char in the document we must warn the user + * as it can make the document useless if it is saved. + */ + info_bar = GTK_WIDGET (tepl_io_error_info_bar_invalid_characters (location)); + g_return_if_fail (info_bar != NULL); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (invalid_character_info_bar_response), + saving_task); + } + else if (error->domain == GTK_SOURCE_FILE_SAVER_ERROR || + (error->domain == G_IO_ERROR && + error->code != G_IO_ERROR_INVALID_DATA && + error->code != G_IO_ERROR_PARTIAL_INPUT)) + { + /* These errors are _NOT_ recoverable */ + gedit_recent_remove_if_local (location); + + info_bar = gedit_unrecoverable_saving_error_info_bar_new (location, error); + g_return_if_fail (info_bar != NULL); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (unrecoverable_saving_error_info_bar_response), + saving_task); + } + else + { + const GtkSourceEncoding *encoding; + + /* This error is recoverable */ + g_return_if_fail (error->domain == G_CONVERT_ERROR || + error->domain == G_IO_ERROR); + + encoding = gtk_source_file_saver_get_encoding (saver); + + info_bar = gedit_conversion_error_while_saving_info_bar_new (location, encoding); + g_return_if_fail (info_bar != NULL); + + g_signal_connect (info_bar, + "response", + G_CALLBACK (recoverable_saving_error_info_bar_response), + saving_task); + } + + set_info_bar (tab, info_bar); + } + else + { + gedit_recent_add_document (doc); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + + tab->ask_if_externally_modified = TRUE; + + g_signal_emit_by_name (doc, "saved"); + g_task_return_boolean (saving_task, TRUE); + g_object_unref (saving_task); + } + + if (error != NULL) + { + g_error_free (error); + } +} + +static void +launch_saver (GTask *saving_task) +{ + GeditTab *tab = g_task_get_source_object (saving_task); + GeditDocument *doc = gedit_tab_get_document (tab); + SaverData *data = g_task_get_task_data (saving_task); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING); + + g_signal_emit_by_name (doc, "save"); + + if (data->timer != NULL) + { + g_timer_destroy (data->timer); + } + + data->timer = g_timer_new (); + + gtk_source_file_saver_save_async (data->saver, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (saving_task), + (GFileProgressCallback) saver_progress_cb, + saving_task, + NULL, + (GAsyncReadyCallback) save_cb, + saving_task); +} + +/* Gets the initial save flags, when launching a new FileSaver. */ +static GtkSourceFileSaverFlags +get_initial_save_flags (GeditTab *tab, + gboolean auto_save) +{ + GtkSourceFileSaverFlags save_flags; + gboolean create_backup; + + save_flags = tab->save_flags; + + create_backup = g_settings_get_boolean (tab->editor_settings, + GEDIT_SETTINGS_CREATE_BACKUP_COPY); + + /* In case of autosaving, we need to preserve the backup that was produced + * the last time the user "manually" saved the file. So we don't set the + * CREATE_BACKUP flag for an automatic file saving. + */ + if (create_backup && !auto_save) + { + save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP; + } + + return save_flags; +} + +void +_gedit_tab_save_async (GeditTab *tab, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *saving_task; + SaverData *data; + GeditDocument *doc; + GtkSourceFile *file; + GtkSourceFileSaverFlags save_flags; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL || + tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION || + tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); + + /* The Save and Save As window actions are insensitive when the print + * preview is shown, but it's still possible to save several documents + * at once (with the Save All action or when quitting gedit). In that + * case, the print preview is simply closed. Handling correctly the + * document saving when the print preview is shown is more complicated + * and error-prone, it doesn't worth the effort. (the print preview + * would need to be updated when the filename changes, dealing with file + * saving errors is also more complicated, etc). + */ + if (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + close_printing (tab); + } + + doc = gedit_tab_get_document (tab); + g_return_if_fail (!_gedit_document_is_untitled (doc)); + + saving_task = g_task_new (tab, cancellable, callback, user_data); + + data = saver_data_new (); + g_task_set_task_data (saving_task, data, (GDestroyNotify) saver_data_free); + + save_flags = get_initial_save_flags (tab, FALSE); + + if (tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) + { + /* We already told the user about the external modification: + * hide the message bar and set the save flag. + */ + set_info_bar (tab, NULL); + save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME; + } + + file = gedit_document_get_file (doc); + + data->saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (doc), file); + + gtk_source_file_saver_set_flags (data->saver, save_flags); + + launch_saver (saving_task); +} + +gboolean +_gedit_tab_save_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 +auto_save_finished_cb (GeditTab *tab, + GAsyncResult *result, + gpointer user_data) +{ + _gedit_tab_save_finish (tab, result); +} + +static gboolean +gedit_tab_auto_save (GeditTab *tab) +{ + GTask *saving_task; + SaverData *data; + GeditDocument *doc; + GtkSourceFile *file; + GtkSourceFileSaverFlags save_flags; + + gedit_debug (DEBUG_TAB); + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + + g_return_val_if_fail (!_gedit_document_is_untitled (doc), G_SOURCE_REMOVE); + g_return_val_if_fail (!gtk_source_file_is_readonly (file), G_SOURCE_REMOVE); + + if (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc))) + { + gedit_debug_message (DEBUG_TAB, "Document not modified"); + + return G_SOURCE_CONTINUE; + } + + if (tab->state != GEDIT_TAB_STATE_NORMAL) + { + gedit_debug_message (DEBUG_TAB, "Retry after 30 seconds"); + + tab->auto_save_timeout = g_timeout_add_seconds (30, + (GSourceFunc) gedit_tab_auto_save, + tab); + + /* Destroy the old timeout. */ + return G_SOURCE_REMOVE; + } + + /* Set auto_save_timeout to 0 since the timeout is going to be destroyed */ + tab->auto_save_timeout = 0; + + saving_task = g_task_new (tab, + NULL, + (GAsyncReadyCallback) auto_save_finished_cb, + NULL); + + data = saver_data_new (); + g_task_set_task_data (saving_task, data, (GDestroyNotify) saver_data_free); + + data->saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (doc), file); + + save_flags = get_initial_save_flags (tab, TRUE); + gtk_source_file_saver_set_flags (data->saver, save_flags); + + launch_saver (saving_task); + + return G_SOURCE_REMOVE; +} + +/* Call _gedit_tab_save_finish() in @callback, there is no + * _gedit_tab_save_as_finish(). + */ +void +_gedit_tab_save_as_async (GeditTab *tab, + GFile *location, + const GtkSourceEncoding *encoding, + GtkSourceNewlineType newline_type, + GtkSourceCompressionType compression_type, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *saving_task; + SaverData *data; + GeditDocument *doc; + GtkSourceFile *file; + GtkSourceFileSaverFlags save_flags; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL || + tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION || + tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); + g_return_if_fail (G_IS_FILE (location)); + g_return_if_fail (encoding != NULL); + + /* See note at _gedit_tab_save_async(). */ + if (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + close_printing (tab); + } + + saving_task = g_task_new (tab, cancellable, callback, user_data); + + data = saver_data_new (); + g_task_set_task_data (saving_task, data, (GDestroyNotify) saver_data_free); + + doc = gedit_tab_get_document (tab); + + /* reset the save flags, when saving as */ + tab->save_flags = GTK_SOURCE_FILE_SAVER_FLAGS_NONE; + + save_flags = get_initial_save_flags (tab, FALSE); + + if (tab->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) + { + /* We already told the user about the external modification: + * hide the message bar and set the save flag. + */ + set_info_bar (tab, NULL); + save_flags |= GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME; + } + + file = gedit_document_get_file (doc); + + data->saver = gtk_source_file_saver_new_with_target (GTK_SOURCE_BUFFER (doc), + file, + location); + + gtk_source_file_saver_set_encoding (data->saver, encoding); + gtk_source_file_saver_set_newline_type (data->saver, newline_type); + gtk_source_file_saver_set_compression_type (data->saver, compression_type); + gtk_source_file_saver_set_flags (data->saver, save_flags); + + launch_saver (saving_task); +} + +#define GEDIT_PAGE_SETUP_KEY "gedit-page-setup-key" +#define GEDIT_PRINT_SETTINGS_KEY "gedit-print-settings-key" + +static GtkPageSetup * +get_page_setup (GeditTab *tab) +{ + gpointer data; + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + data = g_object_get_data (G_OBJECT (doc), + GEDIT_PAGE_SETUP_KEY); + + if (data == NULL) + { + return _gedit_app_get_default_page_setup (GEDIT_APP (g_application_get_default ())); + } + else + { + return gtk_page_setup_copy (GTK_PAGE_SETUP (data)); + } +} + +static GtkPrintSettings * +get_print_settings (GeditTab *tab) +{ + gpointer data; + GeditDocument *doc; + GtkPrintSettings *settings; + gchar *name; + + doc = gedit_tab_get_document (tab); + + data = g_object_get_data (G_OBJECT (doc), + GEDIT_PRINT_SETTINGS_KEY); + + if (data == NULL) + { + settings = _gedit_app_get_default_print_settings (GEDIT_APP (g_application_get_default ())); + } + else + { + settings = gtk_print_settings_copy (GTK_PRINT_SETTINGS (data)); + } + + /* Be sure the OUTPUT_URI is unset, because otherwise the + * OUTPUT_BASENAME is not taken into account. + */ + gtk_print_settings_set (settings, GTK_PRINT_SETTINGS_OUTPUT_URI, NULL); + + name = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + gtk_print_settings_set (settings, GTK_PRINT_SETTINGS_OUTPUT_BASENAME, name); + g_free (name); + + return settings; +} + +/* FIXME: show the info bar only if the operation will be "long" */ +static void +printing_cb (GeditPrintJob *job, + GeditPrintJobStatus status, + GeditTab *tab) +{ + g_return_if_fail (TEPL_IS_PROGRESS_INFO_BAR (tab->info_bar)); + + gtk_widget_show (tab->info_bar); + + tepl_progress_info_bar_set_text (TEPL_PROGRESS_INFO_BAR (tab->info_bar), + gedit_print_job_get_status_string (job)); + + tepl_progress_info_bar_set_fraction (TEPL_PROGRESS_INFO_BAR (tab->info_bar), + gedit_print_job_get_progress (job)); +} + +static void +store_print_settings (GeditTab *tab, + GeditPrintJob *job) +{ + GeditDocument *doc; + GtkPrintSettings *settings; + GtkPageSetup *page_setup; + + doc = gedit_tab_get_document (tab); + + settings = gedit_print_job_get_print_settings (job); + + /* clear n-copies settings since we do not want to + * persist that one */ + gtk_print_settings_unset (settings, + GTK_PRINT_SETTINGS_N_COPIES); + + /* remember settings for this document */ + g_object_set_data_full (G_OBJECT (doc), + GEDIT_PRINT_SETTINGS_KEY, + g_object_ref (settings), + (GDestroyNotify)g_object_unref); + + /* make them the default */ + _gedit_app_set_default_print_settings (GEDIT_APP (g_application_get_default ()), + settings); + + page_setup = gedit_print_job_get_page_setup (job); + + /* remember page setup for this document */ + g_object_set_data_full (G_OBJECT (doc), + GEDIT_PAGE_SETUP_KEY, + g_object_ref (page_setup), + (GDestroyNotify)g_object_unref); + + /* make it the default */ + _gedit_app_set_default_page_setup (GEDIT_APP (g_application_get_default ()), + page_setup); +} + +static void +done_printing_cb (GeditPrintJob *job, + GeditPrintJobResult result, + GError *error, + GeditTab *tab) +{ + GeditView *view; + + g_return_if_fail (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW || + tab->state == GEDIT_TAB_STATE_PRINTING); + + if (result == GEDIT_PRINT_JOB_RESULT_OK) + { + store_print_settings (tab, job); + } + + /* TODO Show the error in an info bar. */ + if (error != NULL) + { + g_warning ("Printing error: %s", error->message); + g_error_free (error); + error = NULL; + } + + close_printing (tab); + + view = gedit_tab_get_view (tab); + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +show_preview_cb (GeditPrintJob *job, + GeditPrintPreview *preview, + GeditTab *tab) +{ + g_return_if_fail (tab->print_preview == NULL); + + /* destroy the info bar */ + set_info_bar (tab, NULL); + + tab->print_preview = GTK_WIDGET (preview); + g_object_ref_sink (tab->print_preview); + + gtk_box_pack_end (GTK_BOX (tab), tab->print_preview, TRUE, TRUE, 0); + + gtk_widget_show (tab->print_preview); + gtk_widget_grab_focus (tab->print_preview); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); +} + +static void +print_cancelled (GtkWidget *bar, + gint response_id, + GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + if (tab->print_job != NULL) + { + gedit_print_job_cancel (tab->print_job); + } +} + +static void +add_printing_info_bar (GeditTab *tab) +{ + TeplProgressInfoBar *bar; + + bar = tepl_progress_info_bar_new ("document-print", NULL, TRUE); + + g_signal_connect (bar, + "response", + G_CALLBACK (print_cancelled), + tab); + + set_info_bar (tab, GTK_WIDGET (bar)); + + /* hide until we start printing */ + gtk_widget_hide (GTK_WIDGET (bar)); +} + +void +_gedit_tab_print (GeditTab *tab) +{ + GeditView *view; + GtkPageSetup *setup; + GtkPrintSettings *settings; + GtkPrintOperationResult res; + GError *error = NULL; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + + /* FIXME: currently we can have just one printoperation going on at a + * given time, so before starting the print we close the preview. + * Would be nice to handle it properly though. + */ + if (tab->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + close_printing (tab); + } + + g_return_if_fail (tab->print_job == NULL); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL); + + view = gedit_tab_get_view (tab); + + tab->print_job = gedit_print_job_new (TEPL_VIEW (view)); + + add_printing_info_bar (tab); + + g_signal_connect_object (tab->print_job, + "printing", + G_CALLBACK (printing_cb), + tab, + 0); + + g_signal_connect_object (tab->print_job, + "show-preview", + G_CALLBACK (show_preview_cb), + tab, + 0); + + g_signal_connect_object (tab->print_job, + "done", + G_CALLBACK (done_printing_cb), + tab, + 0); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_PRINTING); + + setup = get_page_setup (tab); + settings = get_print_settings (tab); + + res = gedit_print_job_print (tab->print_job, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + setup, + settings, + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), + &error); + + /* TODO: manage res in the correct way */ + if (res == GTK_PRINT_OPERATION_RESULT_ERROR) + { + /* FIXME: go in error state */ + g_warning ("Async print preview failed (%s)", error->message); + g_error_free (error); + + close_printing (tab); + } + + g_object_unref (setup); + g_object_unref (settings); +} + +void +_gedit_tab_mark_for_closing (GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (tab->state == GEDIT_TAB_STATE_NORMAL); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_CLOSING); +} + +gboolean +_gedit_tab_get_can_close (GeditTab *tab) +{ + GeditDocument *doc; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE); + + /* if we are loading or reverting, the tab can be closed */ + if (tab->state == GEDIT_TAB_STATE_LOADING || + tab->state == GEDIT_TAB_STATE_LOADING_ERROR || + tab->state == GEDIT_TAB_STATE_REVERTING || + tab->state == GEDIT_TAB_STATE_REVERTING_ERROR) /* CHECK: I'm not sure this is the right behavior for REVERTING ERROR */ + { + return TRUE; + } + + /* Do not close tab with saving errors */ + if (tab->state == GEDIT_TAB_STATE_SAVING_ERROR) + { + return FALSE; + } + + doc = gedit_tab_get_document (tab); + + if (_gedit_document_needs_saving (doc)) + { + return FALSE; + } + + return TRUE; +} + +/** + * gedit_tab_get_auto_save_enabled: + * @tab: a #GeditTab + * + * Gets the current state for the autosave feature + * + * Return value: %TRUE if the autosave is enabled, else %FALSE + **/ +gboolean +gedit_tab_get_auto_save_enabled (GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE); + + return tab->auto_save; +} + +/** + * gedit_tab_set_auto_save_enabled: + * @tab: a #GeditTab + * @enable: enable (%TRUE) or disable (%FALSE) auto save + * + * Enables or disables the autosave feature. It does not install an + * autosave timeout if the document is new or is read-only + **/ +void +gedit_tab_set_auto_save_enabled (GeditTab *tab, + gboolean enable) +{ + gedit_debug (DEBUG_TAB); + + g_return_if_fail (GEDIT_IS_TAB (tab)); + + enable = enable != FALSE; + + if (tab->auto_save != enable) + { + tab->auto_save = enable; + update_auto_save_timeout (tab); + return; + } +} + +/** + * gedit_tab_get_auto_save_interval: + * @tab: a #GeditTab + * + * Gets the current interval for the autosaves + * + * Return value: the value of the autosave + **/ +gint +gedit_tab_get_auto_save_interval (GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + g_return_val_if_fail (GEDIT_IS_TAB (tab), 0); + + return tab->auto_save_interval; +} + +/** + * gedit_tab_set_auto_save_interval: + * @tab: a #GeditTab + * @interval: the new interval + * + * Sets the interval for the autosave feature. + */ +void +gedit_tab_set_auto_save_interval (GeditTab *tab, + gint interval) +{ + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (interval > 0); + + gedit_debug (DEBUG_TAB); + + if (tab->auto_save_interval != interval) + { + tab->auto_save_interval = interval; + remove_auto_save_timeout (tab); + update_auto_save_timeout (tab); + } +} + +void +gedit_tab_set_info_bar (GeditTab *tab, + GtkWidget *info_bar) +{ + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (info_bar == NULL || GTK_IS_WIDGET (info_bar)); + + /* FIXME: this can cause problems with the tab state machine */ + set_info_bar (tab, info_bar); +} + +GeditViewFrame * +_gedit_tab_get_view_frame (GeditTab *tab) +{ + return tab->frame; +} + +/* ex:set ts=8 noet: */ -- cgit v1.2.3