/* * xed-tab.c * This file is part of xed * * Copyright (C) 2005 - Paolo Maggi * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * Modified by the xed Team, 2005. See the AUTHORS file for a * list of people on the xed Team. * See the ChangeLog files for a list of changes. */ #include #include #include #include #include "xed-app.h" #include "xed-notebook.h" #include "xed-tab.h" #include "xed-utils.h" #include "xed-io-error-info-bar.h" #include "xed-print-job.h" #include "xed-print-preview.h" #include "xed-progress-info-bar.h" #include "xed-debug.h" #include "xed-document.h" #include "xed-document-private.h" #include "xed-enum-types.h" #include "xed-settings.h" #include "xed-view-frame.h" #define XED_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), XED_TYPE_TAB, XedTabPrivate)) #define XED_TAB_KEY "XED_TAB_KEY" struct _XedTabPrivate { GSettings *editor; XedTabState state; XedViewFrame *frame; GtkWidget *info_bar; GtkWidget *print_preview; XedPrintJob *print_job; GTask *task_saver; GtkSourceFileSaverFlags save_flags; /* tmp data for loading */ GtkSourceFileLoader *loader; GCancellable *cancellable; gint tmp_line_pos; guint idle_scroll; GTimer *timer; guint times_called; guint auto_save_interval; guint auto_save_timeout; gint editable : 1; gint auto_save : 1; gint ask_if_externally_modified : 1; /*tmp data for loading */ guint user_requested_encoding : 1; }; typedef struct _SaverData SaverData; struct _SaverData { GtkSourceFileSaver *saver; /* 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; }; G_DEFINE_TYPE(XedTab, xed_tab, GTK_TYPE_BOX) enum { PROP_0, PROP_NAME, PROP_STATE, PROP_AUTO_SAVE, PROP_AUTO_SAVE_INTERVAL, PROP_CAN_CLOSE }; static gboolean xed_tab_auto_save (XedTab *tab); static void load (XedTab *tab, const GtkSourceEncoding *encoding, gint line_pos); static void save (XedTab *tab); 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); } g_slice_free (SaverData, data); } } static void install_auto_save_timeout (XedTab *tab) { if (tab->priv->auto_save_timeout == 0) { g_return_if_fail (tab->priv->auto_save_interval > 0); tab->priv->auto_save_timeout = g_timeout_add_seconds (tab->priv->auto_save_interval * 60, (GSourceFunc) xed_tab_auto_save, tab); } } static void remove_auto_save_timeout (XedTab *tab) { xed_debug (DEBUG_TAB); if (tab->priv->auto_save_timeout > 0) { g_source_remove (tab->priv->auto_save_timeout); tab->priv->auto_save_timeout = 0; } } static void update_auto_save_timeout (XedTab *tab) { gboolean good_state; XedDocument *doc; xed_debug (DEBUG_TAB); good_state = (tab->priv->state == XED_TAB_STATE_NORMAL || tab->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW); doc = xed_tab_get_document (tab); if (good_state && tab->priv->auto_save && !xed_document_is_untitled (doc) && !xed_document_get_readonly (doc)) { install_auto_save_timeout (tab); } else { remove_auto_save_timeout (tab); } } static void xed_tab_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { XedTab *tab = XED_TAB (object); switch (prop_id) { case PROP_NAME: g_value_take_string (value, _xed_tab_get_name (tab)); break; case PROP_STATE: g_value_set_enum (value, xed_tab_get_state (tab)); break; case PROP_AUTO_SAVE: g_value_set_boolean (value, xed_tab_get_auto_save_enabled (tab)); break; case PROP_AUTO_SAVE_INTERVAL: g_value_set_int (value, xed_tab_get_auto_save_interval (tab)); break; case PROP_CAN_CLOSE: g_value_set_boolean (value, _xed_tab_get_can_close (tab)); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void xed_tab_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { XedTab *tab = XED_TAB (object); switch (prop_id) { case PROP_AUTO_SAVE: xed_tab_set_auto_save_enabled (tab, g_value_get_boolean (value)); break; case PROP_AUTO_SAVE_INTERVAL: xed_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 clear_loading (XedTab *tab) { g_clear_object (&tab->priv->loader); g_clear_object (&tab->priv->cancellable); } static void xed_tab_dispose (GObject *object) { XedTab *tab = XED_TAB (object); g_clear_object (&tab->priv->task_saver); clear_loading (tab); GAction *action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()), "print-now"); g_signal_handlers_disconnect_by_data (action, tab); G_OBJECT_CLASS (xed_tab_parent_class)->dispose (object); } static void xed_tab_finalize (GObject *object) { XedTab *tab = XED_TAB (object); g_clear_object (&tab->priv->editor); if (tab->priv->timer != NULL) { g_timer_destroy (tab->priv->timer); } remove_auto_save_timeout (tab); if (tab->priv->idle_scroll != 0) { g_source_remove (tab->priv->idle_scroll); tab->priv->idle_scroll = 0; } G_OBJECT_CLASS (xed_tab_parent_class)->finalize (object); } static void xed_tab_class_init (XedTabClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = xed_tab_dispose; object_class->finalize = xed_tab_finalize; object_class->get_property = xed_tab_get_property; object_class->set_property = xed_tab_set_property; g_object_class_install_property (object_class, PROP_NAME, g_param_spec_string ("name", "Name", "The tab's name", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_STATE, g_param_spec_enum ("state", "State", "The tab's state", XED_TYPE_TAB_STATE, XED_TAB_STATE_NORMAL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_AUTO_SAVE, g_param_spec_boolean ("autosave", "Autosave", "Autosave feature", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, 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)); g_object_class_install_property (object_class, PROP_CAN_CLOSE, g_param_spec_boolean ("can-close", "Can close", "Wheather the tab can be closed", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_type_class_add_private (object_class, sizeof (XedTabPrivate)); } /** * xed_tab_get_state: * @tab: a #XedTab * * Gets the #XedTabState of @tab. * * Returns: the #XedTabState of @tab */ XedTabState xed_tab_get_state (XedTab *tab) { g_return_val_if_fail (XED_IS_TAB (tab), XED_TAB_STATE_NORMAL); return tab->priv->state; } static void set_cursor_according_to_state (GtkTextView *view, XedTabState state) { GdkCursor *cursor; GdkWindow *text_window; GdkWindow *left_window; 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 == XED_TAB_STATE_LOADING) || (state == XED_TAB_STATE_REVERTING) || (state == XED_TAB_STATE_SAVING) || (state == XED_TAB_STATE_PRINTING) || (state == XED_TAB_STATE_PRINT_PREVIEWING) || (state == XED_TAB_STATE_CLOSING)) { cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (view)), GDK_WATCH); if (text_window != NULL) { gdk_window_set_cursor (text_window, cursor); } if (left_window != NULL) { gdk_window_set_cursor (left_window, cursor); } g_object_unref (cursor); } else { cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (view)), GDK_XTERM); if (text_window != NULL) { gdk_window_set_cursor (text_window, cursor); } if (left_window != NULL) { gdk_window_set_cursor (left_window, NULL); } g_object_unref (cursor); } } static void view_realized (GtkTextView *view, XedTab *tab) { set_cursor_according_to_state (view, tab->priv->state); } static void set_view_properties_according_to_state (XedTab *tab, XedTabState state) { XedView *view; gboolean val; gboolean hl_current_line; hl_current_line = g_settings_get_boolean (tab->priv->editor, XED_SETTINGS_HIGHLIGHT_CURRENT_LINE); view = xed_view_frame_get_view (tab->priv->frame); val = ((state == XED_TAB_STATE_NORMAL) && (tab->priv->print_preview == NULL) && tab->priv->editable); gtk_text_view_set_editable (GTK_TEXT_VIEW (view), val); val = ((state != XED_TAB_STATE_LOADING) && (state != XED_TAB_STATE_CLOSING)); gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), val); val = ((state != XED_TAB_STATE_LOADING) && (state != XED_TAB_STATE_CLOSING) && (hl_current_line)); gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (view), val); } static void xed_tab_set_state (XedTab *tab, XedTabState state) { g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail ((state >= 0) && (state < XED_TAB_NUM_OF_STATES)); if (tab->priv->state == state) { return; } tab->priv->state = state; set_view_properties_according_to_state (tab, state); if ((state == XED_TAB_STATE_LOADING_ERROR) || /* FIXME: add other states if needed */ (state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW)) { gtk_widget_hide (GTK_WIDGET (tab->priv->frame)); } else { if (tab->priv->print_preview == NULL) { gtk_widget_show (GTK_WIDGET (tab->priv->frame)); } } set_cursor_according_to_state (GTK_TEXT_VIEW (xed_view_frame_get_view (tab->priv->frame)), state); update_auto_save_timeout (tab); g_object_notify (G_OBJECT (tab), "state"); g_object_notify (G_OBJECT (tab), "can-close"); } static void document_location_notify_handler (GtkSourceFile *file, GParamSpec *pspec, XedTab *tab) { xed_debug (DEBUG_TAB); /* Notify the change in the location */ g_object_notify (G_OBJECT (tab), "name"); } static void document_shortname_notify_handler (XedDocument *document, GParamSpec *pspec, XedTab *tab) { xed_debug (DEBUG_TAB); /* Notify the change in the shortname */ g_object_notify (G_OBJECT (tab), "name"); } static void document_modified_changed (GtkTextBuffer *document, XedTab *tab) { g_object_notify (G_OBJECT (tab), "name"); g_object_notify (G_OBJECT (tab), "can-close"); } static void set_info_bar (XedTab *tab, GtkWidget *info_bar) { if (tab->priv->info_bar == info_bar) { return; } if (tab->priv->info_bar != NULL) { gtk_widget_destroy (tab->priv->info_bar); } tab->priv->info_bar = info_bar; if (info_bar == NULL) { return; } gtk_box_pack_start (GTK_BOX (tab), tab->priv->info_bar, FALSE, FALSE, 0); g_object_add_weak_pointer (G_OBJECT (tab->priv->info_bar), (gpointer *)&tab->priv->info_bar); } static void remove_tab (XedTab *tab) { XedNotebook *notebook; notebook = XED_NOTEBOOK (gtk_widget_get_parent (GTK_WIDGET (tab))); xed_notebook_remove_tab (notebook, tab); } static void io_loading_error_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { XedView *view; GFile *location; const GtkSourceEncoding *encoding; g_return_if_fail (tab->priv->loader != NULL); view = xed_tab_get_view (tab); location = gtk_source_file_loader_get_location (tab->priv->loader); switch (response_id) { case GTK_RESPONSE_OK: encoding = xed_conversion_error_info_bar_get_encoding (GTK_WIDGET (info_bar)); set_info_bar (tab, NULL); xed_tab_set_state (tab, XED_TAB_STATE_LOADING); load (tab, encoding, tab->priv->tmp_line_pos); break; case GTK_RESPONSE_YES: /* This means that we want to edit the document anyway */ tab->priv->editable = TRUE; gtk_text_view_set_editable (GTK_TEXT_VIEW (view), TRUE); set_info_bar (tab, NULL); clear_loading (tab); break; default: _xed_recent_remove (XED_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), location); remove_tab (tab); break; } } static void file_already_open_warning_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { XedView *view; view = xed_tab_get_view (tab); if (response_id == GTK_RESPONSE_YES) { tab->priv->editable = TRUE; gtk_text_view_set_editable (GTK_TEXT_VIEW (view), TRUE); } gtk_widget_destroy (info_bar); gtk_widget_grab_focus (GTK_WIDGET (view)); } static void load_cancelled (GtkWidget *area, gint response_id, XedTab *tab) { g_return_if_fail (XED_IS_PROGRESS_INFO_BAR (tab->priv->info_bar)); g_return_if_fail (G_IS_CANCELLABLE (tab->priv->cancellable)); g_cancellable_cancel (tab->priv->cancellable); } static void unrecoverable_reverting_error_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { XedView *view; xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); set_info_bar (tab, NULL); clear_loading (tab); view = xed_tab_get_view (tab); gtk_widget_grab_focus (GTK_WIDGET (view)); } #define MAX_MSG_LENGTH 100 static void show_loading_info_bar (XedTab *tab) { GtkWidget *bar; XedDocument *doc = NULL; gchar *name; gchar *dirname = NULL; gchar *msg = NULL; gchar *name_markup; gchar *dirname_markup; gint len; if (tab->priv->info_bar != NULL) { return; } xed_debug (DEBUG_TAB); doc = xed_tab_get_document (tab); g_return_if_fail (doc != NULL); name = xed_document_get_short_name_for_display (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 = xed_utils_str_middle_truncate (name, MAX_MSG_LENGTH); g_free (name); name = str; } else { GtkSourceFile *file = xed_document_get_file (doc); GFile *location = gtk_source_file_get_location (file); if (location != NULL) { gchar *str = xed_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 = xed_utils_str_middle_truncate (str, MAX (20, MAX_MSG_LENGTH - len)); g_free (str); } } name_markup = g_markup_printf_escaped ("%s", name); if (tab->priv->state == XED_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 = xed_progress_info_bar_new ("document-revert-symbolic", 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 = xed_progress_info_bar_new ("document-open-symbolic", msg, TRUE); } g_signal_connect (bar, "response", G_CALLBACK (load_cancelled), tab); gtk_widget_show (bar); set_info_bar (tab, bar); g_free (msg); g_free (name); g_free (name_markup); g_free (dirname); } static void show_saving_info_bar (XedTab *tab) { GtkWidget *bar; XedDocument *doc = NULL; gchar *short_name; gchar *from; gchar *to = NULL; gchar *from_markup; gchar *to_markup; gchar *msg = NULL; gint len; g_return_if_fail (tab->priv->task_saver != NULL); if (tab->priv->info_bar != NULL) { return; } xed_debug (DEBUG_TAB); doc = xed_tab_get_document (tab); g_return_if_fail (doc != NULL); short_name = xed_document_get_short_name_for_display (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 = xed_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 (tab->priv->task_saver); location = gtk_source_file_saver_get_location (data->saver); from = short_name; to = g_file_get_parse_name (location); str = xed_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 = xed_progress_info_bar_new ("document-save-symbolic", msg, FALSE); gtk_widget_show (bar); set_info_bar (tab, bar); g_free (msg); g_free (to); g_free (from); g_free (from_markup); } static void info_bar_set_progress (XedTab *tab, goffset size, goffset total_size) { if (tab->priv->info_bar == NULL) { return; } xed_debug_message (DEBUG_TAB, "%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, size, total_size); g_return_if_fail (XED_IS_PROGRESS_INFO_BAR (tab->priv->info_bar)); if (total_size == 0) { if (size != 0) xed_progress_info_bar_pulse (XED_PROGRESS_INFO_BAR (tab->priv->info_bar)); else xed_progress_info_bar_set_fraction (XED_PROGRESS_INFO_BAR (tab->priv->info_bar), 0); } else { gdouble frac; frac = (gdouble)size / (gdouble)total_size; xed_progress_info_bar_set_fraction (XED_PROGRESS_INFO_BAR (tab->priv->info_bar), frac); } } static gboolean scroll_to_cursor (XedTab *tab) { XedView *view; view = xed_tab_get_view (tab); xed_view_scroll_to_cursor (view); tab->priv->idle_scroll = 0; return G_SOURCE_REMOVE; } static void unrecoverable_saving_error_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { XedView *view; if (tab->priv->print_preview != NULL) { xed_tab_set_state (tab, XED_TAB_STATE_SHOWING_PRINT_PREVIEW); } else { xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); } set_info_bar (tab, NULL); g_return_if_fail (tab->priv->task_saver != NULL); g_task_return_boolean (tab->priv->task_saver, FALSE); view = xed_tab_get_view (tab); gtk_widget_grab_focus (GTK_WIDGET (view)); } /* Sets the save flags after an info bar response. */ static void response_set_save_flags (XedTab *tab, GtkSourceFileSaverFlags save_flags) { SaverData *data; gboolean create_backup; data = g_task_get_task_data (tab->priv->task_saver); create_backup = g_settings_get_boolean (tab->priv->editor, XED_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, XedTab *tab) { if (response_id == GTK_RESPONSE_YES) { SaverData *data; GtkSourceFileSaverFlags save_flags; set_info_bar (tab, NULL); g_return_if_fail (tab->priv->task_saver != NULL); data = g_task_get_task_data (tab->priv->task_saver); /* Don't bug the user again with this... */ tab->priv->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 (tab, save_flags); /* Force saving */ save (tab); } else { unrecoverable_saving_error_info_bar_response (info_bar, response_id, tab); } } static void no_backup_error_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { if (response_id == GTK_RESPONSE_YES) { SaverData *data; GtkSourceFileSaverFlags save_flags; set_info_bar (tab, NULL); g_return_if_fail (tab->priv->task_saver != NULL); data = g_task_get_task_data (tab->priv->task_saver); data->force_no_backup = TRUE; save_flags = gtk_source_file_saver_get_flags (data->saver); response_set_save_flags (tab, save_flags); /* Force saving */ save (tab); } else { unrecoverable_saving_error_info_bar_response (info_bar, response_id, tab); } } static void externally_modified_error_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { if (response_id == GTK_RESPONSE_YES) { SaverData *data; GtkSourceFileSaverFlags save_flags; set_info_bar (tab, NULL); g_return_if_fail (tab->priv->task_saver != NULL); data = g_task_get_task_data (tab->priv->task_saver); /* ignore_modification_time should not be persisted in save * flags across saves (i.e. priv->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 (tab, save_flags); /* Force saving */ save (tab); } else { unrecoverable_saving_error_info_bar_response (info_bar, response_id, tab); } } static void recoverable_saving_error_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { if (response_id == GTK_RESPONSE_OK) { SaverData *data; const GtkSourceEncoding *encoding; set_info_bar (tab, NULL); g_return_if_fail (tab->priv->task_saver != NULL); data = g_task_get_task_data (tab->priv->task_saver); encoding = xed_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); save (tab); } else { unrecoverable_saving_error_info_bar_response (info_bar, response_id, tab); } } static void externally_modified_notification_info_bar_response (GtkWidget *info_bar, gint response_id, XedTab *tab) { XedView *view; set_info_bar (tab, NULL); view = xed_tab_get_view (tab); if (response_id == GTK_RESPONSE_OK) { _xed_tab_revert (tab); } else { tab->priv->ask_if_externally_modified = FALSE; /* go back to normal state */ xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); } gtk_widget_grab_focus (GTK_WIDGET (view)); } static void display_externally_modified_notification (XedTab *tab) { GtkWidget *info_bar; XedDocument *doc; GtkSourceFile *file; GFile *location; gboolean document_modified; doc = xed_tab_get_document (tab); g_return_if_fail (XED_IS_DOCUMENT (doc)); file = xed_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 = xed_externally_modified_info_bar_new (location, document_modified); tab->priv->info_bar = NULL; set_info_bar (tab, info_bar); gtk_widget_show (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, XedTab *tab) { XedDocument *doc; GtkSourceFile *file; g_return_val_if_fail (XED_IS_TAB (tab), FALSE); /* we try to detect file changes only in the normal state */ if (tab->priv->state != XED_TAB_STATE_NORMAL) { return GDK_EVENT_PROPAGATE; } /* we already asked, don't bug the user again */ if (!tab->priv->ask_if_externally_modified) { return GDK_EVENT_PROPAGATE; } doc = xed_tab_get_document (tab); file = xed_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)) { xed_tab_set_state (tab, XED_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION); display_externally_modified_notification (tab); } } return GDK_EVENT_PROPAGATE; } static void print_now_action_cb (XedTab *tab) { g_return_if_fail (XED_IS_TAB (tab)); _xed_tab_print (tab, FALSE); } static void xed_tab_init (XedTab *tab) { gboolean auto_save; guint auto_save_interval; XedDocument *doc; XedView *view; GtkSourceFile *file; tab->priv = XED_TAB_GET_PRIVATE (tab); tab->priv->editor = g_settings_new ("org.x.editor.preferences.editor"); tab->priv->state = XED_TAB_STATE_NORMAL; tab->priv->editable = TRUE; tab->priv->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->priv->editor, XED_SETTINGS_AUTO_SAVE); auto_save_interval = g_settings_get_uint (tab->priv->editor, XED_SETTINGS_AUTO_SAVE_INTERVAL); tab->priv->auto_save = auto_save; tab->priv->auto_save = (tab->priv->auto_save != FALSE); tab->priv->auto_save_interval = auto_save_interval; /* Create the frame */ tab->priv->frame = xed_view_frame_new (); gtk_widget_show (GTK_WIDGET (tab->priv->frame)); gtk_box_pack_end (GTK_BOX (tab), GTK_WIDGET (tab->priv->frame), TRUE, TRUE, 0); doc = xed_view_frame_get_document (tab->priv->frame); g_object_set_data (G_OBJECT (doc), XED_TAB_KEY, tab); view = xed_view_frame_get_view (tab->priv->frame); g_object_set_data (G_OBJECT (view), XED_TAB_KEY, tab); file = xed_document_get_file (doc); g_signal_connect_object (file, "notify::location", G_CALLBACK (document_location_notify_handler), tab, 0); g_signal_connect (doc, "notify::shortname", G_CALLBACK (document_shortname_notify_handler), tab); g_signal_connect (doc, "modified_changed", G_CALLBACK (document_modified_changed), 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); GAction *action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()), "print-now"); g_signal_connect_swapped (action, "activate", G_CALLBACK (print_now_action_cb), tab); } GtkWidget * _xed_tab_new (void) { return GTK_WIDGET (g_object_new (XED_TYPE_TAB, NULL)); } /* Whether create is TRUE, creates a new empty document if location does not refer to an existing file */ GtkWidget * _xed_tab_new_from_location (GFile *location, const GtkSourceEncoding *encoding, gint line_pos, gboolean create) { XedTab *tab; g_return_val_if_fail (G_IS_FILE (location), NULL); tab = XED_TAB (_xed_tab_new ()); _xed_tab_load (tab, location, encoding, line_pos, create); return GTK_WIDGET (tab); } GtkWidget * _xed_tab_new_from_stream (GInputStream *stream, const GtkSourceEncoding *encoding, gint line_pos) { GtkWidget *tab; g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL); tab = _xed_tab_new (); _xed_tab_load_stream (XED_TAB (tab), stream, encoding, line_pos); return tab; } /** * xed_tab_get_view: * @tab: a #XedTab * * Gets the #XedView inside @tab. * * Returns: (transfer none): the #XedView inside @tab */ XedView * xed_tab_get_view (XedTab *tab) { g_return_val_if_fail (XED_IS_TAB (tab), NULL); return xed_view_frame_get_view (tab->priv->frame); } /** * xed_tab_get_document: * @tab: a #XedTab * * Gets the #XedDocument associated to @tab. * * Returns: (transfer none): the #XedDocument associated to @tab */ XedDocument * xed_tab_get_document (XedTab *tab) { g_return_val_if_fail (XED_IS_TAB (tab), NULL); return xed_view_frame_get_document (tab->priv->frame); } #define MAX_DOC_NAME_LENGTH 40 gchar * _xed_tab_get_name (XedTab *tab) { XedDocument *doc; gchar *name; gchar *docname; gchar *tab_name; g_return_val_if_fail (XED_IS_TAB (tab), NULL); doc = xed_tab_get_document (tab); name = xed_document_get_short_name_for_display (doc); /* Truncate the name so it doesn't get insanely wide. */ docname = xed_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 { #if 0 if (xed_document_get_readonly (doc)) { tab_name = g_strdup_printf ("%s [%s]", docname, /*Read only*/ _("RO")); } else { tab_name = g_strdup_printf ("%s", docname); } #endif tab_name = g_strdup (docname); } g_free (docname); g_free (name); return tab_name; } gchar * _xed_tab_get_tooltips (XedTab *tab) { XedDocument *doc; gchar *tip; gchar *uri; gchar *ruri; gchar *ruri_markup; g_return_val_if_fail (XED_IS_TAB (tab), NULL); doc = xed_tab_get_document (tab); uri = xed_document_get_uri_for_display (doc); g_return_val_if_fail (uri != NULL, NULL); ruri = xed_utils_replace_home_dir_with_tilde (uri); g_free (uri); ruri_markup = g_markup_printf_escaped ("%s", ruri); switch (tab->priv->state) { gchar *content_type; gchar *mime_type; gchar *content_description; gchar *content_full_description; gchar *encoding; GtkSourceFile *file; const GtkSourceEncoding *enc; case XED_TAB_STATE_LOADING_ERROR: tip = g_strdup_printf (_("Error opening file %s"), ruri_markup); break; case XED_TAB_STATE_REVERTING_ERROR: tip = g_strdup_printf (_("Error reverting file %s"), ruri_markup); break; case XED_TAB_STATE_SAVING_ERROR: tip = g_strdup_printf (_("Error saving file %s"), ruri_markup); break; default: content_type = xed_document_get_content_type (doc); mime_type = xed_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 = xed_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:"), ruri, _("MIME Type:"), content_full_description, _("Encoding:"), encoding); g_free (encoding); g_free (content_full_description); break; } g_free (ruri); g_free (ruri_markup); return tip; } static GdkPixbuf * get_icon (GtkIconTheme *theme, GFile *location, gint size) { GdkPixbuf *pixbuf; GtkIconInfo *icon_info; GFileInfo *info; GIcon *gicon; if (location == NULL) { return gtk_icon_theme_load_icon (theme, "text-x-generic", size, 0, NULL); } /* FIXME: Doing a sync stat is bad, this should be fixed */ info = g_file_query_info (location, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info == NULL) { return gtk_icon_theme_load_icon (theme, "text-x-generic", size, 0, NULL); } gicon = g_file_info_get_icon (info); if (gicon == NULL) { g_object_unref (info); return gtk_icon_theme_load_icon (theme, "text-x-generic", size, 0, NULL); } icon_info = gtk_icon_theme_lookup_by_gicon (theme, gicon, size, 0); g_object_unref (info); if (icon_info == NULL) { return gtk_icon_theme_load_icon (theme, "text-x-generic", size, 0, NULL); } pixbuf = gtk_icon_info_load_icon (icon_info, NULL); g_object_unref (icon_info); if (pixbuf == NULL) { return gtk_icon_theme_load_icon (theme, "text-x-generic", size, 0, NULL); } return pixbuf; } /* FIXME: add support for theme changed. I think it should be as easy as call g_object_notify (tab, "name") when the icon theme changes */ GdkPixbuf * _xed_tab_get_icon (XedTab *tab) { GdkScreen *screen; GtkIconTheme *theme; gint icon_size; const gchar *icon_name; GdkPixbuf *pixbuf = NULL; g_return_val_if_fail (XED_IS_TAB (tab), NULL); 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); switch (tab->priv->state) { case XED_TAB_STATE_LOADING: icon_name = "document-open-symbolic"; break; case XED_TAB_STATE_REVERTING: icon_name = "document-revert-symbolic"; break; case XED_TAB_STATE_SAVING: icon_name = "document-save-symbolic"; break; case XED_TAB_STATE_PRINTING: icon_name = "printer-printing-symbolic"; break; case XED_TAB_STATE_PRINT_PREVIEWING: case XED_TAB_STATE_SHOWING_PRINT_PREVIEW: icon_name = "printer-symbolic"; break; case XED_TAB_STATE_LOADING_ERROR: case XED_TAB_STATE_REVERTING_ERROR: case XED_TAB_STATE_SAVING_ERROR: case XED_TAB_STATE_GENERIC_ERROR: icon_name = "dialog-error-symbolic"; break; case XED_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION: icon_name = "dialog-warning-symbolic"; break; default: icon_name = NULL; } if (icon_name != NULL) { pixbuf = gtk_icon_theme_load_icon (theme, icon_name, icon_size, 0, NULL); } else { GFile *location; XedDocument *doc; doc = xed_tab_get_document (tab); location = xed_document_get_location (doc); pixbuf = get_icon (theme, location, icon_size); } return pixbuf; } /** * xed_tab_get_from_document: * @doc: a #XedDocument * * Gets the #XedTab associated with @doc. * * Returns: (transfer none): the #XedTab associated with @doc */ XedTab * xed_tab_get_from_document (XedDocument *doc) { gpointer res; g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL); res = g_object_get_data (G_OBJECT (doc), XED_TAB_KEY); return (res != NULL) ? XED_TAB (res) : NULL; } static void loader_progress_cb (goffset size, goffset total_size, XedTab *tab) { gdouble elapsed_time; gdouble total_time; gdouble remaining_time; g_return_if_fail (tab->priv->state == XED_TAB_STATE_LOADING || tab->priv->state == XED_TAB_STATE_REVERTING); if (tab->priv->timer == NULL) { tab->priv->timer = g_timer_new (); } elapsed_time = g_timer_elapsed (tab->priv->timer, NULL); /* 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) { show_loading_info_bar (tab); } info_bar_set_progress (tab, size, total_size); } static void goto_line (XedTab *tab) { XedDocument *doc = xed_tab_get_document (tab); GtkTextIter iter; /* Move the cursor at the requested line if any. */ if (tab->priv->tmp_line_pos > 0) { xed_document_goto_line_offset (doc, tab->priv->tmp_line_pos - 1, 0); return; } /* If enabled, move to the position stored in the metadata. */ if (g_settings_get_boolean (tab->priv->editor, XED_SETTINGS_RESTORE_CURSOR_POSITION)) { gchar *pos; gint offset; pos = xed_document_get_metadata (doc, XED_METADATA_ATTRIBUTE_POSITION); offset = pos != NULL ? atoi (pos) : 0; g_free (pos); gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &iter, MAX (0, offset)); /* make sure it's a valid position, if the file * changed we may have ended up in the middle of * a utf8 character cluster */ if (!gtk_text_iter_is_cursor_position (&iter)) { gtk_text_iter_set_line_offset (&iter, 0); } } /* Otherwise to the top. */ else { gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (doc), &iter); } gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter); } static void load_cb (GtkSourceFileLoader *loader, GAsyncResult *result, XedTab *tab) { XedDocument *doc = xed_tab_get_document (tab); GFile *location = gtk_source_file_loader_get_location (loader); gboolean create_named_new_doc; GError *error = NULL; g_return_if_fail (tab->priv->state == XED_TAB_STATE_LOADING || tab->priv->state == XED_TAB_STATE_REVERTING); gtk_source_file_loader_load_finish (loader, result, &error); if (error != NULL) { xed_debug_message (DEBUG_TAB, "File loading error: %s", error->message); } if (tab->priv->timer != NULL) { g_timer_destroy (tab->priv->timer); tab->priv->timer = NULL; } set_info_bar (tab, NULL); /* Load was successful. */ if (error == NULL || (error->domain == GTK_SOURCE_FILE_LOADER_ERROR && error->code == GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK)) { if (tab->priv->user_requested_encoding) { const GtkSourceEncoding *encoding = gtk_source_file_loader_get_encoding (loader); const gchar *charset = gtk_source_encoding_get_charset (encoding); xed_document_set_metadata (doc, XED_METADATA_ATTRIBUTE_ENCODING, charset, NULL); } goto_line (tab); } /* Special case creating a named new doc. */ create_named_new_doc = (_xed_document_get_create (doc) && error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND && g_file_has_uri_scheme (location, "file")); if (create_named_new_doc) { g_error_free (error); error = NULL; } /* If the error is CONVERSION FALLBACK don't treat it as a normal error. */ if (error != NULL && (error->domain != GTK_SOURCE_FILE_LOADER_ERROR || error->code != GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK)) { if (tab->priv->state == XED_TAB_STATE_LOADING) { xed_tab_set_state (tab, XED_TAB_STATE_LOADING_ERROR); } else { xed_tab_set_state (tab, XED_TAB_STATE_REVERTING_ERROR); } if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) { remove_tab (tab); } else { GtkWidget *info_bar; if (location != NULL) { _xed_recent_remove (XED_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), location); } if (tab->priv->state == XED_TAB_STATE_LOADING_ERROR) { const GtkSourceEncoding *encoding; encoding = gtk_source_file_loader_get_encoding (loader); info_bar = xed_io_loading_error_info_bar_new (location, encoding, error); g_signal_connect (info_bar, "response", G_CALLBACK (io_loading_error_info_bar_response), tab); } else { g_return_if_fail (tab->priv->state == XED_TAB_STATE_REVERTING_ERROR); info_bar = xed_unrecoverable_reverting_error_info_bar_new (location, error); g_signal_connect (info_bar, "response", G_CALLBACK (unrecoverable_reverting_error_info_bar_response), tab); } set_info_bar (tab, info_bar); gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); gtk_widget_show (info_bar); } goto end; } if (location != NULL && !create_named_new_doc) { gchar *mime = xed_document_get_mime_type (doc); _xed_recent_add (XED_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), location, mime); g_free (mime); } if (error != NULL && error->domain == GTK_SOURCE_FILE_LOADER_ERROR && error->code == 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. */ tab->priv->editable = FALSE; encoding = gtk_source_file_loader_get_encoding (loader); info_bar = xed_io_loading_error_info_bar_new (location, encoding, error); g_signal_connect (info_bar, "response", G_CALLBACK (io_loading_error_info_bar_response), tab); set_info_bar (tab, info_bar); gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); gtk_widget_show (info_bar); } /* 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 (tab->priv->idle_scroll == 0) { tab->priv->idle_scroll = g_idle_add ((GSourceFunc)scroll_to_cursor, tab); } /* If the document is readonly we don't care how many times the document * is opened. */ if (!xed_document_get_readonly (doc)) { GList *all_documents; GList *l; all_documents = xed_app_get_documents (XED_APP (g_application_get_default ())); for (l = all_documents; l != NULL; l = g_list_next (l)) { XedDocument *cur_doc = l->data; if (cur_doc != doc) { GtkSourceFile *cur_file = xed_document_get_file (cur_doc); GFile *cur_location = gtk_source_file_get_location (cur_file); if (cur_location != NULL && location != NULL && g_file_equal (location, cur_location)) { GtkWidget *info_bar; tab->priv->editable = FALSE; info_bar = xed_file_already_open_warning_info_bar_new (location); g_signal_connect (info_bar, "response", G_CALLBACK (file_already_open_warning_info_bar_response), tab); set_info_bar (tab, info_bar); gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); gtk_widget_show (info_bar); break; } } } g_list_free (all_documents); } xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); if (location == NULL) { /* FIXME: hackish */ gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (doc), TRUE); } tab->priv->ask_if_externally_modified = TRUE; if (error == NULL) { clear_loading (tab); } g_signal_emit_by_name (doc, "loaded"); end: /* Async operation finished. */ g_object_unref (tab); if (error != NULL) { g_error_free (error); } } static GSList * get_candidate_encodings (XedTab *tab) { XedDocument *doc; GSettings *enc_settings; gchar **enc_strv; gchar *metadata_charset; GtkSourceFile *file; const GtkSourceEncoding *file_encoding; GSList *encodings; enc_settings = g_settings_new ("org.x.editor.preferences.encodings"); enc_strv = g_settings_get_strv (enc_settings, XED_SETTINGS_ENCODING_AUTO_DETECTED); encodings = _xed_utils_encoding_strv_to_list ((const gchar * const *)enc_strv); doc = xed_tab_get_document (tab); metadata_charset = xed_document_get_metadata (doc, XED_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) { encodings = g_slist_prepend (encodings, (gpointer)metadata_enc); } } file = xed_document_get_file (doc); file_encoding = gtk_source_file_get_encoding (file); if (file_encoding != NULL) { encodings = g_slist_prepend (encodings, (gpointer)file_encoding); } g_object_unref (enc_settings); g_strfreev (enc_strv); g_free (metadata_charset); return encodings; } static void load (XedTab *tab, const GtkSourceEncoding *encoding, gint line_pos) { GSList *candidate_encodings = NULL; XedDocument *doc; g_return_if_fail (GTK_SOURCE_IS_FILE_LOADER (tab->priv->loader)); if (encoding != NULL) { tab->priv->user_requested_encoding = TRUE; candidate_encodings = g_slist_append (NULL, (gpointer) encoding); } else { tab->priv->user_requested_encoding = FALSE; candidate_encodings = get_candidate_encodings (tab); } gtk_source_file_loader_set_candidate_encodings (tab->priv->loader, candidate_encodings); g_slist_free (candidate_encodings); tab->priv->tmp_line_pos = line_pos; g_clear_object (&tab->priv->cancellable); tab->priv->cancellable = g_cancellable_new (); doc = xed_tab_get_document (tab); g_signal_emit_by_name (doc, "load"); /* Keep the tab alive during the async operation. */ g_object_ref (tab); gtk_source_file_loader_load_async (tab->priv->loader, G_PRIORITY_DEFAULT, tab->priv->cancellable, (GFileProgressCallback) loader_progress_cb, tab, NULL, (GAsyncReadyCallback) load_cb, tab); } void _xed_tab_load (XedTab *tab, GFile *location, const GtkSourceEncoding *encoding, gint line_pos, gboolean create) { XedDocument *doc; GtkSourceFile *file; g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail (G_IS_FILE (location)); g_return_if_fail (tab->priv->state == XED_TAB_STATE_NORMAL); xed_tab_set_state (tab, XED_TAB_STATE_LOADING); doc = xed_tab_get_document (tab); file = xed_document_get_file (doc); if (tab->priv->loader != NULL) { g_warning ("XedTab: file loader already exists."); g_object_unref (tab->priv->loader); } gtk_source_file_set_location (file, location); tab->priv->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (doc), file); _xed_document_set_create (doc, create); load (tab, encoding, line_pos); } void _xed_tab_load_stream (XedTab *tab, GInputStream *stream, const GtkSourceEncoding *encoding, gint line_pos) { XedDocument *doc; GtkSourceFile *file; g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail (G_IS_INPUT_STREAM (stream)); g_return_if_fail (tab->priv->state == XED_TAB_STATE_NORMAL); xed_tab_set_state (tab, XED_TAB_STATE_LOADING); doc = xed_tab_get_document (tab); file = xed_document_get_file (doc); if (tab->priv->loader != NULL) { g_warning ("XedTab: file loader already exists."); g_object_unref (tab->priv->loader); } gtk_source_file_set_location (file, NULL); tab->priv->loader = gtk_source_file_loader_new_from_stream (GTK_SOURCE_BUFFER (doc), file, stream); _xed_document_set_create (doc, FALSE); load (tab, encoding, line_pos); } void _xed_tab_revert (XedTab *tab) { XedDocument *doc; GtkSourceFile *file; GFile *location; g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail (tab->priv->state == XED_TAB_STATE_NORMAL || tab->priv->state == XED_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION); if (tab->priv->state == XED_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) { set_info_bar (tab, NULL); } doc = xed_tab_get_document (tab); file = xed_document_get_file (doc); location = gtk_source_file_get_location (file); g_return_if_fail (location != NULL); xed_tab_set_state (tab, XED_TAB_STATE_REVERTING); if (tab->priv->loader != NULL) { g_warning ("XedTab: file loader already exists."); g_object_unref (tab->priv->loader); } tab->priv->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (doc), file); load (tab, NULL, 0); } static void saver_progress_cb (goffset size, goffset total_size, XedTab *tab) { gdouble elapsed_time; gdouble total_time; gdouble remaining_time; g_return_if_fail (tab->priv->state == XED_TAB_STATE_SAVING); if (tab->priv->timer == NULL) { tab->priv->timer = g_timer_new (); } elapsed_time = g_timer_elapsed (tab->priv->timer, NULL); /* 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) { show_saving_info_bar (tab); } info_bar_set_progress (tab, size, total_size); } static void save_cb (GtkSourceFileSaver *saver, GAsyncResult *result, XedTab *tab) { XedDocument *doc = xed_tab_get_document (tab); GFile *location = gtk_source_file_saver_get_location (saver); GError *error = NULL; g_return_if_fail (tab->priv->task_saver != NULL); gtk_source_file_saver_save_finish (saver, result, &error); if (error != NULL) { xed_debug_message (DEBUG_TAB, "File saving error: %s", error->message); } if (tab->priv->timer != NULL) { g_timer_destroy (tab->priv->timer); tab->priv->timer = NULL; } set_info_bar (tab, NULL); if (error != NULL) { GtkWidget *info_bar; xed_tab_set_state (tab, XED_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 = xed_externally_modified_saving_error_info_bar_new (location, error); g_return_if_fail (info_bar != NULL); g_signal_connect (info_bar, "response", G_CALLBACK (externally_modified_error_info_bar_response), tab); } else if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANT_CREATE_BACKUP) { /* This error is recoverable */ info_bar = xed_no_backup_saving_error_info_bar_new (location, error); g_return_if_fail (info_bar != NULL); g_signal_connect (info_bar, "response", G_CALLBACK (no_backup_error_info_bar_response), tab); } 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 = xed_invalid_character_info_bar_new (location); g_return_if_fail (info_bar != NULL); g_signal_connect (info_bar, "response", G_CALLBACK (invalid_character_info_bar_response), tab); } 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 */ _xed_recent_remove (XED_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), location); info_bar = xed_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), tab); } 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 = xed_conversion_error_while_saving_info_bar_new (location, encoding, error); g_return_if_fail (info_bar != NULL); g_signal_connect (info_bar, "response", G_CALLBACK (recoverable_saving_error_info_bar_response), tab); } set_info_bar (tab, info_bar); gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); gtk_widget_show (info_bar); } else { gchar *mime = xed_document_get_mime_type (doc); _xed_recent_add (XED_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), location, mime); g_free (mime); if (tab->priv->print_preview != NULL) { xed_tab_set_state (tab, XED_TAB_STATE_SHOWING_PRINT_PREVIEW); } else { xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); } tab->priv->ask_if_externally_modified = TRUE; g_signal_emit_by_name (doc, "saved"); g_task_return_boolean (tab->priv->task_saver, TRUE); } if (error != NULL) { g_error_free (error); } } static void save (XedTab *tab) { XedDocument *doc; SaverData *data; g_return_if_fail (G_IS_TASK (tab->priv->task_saver)); xed_tab_set_state (tab, XED_TAB_STATE_SAVING); doc = xed_tab_get_document (tab); g_signal_emit_by_name (doc, "save"); data = g_task_get_task_data (tab->priv->task_saver); gtk_source_file_saver_save_async (data->saver, G_PRIORITY_DEFAULT, g_task_get_cancellable (tab->priv->task_saver), (GFileProgressCallback) saver_progress_cb, tab, NULL, (GAsyncReadyCallback) save_cb, tab); } /* Gets the initial save flags, when launching a new FileSaver. */ static GtkSourceFileSaverFlags get_initial_save_flags (XedTab *tab, gboolean auto_save) { GtkSourceFileSaverFlags save_flags; gboolean create_backup; save_flags = tab->priv->save_flags; create_backup = g_settings_get_boolean (tab->priv->editor, XED_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 _xed_tab_save_async (XedTab *tab, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { SaverData *data; XedDocument *doc; GtkSourceFile *file; GtkSourceFileSaverFlags save_flags; g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail ((tab->priv->state == XED_TAB_STATE_NORMAL) || (tab->priv->state == XED_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) || (tab->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW)); if (tab->priv->task_saver != NULL) { g_warning ("XedTab: file saver already exists."); return; } doc = xed_tab_get_document (tab); // g_return_if_fail (XED_IS_DOCUMENT (doc)); g_return_if_fail (!xed_document_is_untitled (doc)); tab->priv->task_saver = g_task_new (tab, cancellable, callback, user_data); data = saver_data_new (); g_task_set_task_data (tab->priv->task_saver, data, (GDestroyNotify) saver_data_free); save_flags = get_initial_save_flags (tab, FALSE); if (tab->priv->state == XED_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 = xed_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); save (tab); } gboolean _xed_tab_save_finish (XedTab *tab, GAsyncResult *result) { gboolean success; g_return_val_if_fail (g_task_is_valid (result, tab), FALSE); g_return_val_if_fail (tab->priv->task_saver == G_TASK (result), FALSE); success = g_task_propagate_boolean (tab->priv->task_saver, NULL); g_clear_object (&tab->priv->task_saver); return success; } static void auto_save_finished_cb (XedTab *tab, GAsyncResult *result, gpointer user_data) { _xed_tab_save_finish (tab, result); } static gboolean xed_tab_auto_save (XedTab *tab) { SaverData *data; XedDocument *doc; GtkSourceFile *file; GtkSourceFileSaverFlags save_flags; xed_debug (DEBUG_TAB); doc = xed_tab_get_document (tab); g_return_val_if_fail (!xed_document_is_untitled (doc), G_SOURCE_REMOVE); g_return_val_if_fail (!xed_document_get_readonly (doc), G_SOURCE_REMOVE); if (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc))) { xed_debug_message (DEBUG_TAB, "Document not modified"); return G_SOURCE_CONTINUE; } if ((tab->priv->state != XED_TAB_STATE_NORMAL) && (tab->priv->state != XED_TAB_STATE_SHOWING_PRINT_PREVIEW)) { xed_debug_message (DEBUG_TAB, "Retry after 30 seconds"); tab->priv->auto_save_timeout = g_timeout_add_seconds (30, (GSourceFunc) xed_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->priv->auto_save_timeout = 0; if (tab->priv->task_saver != NULL) { g_warning ("XedTab: file saver already exists."); return G_SOURCE_REMOVE; } tab->priv->task_saver = g_task_new (tab, NULL, (GAsyncReadyCallback) auto_save_finished_cb, NULL); data = saver_data_new (); g_task_set_task_data (tab->priv->task_saver, data, (GDestroyNotify) saver_data_free); file = xed_document_get_file (doc); 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); save (tab); return G_SOURCE_REMOVE; } /* Call _xed_tab_save_finish() in @callback, there is no * _xed_tab_save_as_finish(). */ void _xed_tab_save_as_async (XedTab *tab, GFile *location, const GtkSourceEncoding *encoding, GtkSourceNewlineType newline_type, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { SaverData *data; XedDocument *doc; GtkSourceFile *file; GtkSourceFileSaverFlags save_flags; g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail ((tab->priv->state == XED_TAB_STATE_NORMAL) || (tab->priv->state == XED_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) || (tab->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW)); g_return_if_fail (G_IS_FILE (location)); g_return_if_fail (encoding != NULL); if (tab->priv->task_saver != NULL) { g_warning ("XedTab: file saver already exists."); return; } tab->priv->task_saver = g_task_new (tab, cancellable, callback, user_data); data = saver_data_new (); g_task_set_task_data (tab->priv->task_saver, data, (GDestroyNotify) saver_data_free); doc = xed_tab_get_document (tab); g_return_if_fail (XED_IS_DOCUMENT (doc)); /* reset the save flags, when saving as */ tab->priv->save_flags = GTK_SOURCE_FILE_SAVER_FLAGS_NONE; save_flags = get_initial_save_flags (tab, FALSE); if (tab->priv->state == XED_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 = xed_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_flags (data->saver, save_flags); save (tab); } #define XED_PAGE_SETUP_KEY "xed-page-setup-key" #define XED_PRINT_SETTINGS_KEY "xed-print-settings-key" static GtkPageSetup * get_page_setup (XedTab *tab) { gpointer data; XedDocument *doc; doc = xed_tab_get_document (tab); data = g_object_get_data (G_OBJECT (doc), XED_PAGE_SETUP_KEY); if (data == NULL) { return _xed_app_get_default_page_setup (XED_APP (g_application_get_default ())); } else { return gtk_page_setup_copy (GTK_PAGE_SETUP (data)); } } static GtkPrintSettings * get_print_settings (XedTab *tab) { gpointer data; XedDocument *doc; GtkPrintSettings *settings; gchar *uri, *name; doc = xed_tab_get_document (tab); data = g_object_get_data (G_OBJECT (doc), XED_PRINT_SETTINGS_KEY); if (data == NULL) { settings = _xed_app_get_default_print_settings (XED_APP (g_application_get_default ())); } else { settings = gtk_print_settings_copy (GTK_PRINT_SETTINGS (data)); } name = xed_document_get_short_name_for_display (doc); uri = g_strconcat ("file://", g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS), "/", name, ".pdf", NULL); gtk_print_settings_set (settings, GTK_PRINT_SETTINGS_OUTPUT_URI, uri); g_free (uri); g_free (name); return settings; } /* FIXME: show the message area only if the operation will be "long" */ static void printing_cb (XedPrintJob *job, XedPrintJobStatus status, XedTab *tab) { g_return_if_fail (XED_IS_PROGRESS_INFO_BAR (tab->priv->info_bar)); gtk_widget_show (tab->priv->info_bar); xed_progress_info_bar_set_text (XED_PROGRESS_INFO_BAR (tab->priv->info_bar), xed_print_job_get_status_string (job)); xed_progress_info_bar_set_fraction (XED_PROGRESS_INFO_BAR (tab->priv->info_bar), xed_print_job_get_progress (job)); } static void store_print_settings (XedTab *tab, XedPrintJob *job) { XedDocument *doc; GtkPrintSettings *settings; GtkPageSetup *page_setup; doc = xed_tab_get_document (tab); settings = xed_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), XED_PRINT_SETTINGS_KEY, g_object_ref (settings), (GDestroyNotify)g_object_unref); /* make them the default */ _xed_app_set_default_print_settings (XED_APP (g_application_get_default ()), settings); page_setup = xed_print_job_get_page_setup (job); /* remember page setup for this document */ g_object_set_data_full (G_OBJECT (doc), XED_PAGE_SETUP_KEY, g_object_ref (page_setup), (GDestroyNotify)g_object_unref); /* make it the default */ _xed_app_set_default_page_setup (XED_APP (g_application_get_default ()), page_setup); } static void done_printing_cb (XedPrintJob *job, XedPrintJobResult result, const GError *error, XedTab *tab) { XedView *view; g_return_if_fail (tab->priv->state == XED_TAB_STATE_PRINT_PREVIEWING || tab->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW || tab->priv->state == XED_TAB_STATE_PRINTING); if (tab->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW) { /* print preview has been destroyed... */ tab->priv->print_preview = NULL; } else { g_return_if_fail (XED_IS_PROGRESS_INFO_BAR (tab->priv->info_bar)); set_info_bar (tab, NULL); /* destroy the message area */ } // TODO: check status and error if (result == XED_PRINT_JOB_RESULT_OK) { store_print_settings (tab, job); } #if 0 if (tab->priv->print_preview != NULL) { /* If we were printing while showing the print preview, see bug #352658 */ gtk_widget_destroy (tab->priv->print_preview); g_return_if_fail (tab->priv->state == XED_TAB_STATE_PRINTING); } #endif xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); view = xed_tab_get_view (tab); gtk_widget_grab_focus (GTK_WIDGET (view)); g_object_unref (tab->priv->print_job); tab->priv->print_job = NULL; } #if 0 static void print_preview_destroyed (GtkWidget *preview, XedTab *tab) { tab->priv->print_preview = NULL; if (tab->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW) { XedView *view; xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); view = xed_tab_get_view (tab); gtk_widget_grab_focus (GTK_WIDGET (view)); } else { /* This should happen only when printing while showing the print * preview. In this case let us continue whithout changing * the state and show the document. See bug #352658 */ gtk_widget_show (tab->priv->view_scrolled_window); g_return_if_fail (tab->priv->state == XED_TAB_STATE_PRINTING); } } #endif static void show_preview_cb (XedPrintJob *job, XedPrintPreview *preview, XedTab *tab) { // g_return_if_fail (tab->priv->state == XED_TAB_STATE_PRINT_PREVIEWING); g_return_if_fail (tab->priv->print_preview == NULL); set_info_bar (tab, NULL); /* destroy the message area */ tab->priv->print_preview = GTK_WIDGET (preview); gtk_box_pack_end (GTK_BOX (tab), tab->priv->print_preview, TRUE, TRUE, 0); gtk_widget_show (tab->priv->print_preview); gtk_widget_grab_focus (tab->priv->print_preview); /* when the preview gets destroyed we get "done" signal g_signal_connect (tab->priv->print_preview, "destroy", G_CALLBACK (print_preview_destroyed), tab); */ xed_tab_set_state (tab, XED_TAB_STATE_SHOWING_PRINT_PREVIEW); } #if 0 static void set_print_preview (XedTab *tab, GtkWidget *print_preview) { if (tab->priv->print_preview == print_preview) return; if (tab->priv->print_preview != NULL) gtk_widget_destroy (tab->priv->print_preview); tab->priv->print_preview = print_preview; gtk_box_pack_end (GTK_BOX (tab), tab->priv->print_preview, TRUE, TRUE, 0); gtk_widget_grab_focus (tab->priv->print_preview); g_signal_connect (tab->priv->print_preview, "destroy", G_CALLBACK (print_preview_destroyed), tab); } static void preview_finished_cb (GtkSourcePrintJob *pjob, XedTab *tab) { MatePrintJob *gjob; GtkWidget *preview = NULL; g_return_if_fail (XED_IS_PROGRESS_INFO_BAR (tab->priv->info_bar)); set_info_bar (tab, NULL); /* destroy the message area */ gjob = gtk_source_print_job_get_print_job (pjob); preview = xed_print_job_preview_new (gjob); g_object_unref (gjob); set_print_preview (tab, preview); gtk_widget_show (preview); g_object_unref (pjob); xed_tab_set_state (tab, XED_TAB_STATE_SHOWING_PRINT_PREVIEW); } #endif static void print_cancelled (GtkWidget *area, gint response_id, XedTab *tab) { g_return_if_fail (XED_IS_PROGRESS_INFO_BAR (tab->priv->info_bar)); xed_print_job_cancel (tab->priv->print_job); g_debug ("print_cancelled"); } static void show_printing_info_bar (XedTab *tab, gboolean preview) { GtkWidget *area; if (preview) { area = xed_progress_info_bar_new ("document-print-preview-symbolic", "", TRUE); } else { area = xed_progress_info_bar_new ("document-print-symbolic", "", TRUE); } g_signal_connect (area, "response", G_CALLBACK (print_cancelled), tab); set_info_bar (tab, area); } static void xed_tab_print_or_print_preview (XedTab *tab, GtkPrintOperationAction print_action) { XedView *view; gboolean is_preview; GtkPageSetup *setup; GtkPrintSettings *settings; GtkPrintOperationResult res; GError *error = NULL; g_return_if_fail (tab->priv->print_job == NULL); g_return_if_fail (tab->priv->state == XED_TAB_STATE_NORMAL); view = xed_tab_get_view (tab); is_preview = (print_action == GTK_PRINT_OPERATION_ACTION_PREVIEW); tab->priv->print_job = xed_print_job_new (view); g_object_add_weak_pointer (G_OBJECT (tab->priv->print_job), (gpointer *) &tab->priv->print_job); show_printing_info_bar (tab, is_preview); g_signal_connect (tab->priv->print_job, "printing", G_CALLBACK (printing_cb), tab); g_signal_connect (tab->priv->print_job, "show-preview", G_CALLBACK (show_preview_cb), tab); g_signal_connect (tab->priv->print_job, "done", G_CALLBACK (done_printing_cb), tab); if (is_preview) { xed_tab_set_state (tab, XED_TAB_STATE_PRINT_PREVIEWING); } else { xed_tab_set_state (tab, XED_TAB_STATE_PRINTING); } setup = get_page_setup (tab); settings = get_print_settings (tab); res = xed_print_job_print (tab->priv->print_job, print_action, 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 */ xed_tab_set_state (tab, XED_TAB_STATE_NORMAL); g_warning ("Async print preview failed (%s)", error->message); g_object_unref (tab->priv->print_job); g_error_free (error); } } void _xed_tab_print (XedTab *tab, gboolean with_dialog) { g_return_if_fail (XED_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->priv->state == XED_TAB_STATE_SHOWING_PRINT_PREVIEW) { gtk_widget_destroy (tab->priv->print_preview); } if (with_dialog) { xed_tab_print_or_print_preview (tab, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); } else { xed_tab_print_or_print_preview (tab, GTK_PRINT_OPERATION_ACTION_PRINT); } } void _xed_tab_print_preview (XedTab *tab) { g_return_if_fail (XED_IS_TAB (tab)); xed_tab_print_or_print_preview (tab, GTK_PRINT_OPERATION_ACTION_PREVIEW); } void _xed_tab_mark_for_closing (XedTab *tab) { g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail (tab->priv->state == XED_TAB_STATE_NORMAL); xed_tab_set_state (tab, XED_TAB_STATE_CLOSING); } gboolean _xed_tab_get_can_close (XedTab *tab) { XedDocument *doc; XedTabState ts; g_return_val_if_fail (XED_IS_TAB (tab), FALSE); ts = xed_tab_get_state (tab); /* if we are loading or reverting, the tab can be closed */ if ((ts == XED_TAB_STATE_LOADING) || (ts == XED_TAB_STATE_LOADING_ERROR) || (ts == XED_TAB_STATE_REVERTING) || (ts == XED_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 (ts == XED_TAB_STATE_SAVING_ERROR) { return FALSE; } doc = xed_tab_get_document (tab); if (_xed_document_needs_saving (doc)) { return FALSE; } return TRUE; } /** * xed_tab_get_auto_save_enabled: * @tab: a #XedTab * * Gets the current state for the autosave feature * * Return value: %TRUE if the autosave is enabled, else %FALSE **/ gboolean xed_tab_get_auto_save_enabled (XedTab *tab) { xed_debug (DEBUG_TAB); g_return_val_if_fail (XED_IS_TAB (tab), FALSE); return tab->priv->auto_save; } /** * xed_tab_set_auto_save_enabled: * @tab: a #XedTab * @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 xed_tab_set_auto_save_enabled (XedTab *tab, gboolean enable) { xed_debug (DEBUG_TAB); g_return_if_fail (XED_IS_TAB (tab)); enable = enable != FALSE; if (tab->priv->auto_save != enable) { tab->priv->auto_save = enable; update_auto_save_timeout (tab); return; } } /** * xed_tab_get_auto_save_interval: * @tab: a #XedTab * * Gets the current interval for the autosaves * * Return value: the value of the autosave **/ gint xed_tab_get_auto_save_interval (XedTab *tab) { xed_debug (DEBUG_TAB); g_return_val_if_fail (XED_IS_TAB (tab), 0); return tab->priv->auto_save_interval; } /** * xed_tab_set_auto_save_interval: * @tab: a #XedTab * @interval: the new interval * * Sets the interval for the autosave feature. **/ void xed_tab_set_auto_save_interval (XedTab *tab, gint interval) { g_return_if_fail (XED_IS_TAB (tab)); g_return_if_fail (interval > 0); xed_debug (DEBUG_TAB); if (tab->priv->auto_save_interval != interval) { tab->priv->auto_save_interval = interval; remove_auto_save_timeout (tab); update_auto_save_timeout (tab); } } void xed_tab_set_info_bar (XedTab *tab, GtkWidget *info_bar) { g_return_if_fail (XED_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); } GtkWidget * _xed_tab_get_view_frame (XedTab *tab) { return GTK_WIDGET (tab->priv->frame); }