/*
* 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: */