2225 lines
61 KiB
C
2225 lines
61 KiB
C
/*
|
|
* xed-document.c
|
|
* This file is part of xed
|
|
*
|
|
* Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
|
|
* Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
|
|
* Copyright (C) 2002-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, 1998-2005. See the AUTHORS file for a
|
|
* list of people on the xed Team.
|
|
* See the ChangeLog files for a list of changes.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "xed-document.h"
|
|
#include "xed-settings.h"
|
|
#include "xed-debug.h"
|
|
#include "xed-utils.h"
|
|
#include "xed-document-loader.h"
|
|
#include "xed-document-saver.h"
|
|
#include "xed-marshal.h"
|
|
#include "xed-enum-types.h"
|
|
|
|
#ifndef ENABLE_GVFS_METADATA
|
|
#include "xed-metadata-manager.h"
|
|
#else
|
|
#define METADATA_QUERY "metadata::*"
|
|
#endif
|
|
|
|
#define NO_LANGUAGE_NAME "_NORMAL_"
|
|
|
|
#define XED_DOCUMENT_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), XED_TYPE_DOCUMENT, XedDocumentPrivate))
|
|
|
|
static void xed_document_load_real (XedDocument *doc,
|
|
GFile *location,
|
|
const XedEncoding *encoding,
|
|
gint line_pos,
|
|
gboolean create);
|
|
static void xed_document_save_real (XedDocument *doc,
|
|
GFile *location,
|
|
const XedEncoding *encoding,
|
|
XedDocumentSaveFlags flags);
|
|
|
|
struct _XedDocumentPrivate
|
|
{
|
|
GSettings *editor_settings;
|
|
|
|
GFile *location;
|
|
|
|
gint untitled_number;
|
|
gchar *short_name;
|
|
|
|
GFileInfo *metadata_info;
|
|
|
|
const XedEncoding *encoding;
|
|
|
|
gchar *content_type;
|
|
|
|
GTimeVal mtime;
|
|
GTimeVal time_of_last_save_or_load;
|
|
|
|
GtkSourceSearchContext *search_context;
|
|
|
|
XedDocumentNewlineType newline_type;
|
|
|
|
/* Temp data while loading */
|
|
XedDocumentLoader *loader;
|
|
gboolean create; /* Create file if uri points
|
|
* to a non existing file */
|
|
const XedEncoding *requested_encoding;
|
|
gint requested_line_pos;
|
|
|
|
/* Saving stuff */
|
|
XedDocumentSaver *saver;
|
|
|
|
GtkTextTag *error_tag;
|
|
|
|
/* Mount operation factory */
|
|
XedMountOperationFactory mount_operation_factory;
|
|
gpointer mount_operation_userdata;
|
|
|
|
guint readonly : 1;
|
|
guint externally_modified : 1;
|
|
guint deleted : 1;
|
|
guint last_save_was_manually : 1;
|
|
guint language_set_by_user : 1;
|
|
guint stop_cursor_moved_emission : 1;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_LOCATION,
|
|
PROP_SHORTNAME,
|
|
PROP_CONTENT_TYPE,
|
|
PROP_MIME_TYPE,
|
|
PROP_READ_ONLY,
|
|
PROP_ENCODING,
|
|
PROP_NEWLINE_TYPE
|
|
};
|
|
|
|
enum
|
|
{
|
|
CURSOR_MOVED,
|
|
LOAD,
|
|
LOADING,
|
|
LOADED,
|
|
SAVE,
|
|
SAVING,
|
|
SAVED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint document_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static GHashTable *allocated_untitled_numbers = NULL;
|
|
|
|
G_DEFINE_TYPE(XedDocument, xed_document, GTK_SOURCE_TYPE_BUFFER)
|
|
|
|
GQuark
|
|
xed_document_error_quark (void)
|
|
{
|
|
static GQuark quark = 0;
|
|
|
|
if (G_UNLIKELY (quark == 0))
|
|
{
|
|
quark = g_quark_from_static_string ("xed_io_load_error");
|
|
}
|
|
|
|
return quark;
|
|
}
|
|
|
|
static gint
|
|
get_untitled_number (void)
|
|
{
|
|
gint i = 1;
|
|
|
|
if (allocated_untitled_numbers == NULL)
|
|
{
|
|
allocated_untitled_numbers = g_hash_table_new (NULL, NULL);
|
|
}
|
|
|
|
g_return_val_if_fail (allocated_untitled_numbers != NULL, -1);
|
|
|
|
while (TRUE)
|
|
{
|
|
if (g_hash_table_lookup (allocated_untitled_numbers, GINT_TO_POINTER (i)) == NULL)
|
|
{
|
|
g_hash_table_insert (allocated_untitled_numbers, GINT_TO_POINTER (i), GINT_TO_POINTER (i));
|
|
|
|
return i;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
static void
|
|
release_untitled_number (gint n)
|
|
{
|
|
g_return_if_fail (allocated_untitled_numbers != NULL);
|
|
|
|
g_hash_table_remove (allocated_untitled_numbers, GINT_TO_POINTER (n));
|
|
}
|
|
|
|
static const gchar *
|
|
get_language_metadata (XedDocument *doc)
|
|
{
|
|
GtkSourceLanguage *lang = xed_document_get_language (doc);
|
|
|
|
return lang != NULL ? gtk_source_language_get_id (lang) : NO_LANGUAGE_NAME;
|
|
}
|
|
|
|
static void
|
|
save_metadata (XedDocument *doc)
|
|
{
|
|
const gchar *language = NULL;
|
|
GtkTextIter iter;
|
|
gchar *position;
|
|
|
|
if (doc->priv->language_set_by_user)
|
|
{
|
|
language = get_language_metadata (doc);
|
|
}
|
|
|
|
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc),
|
|
&iter,
|
|
gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (doc)));
|
|
|
|
position = g_strdup_printf ("%d", gtk_text_iter_get_offset (&iter));
|
|
|
|
if (language == NULL)
|
|
{
|
|
xed_document_set_metadata (doc,
|
|
XED_METADATA_ATTRIBUTE_POSITION, position,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
xed_document_set_metadata (doc,
|
|
XED_METADATA_ATTRIBUTE_POSITION, position,
|
|
XED_METADATA_ATTRIBUTE_LANGUAGE, language,
|
|
NULL);
|
|
}
|
|
|
|
g_free (position);
|
|
}
|
|
|
|
static void
|
|
xed_document_dispose (GObject *object)
|
|
{
|
|
XedDocument *doc = XED_DOCUMENT (object);
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
/* Metadata must be saved here and not in finalize because the language
|
|
* is gone by the time finalize runs.
|
|
*/
|
|
if (doc->priv->location != NULL)
|
|
{
|
|
save_metadata (doc);
|
|
|
|
g_object_unref (doc->priv->location);
|
|
doc->priv->location = NULL;
|
|
}
|
|
|
|
g_clear_object (&doc->priv->loader);
|
|
g_clear_object (&doc->priv->editor_settings);
|
|
g_clear_object (&doc->priv->metadata_info);
|
|
g_clear_object (&doc->priv->search_context);
|
|
|
|
G_OBJECT_CLASS (xed_document_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
xed_document_finalize (GObject *object)
|
|
{
|
|
XedDocument *doc = XED_DOCUMENT (object);
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
if (doc->priv->untitled_number > 0)
|
|
{
|
|
release_untitled_number (doc->priv->untitled_number);
|
|
}
|
|
|
|
g_free (doc->priv->content_type);
|
|
g_free (doc->priv->short_name);
|
|
|
|
G_OBJECT_CLASS (xed_document_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
xed_document_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
XedDocument *doc = XED_DOCUMENT (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_LOCATION:
|
|
g_value_set_object (value, doc->priv->location);
|
|
break;
|
|
case PROP_SHORTNAME:
|
|
g_value_take_string (value, xed_document_get_short_name_for_display (doc));
|
|
break;
|
|
case PROP_CONTENT_TYPE:
|
|
g_value_take_string (value, xed_document_get_content_type (doc));
|
|
break;
|
|
case PROP_MIME_TYPE:
|
|
g_value_take_string (value, xed_document_get_mime_type (doc));
|
|
break;
|
|
case PROP_READ_ONLY:
|
|
g_value_set_boolean (value, doc->priv->readonly);
|
|
break;
|
|
case PROP_ENCODING:
|
|
g_value_set_boxed (value, doc->priv->encoding);
|
|
break;
|
|
case PROP_NEWLINE_TYPE:
|
|
g_value_set_enum (value, doc->priv->newline_type);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
xed_document_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
XedDocument *doc = XED_DOCUMENT (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_LOCATION:
|
|
{
|
|
GFile *location;
|
|
|
|
location = g_value_get_object (value);
|
|
|
|
if (location != NULL)
|
|
{
|
|
xed_document_set_location (doc, location);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case PROP_SHORTNAME:
|
|
xed_document_set_short_name_for_display (doc, g_value_get_string (value));
|
|
break;
|
|
case PROP_CONTENT_TYPE:
|
|
xed_document_set_content_type (doc, g_value_get_string (value));
|
|
break;
|
|
case PROP_NEWLINE_TYPE:
|
|
xed_document_set_newline_type (doc, g_value_get_enum (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
emit_cursor_moved (XedDocument *doc)
|
|
{
|
|
if (!doc->priv->stop_cursor_moved_emission)
|
|
{
|
|
g_signal_emit (doc, document_signals[CURSOR_MOVED], 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xed_document_mark_set (GtkTextBuffer *buffer,
|
|
const GtkTextIter *iter,
|
|
GtkTextMark *mark)
|
|
{
|
|
XedDocument *doc = XED_DOCUMENT (buffer);
|
|
|
|
if (GTK_TEXT_BUFFER_CLASS (xed_document_parent_class)->mark_set != NULL)
|
|
{
|
|
GTK_TEXT_BUFFER_CLASS (xed_document_parent_class)->mark_set (buffer, iter, mark);
|
|
}
|
|
|
|
if (mark == gtk_text_buffer_get_insert (buffer))
|
|
{
|
|
emit_cursor_moved (doc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xed_document_changed (GtkTextBuffer *buffer)
|
|
{
|
|
emit_cursor_moved (XED_DOCUMENT (buffer));
|
|
|
|
GTK_TEXT_BUFFER_CLASS (xed_document_parent_class)->changed (buffer);
|
|
}
|
|
|
|
static void
|
|
xed_document_class_init (XedDocumentClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkTextBufferClass *buf_class = GTK_TEXT_BUFFER_CLASS (klass);
|
|
|
|
object_class->dispose = xed_document_dispose;
|
|
object_class->finalize = xed_document_finalize;
|
|
object_class->get_property = xed_document_get_property;
|
|
object_class->set_property = xed_document_set_property;
|
|
|
|
buf_class->mark_set = xed_document_mark_set;
|
|
buf_class->changed = xed_document_changed;
|
|
|
|
klass->load = xed_document_load_real;
|
|
klass->save = xed_document_save_real;
|
|
|
|
g_object_class_install_property (object_class, PROP_LOCATION,
|
|
g_param_spec_object ("location",
|
|
"Location",
|
|
"The document's location",
|
|
G_TYPE_FILE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_SHORTNAME,
|
|
g_param_spec_string ("shortname",
|
|
"Short Name",
|
|
"The document's short name",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_CONTENT_TYPE,
|
|
g_param_spec_string ("content-type",
|
|
"Content Type",
|
|
"The document's Content Type",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_MIME_TYPE,
|
|
g_param_spec_string ("mime-type",
|
|
"MIME Type",
|
|
"The document's MIME Type",
|
|
"text/plain",
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_READ_ONLY,
|
|
g_param_spec_boolean ("read-only",
|
|
"Read Only",
|
|
"Whether the document is read only or not",
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_ENCODING,
|
|
g_param_spec_boxed ("encoding",
|
|
"Encoding",
|
|
"The XedEncoding used for the document",
|
|
XED_TYPE_ENCODING,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* XedDocument:newline-type:
|
|
*
|
|
* The :newline-type property determines what is considered
|
|
* as a line ending when saving the document
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_NEWLINE_TYPE,
|
|
g_param_spec_enum ("newline-type",
|
|
"Newline type",
|
|
"The accepted types of line ending",
|
|
XED_TYPE_DOCUMENT_NEWLINE_TYPE,
|
|
XED_DOCUMENT_NEWLINE_TYPE_LF,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB));
|
|
|
|
/* This signal is used to update the cursor position is the statusbar,
|
|
* it's emitted either when the insert mark is moved explicitely or
|
|
* when the buffer changes (insert/delete).
|
|
* We prevent the emission of the signal during replace_all to
|
|
* improve performance.
|
|
*/
|
|
document_signals[CURSOR_MOVED] =
|
|
g_signal_new ("cursor-moved",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, cursor_moved),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
/**
|
|
* XedDocument::load:
|
|
* @document: the #XedDocument.
|
|
* @location: the location where to load the document from.
|
|
* @encoding: the #XedEncoding to encode the document.
|
|
* @line_pos: the line to show.
|
|
* @create: whether the document should be created if it doesn't exist.
|
|
*
|
|
* The "load" signal is emitted when a document is loaded.
|
|
*/
|
|
document_signals[LOAD] =
|
|
g_signal_new ("load",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, load),
|
|
NULL, NULL,
|
|
xed_marshal_VOID__OBJECT_BOXED_INT_BOOLEAN,
|
|
G_TYPE_NONE,
|
|
4,
|
|
G_TYPE_FILE,
|
|
/* we rely on the fact that the XedEncoding pointer stays
|
|
* the same forever */
|
|
XED_TYPE_ENCODING | G_SIGNAL_TYPE_STATIC_SCOPE,
|
|
G_TYPE_INT,
|
|
G_TYPE_BOOLEAN);
|
|
|
|
|
|
document_signals[LOADING] =
|
|
g_signal_new ("loading",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, loading),
|
|
NULL, NULL,
|
|
xed_marshal_VOID__UINT64_UINT64,
|
|
G_TYPE_NONE,
|
|
2,
|
|
G_TYPE_UINT64,
|
|
G_TYPE_UINT64);
|
|
|
|
document_signals[LOADED] =
|
|
g_signal_new ("loaded",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, loaded),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_POINTER);
|
|
|
|
/**
|
|
* XedDocument::save:
|
|
* @document: the #XedDocument.
|
|
* @location: the location where the document is about to be saved.
|
|
* @encoding: the #XedEncoding used to save the document.
|
|
* @flags: the #XedDocumentSaveFlags for the save operation.
|
|
*
|
|
* The "save" signal is emitted when the document is saved.
|
|
*/
|
|
document_signals[SAVE] =
|
|
g_signal_new ("save",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, save),
|
|
NULL, NULL,
|
|
xed_marshal_VOID__OBJECT_BOXED_FLAGS,
|
|
G_TYPE_NONE,
|
|
3,
|
|
G_TYPE_FILE,
|
|
/* we rely on the fact that the XedEncoding pointer stays
|
|
* the same forever */
|
|
XED_TYPE_ENCODING | G_SIGNAL_TYPE_STATIC_SCOPE,
|
|
XED_TYPE_DOCUMENT_SAVE_FLAGS);
|
|
|
|
document_signals[SAVING] =
|
|
g_signal_new ("saving",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, saving),
|
|
NULL, NULL,
|
|
xed_marshal_VOID__UINT64_UINT64,
|
|
G_TYPE_NONE,
|
|
2,
|
|
G_TYPE_UINT64,
|
|
G_TYPE_UINT64);
|
|
|
|
document_signals[SAVED] =
|
|
g_signal_new ("saved",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (XedDocumentClass, saved),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_POINTER);
|
|
|
|
g_type_class_add_private (object_class, sizeof (XedDocumentPrivate));
|
|
}
|
|
|
|
static void
|
|
set_language (XedDocument *doc,
|
|
GtkSourceLanguage *lang,
|
|
gboolean set_by_user)
|
|
{
|
|
GtkSourceLanguage *old_lang;
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
old_lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc));
|
|
|
|
if (old_lang == lang)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (doc), lang);
|
|
|
|
if (lang != NULL)
|
|
{
|
|
gboolean syntax_hl;
|
|
|
|
syntax_hl = g_settings_get_boolean (doc->priv->editor_settings,
|
|
XED_SETTINGS_SYNTAX_HIGHLIGHTING);
|
|
gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (doc), syntax_hl);
|
|
}
|
|
else
|
|
{
|
|
gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (doc), FALSE);
|
|
}
|
|
|
|
if (set_by_user)
|
|
{
|
|
const gchar *language = get_language_metadata (doc);
|
|
|
|
xed_document_set_metadata (doc, XED_METADATA_ATTRIBUTE_LANGUAGE, language, NULL);
|
|
}
|
|
|
|
doc->priv->language_set_by_user = set_by_user;
|
|
}
|
|
|
|
static void
|
|
set_encoding (XedDocument *doc,
|
|
const XedEncoding *encoding,
|
|
gboolean set_by_user)
|
|
{
|
|
g_return_if_fail (encoding != NULL);
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
if (doc->priv->encoding == encoding)
|
|
{
|
|
return;
|
|
}
|
|
|
|
doc->priv->encoding = encoding;
|
|
|
|
if (set_by_user)
|
|
{
|
|
const gchar *charset;
|
|
|
|
charset = xed_encoding_get_charset (encoding);
|
|
|
|
xed_document_set_metadata (doc, XED_METADATA_ATTRIBUTE_ENCODING, charset, NULL);
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (doc), "encoding");
|
|
}
|
|
|
|
static GtkSourceStyleScheme *
|
|
get_default_style_scheme (GSettings *editor_settings)
|
|
{
|
|
GtkSourceStyleSchemeManager *manager;
|
|
gchar *scheme_id;
|
|
GtkSourceStyleScheme *def_style;
|
|
|
|
manager = gtk_source_style_scheme_manager_get_default ();
|
|
scheme_id = g_settings_get_string (editor_settings, XED_SETTINGS_SCHEME);
|
|
def_style = gtk_source_style_scheme_manager_get_scheme (manager, scheme_id);
|
|
|
|
if (def_style == NULL)
|
|
{
|
|
g_warning ("Default style scheme '%s' cannot be found, falling back to 'classic' style scheme ", scheme_id);
|
|
|
|
def_style = gtk_source_style_scheme_manager_get_scheme (manager, "classic");
|
|
if (def_style == NULL)
|
|
{
|
|
g_warning ("Style scheme 'classic' cannot be found, check your GtkSourceView installation.");
|
|
}
|
|
}
|
|
|
|
g_free (scheme_id);
|
|
|
|
return def_style;
|
|
}
|
|
|
|
static void
|
|
on_location_changed (XedDocument *doc,
|
|
GParamSpec *pspec,
|
|
gpointer useless)
|
|
{
|
|
#ifdef ENABLE_GVFS_METADATA
|
|
GFile *location;
|
|
|
|
location = xed_document_get_location (doc);
|
|
|
|
/* load metadata for this location: we load sync since metadata is
|
|
* always local so it should be fast and we need the information
|
|
* right after the location was set.
|
|
*/
|
|
if (location != NULL)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (doc->priv->metadata_info != NULL)
|
|
{
|
|
g_object_unref (doc->priv->metadata_info);
|
|
}
|
|
|
|
doc->priv->metadata_info = g_file_query_info (location,
|
|
METADATA_QUERY,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
NULL,
|
|
&error);
|
|
|
|
if (error != NULL)
|
|
{
|
|
if (error->code != G_FILE_ERROR_ISDIR &&
|
|
error->code != G_FILE_ERROR_NOTDIR &&
|
|
error->code != G_FILE_ERROR_NOENT)
|
|
{
|
|
g_warning ("%s", error->message);
|
|
}
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_object_unref (location);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static GtkSourceLanguage *
|
|
guess_language (XedDocument *doc)
|
|
{
|
|
gchar *data;
|
|
GtkSourceLanguageManager *manager = gtk_source_language_manager_get_default ();
|
|
GtkSourceLanguage *language = NULL;
|
|
|
|
data = xed_document_get_metadata (doc, XED_METADATA_ATTRIBUTE_LANGUAGE);
|
|
|
|
if (data != NULL)
|
|
{
|
|
xed_debug_message (DEBUG_DOCUMENT, "Language from metadata: %s", data);
|
|
|
|
if (strcmp (data, NO_LANGUAGE_NAME) != 0)
|
|
{
|
|
language = gtk_source_language_manager_get_language (manager, data);
|
|
}
|
|
|
|
g_free (data);
|
|
}
|
|
else
|
|
{
|
|
GFile *location;
|
|
gchar *basename = NULL;
|
|
|
|
location = xed_document_get_location (doc);
|
|
xed_debug_message (DEBUG_DOCUMENT, "Sniffing Language");
|
|
|
|
if (location)
|
|
{
|
|
basename = g_file_get_basename (location);
|
|
}
|
|
else if (doc->priv->short_name != NULL)
|
|
{
|
|
basename = g_strdup (doc->priv->short_name);
|
|
}
|
|
|
|
language = gtk_source_language_manager_guess_language (manager, basename, doc->priv->content_type);
|
|
|
|
g_free (basename);
|
|
|
|
if (location != NULL)
|
|
{
|
|
g_object_unref (location);
|
|
}
|
|
}
|
|
|
|
return language;
|
|
}
|
|
|
|
static void
|
|
on_content_type_changed (XedDocument *doc,
|
|
GParamSpec *pspec,
|
|
gpointer useless)
|
|
{
|
|
if (!doc->priv->language_set_by_user)
|
|
{
|
|
GtkSourceLanguage *language = guess_language (doc);
|
|
|
|
xed_debug_message (DEBUG_DOCUMENT, "Language: %s",
|
|
language != NULL ? gtk_source_language_get_name (language) : "None");
|
|
|
|
set_language (doc, language, FALSE);
|
|
}
|
|
}
|
|
|
|
static gchar *
|
|
get_default_content_type (void)
|
|
{
|
|
return g_content_type_from_mime_type ("text/plain");
|
|
}
|
|
|
|
static void
|
|
xed_document_init (XedDocument *doc)
|
|
{
|
|
XedDocumentPrivate *priv;
|
|
GtkSourceStyleScheme *style_scheme;
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
doc->priv = XED_DOCUMENT_GET_PRIVATE (doc);
|
|
priv = doc->priv;
|
|
|
|
priv->editor_settings = g_settings_new ("org.x.editor.preferences.editor");
|
|
|
|
priv->location = NULL;
|
|
priv->untitled_number = get_untitled_number ();
|
|
|
|
priv->metadata_info = NULL;
|
|
|
|
priv->content_type = get_default_content_type ();
|
|
|
|
priv->readonly = FALSE;
|
|
|
|
priv->stop_cursor_moved_emission = FALSE;
|
|
|
|
priv->last_save_was_manually = TRUE;
|
|
priv->language_set_by_user = FALSE;
|
|
|
|
priv->mtime.tv_sec = 0;
|
|
priv->mtime.tv_usec = 0;
|
|
|
|
g_get_current_time (&doc->priv->time_of_last_save_or_load);
|
|
|
|
priv->encoding = xed_encoding_get_utf8 ();
|
|
|
|
priv->newline_type = XED_DOCUMENT_NEWLINE_TYPE_DEFAULT;
|
|
|
|
g_settings_bind (priv->editor_settings,
|
|
XED_SETTINGS_MAX_UNDO_ACTIONS,
|
|
doc,
|
|
"max-undo-levels",
|
|
G_SETTINGS_BIND_GET);
|
|
|
|
g_settings_bind (priv->editor_settings,
|
|
XED_SETTINGS_BRACKET_MATCHING,
|
|
doc,
|
|
"highlight-matching-brackets",
|
|
G_SETTINGS_BIND_GET);
|
|
|
|
style_scheme = get_default_style_scheme (priv->editor_settings);
|
|
if (style_scheme != NULL)
|
|
{
|
|
gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (doc), style_scheme);
|
|
}
|
|
|
|
g_signal_connect (doc, "notify::content-type", G_CALLBACK (on_content_type_changed), NULL);
|
|
g_signal_connect (doc, "notify::location", G_CALLBACK (on_location_changed), NULL);
|
|
}
|
|
|
|
XedDocument *
|
|
xed_document_new (void)
|
|
{
|
|
return g_object_new (XED_TYPE_DOCUMENT, NULL);
|
|
}
|
|
|
|
static void
|
|
set_content_type_no_guess (XedDocument *doc,
|
|
const gchar *content_type)
|
|
{
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
if (doc->priv->content_type != NULL &&
|
|
content_type != NULL &&
|
|
g_str_equal (doc->priv->content_type, content_type))
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_free (doc->priv->content_type);
|
|
|
|
if (content_type == NULL || g_content_type_is_unknown (content_type))
|
|
{
|
|
doc->priv->content_type = get_default_content_type ();
|
|
}
|
|
else
|
|
{
|
|
doc->priv->content_type = g_strdup (content_type);
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (doc), "content-type");
|
|
}
|
|
|
|
/**
|
|
* xed_document_set_content_type:
|
|
* @doc:
|
|
* @content_type: (allow-none):
|
|
*/
|
|
void
|
|
xed_document_set_content_type (XedDocument *doc,
|
|
const gchar *content_type)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
if (content_type == NULL)
|
|
{
|
|
GFile *location;
|
|
gchar *guessed_type = NULL;
|
|
|
|
/* If content type is null, we guess from the filename */
|
|
location = xed_document_get_location (doc);
|
|
if (location != NULL)
|
|
{
|
|
gchar *basename;
|
|
|
|
basename = g_file_get_basename (location);
|
|
guessed_type = g_content_type_guess (basename, NULL, 0, NULL);
|
|
|
|
g_free (basename);
|
|
g_object_unref (location);
|
|
}
|
|
|
|
set_content_type_no_guess (doc, guessed_type);
|
|
|
|
g_free (guessed_type);
|
|
}
|
|
else
|
|
{
|
|
set_content_type_no_guess (doc, content_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_location (XedDocument *doc,
|
|
GFile *location)
|
|
{
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
g_return_if_fail ((location == NULL) || xed_utils_is_valid_location (location));
|
|
|
|
if (doc->priv->location == location)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_clear_object (&doc->priv->location);
|
|
|
|
if (location != NULL)
|
|
{
|
|
doc->priv->location = g_file_dup (location);
|
|
|
|
if (doc->priv->untitled_number > 0)
|
|
{
|
|
release_untitled_number (doc->priv->untitled_number);
|
|
doc->priv->untitled_number = 0;
|
|
}
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (doc), "location");
|
|
|
|
if (doc->priv->short_name == NULL)
|
|
{
|
|
g_object_notify (G_OBJECT (doc), "shortname");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xed_document_get_location:
|
|
* @doc: a #XedDocument
|
|
*
|
|
* Returns: (allow-none) (transfer full): a new #GFile
|
|
*/
|
|
GFile *
|
|
xed_document_get_location (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
|
|
return doc->priv->location == NULL ? NULL : g_file_dup (doc->priv->location);
|
|
}
|
|
|
|
void
|
|
xed_document_set_location (XedDocument *doc,
|
|
GFile *location)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
g_return_if_fail (G_IS_FILE (location));
|
|
|
|
set_location (doc, location);
|
|
xed_document_set_content_type (doc, NULL);
|
|
}
|
|
|
|
/**
|
|
* xed_document_get_uri_for_display:
|
|
* @doc:
|
|
*
|
|
* Note: this never returns %NULL.
|
|
**/
|
|
gchar *
|
|
xed_document_get_uri_for_display (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), g_strdup (""));
|
|
|
|
if (doc->priv->location == NULL)
|
|
{
|
|
return g_strdup_printf (_("Unsaved Document %d"), doc->priv->untitled_number);
|
|
}
|
|
else
|
|
{
|
|
return g_file_get_parse_name (doc->priv->location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xed_document_get_short_name_for_display:
|
|
* @doc:
|
|
*
|
|
* Note: this never returns %NULL.
|
|
**/
|
|
gchar *
|
|
xed_document_get_short_name_for_display (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), g_strdup (""));
|
|
|
|
if (doc->priv->short_name != NULL)
|
|
{
|
|
return g_strdup (doc->priv->short_name);
|
|
}
|
|
else if (doc->priv->location == NULL)
|
|
{
|
|
return g_strdup_printf (_("Unsaved Document %d"), doc->priv->untitled_number);
|
|
}
|
|
else
|
|
{
|
|
return xed_utils_basename_for_display (doc->priv->location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xed_document_set_short_name_for_display:
|
|
* @doc:
|
|
* @short_name: (allow-none):
|
|
*/
|
|
void
|
|
xed_document_set_short_name_for_display (XedDocument *doc,
|
|
const gchar *short_name)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
g_free (doc->priv->short_name);
|
|
doc->priv->short_name = g_strdup (short_name);
|
|
|
|
g_object_notify (G_OBJECT (doc), "shortname");
|
|
}
|
|
|
|
gchar *
|
|
xed_document_get_content_type (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
|
|
return g_strdup (doc->priv->content_type);
|
|
}
|
|
|
|
/**
|
|
* xed_document_get_mime_type:
|
|
* @doc:
|
|
*
|
|
* Note: this never returns %NULL.
|
|
**/
|
|
gchar *
|
|
xed_document_get_mime_type (XedDocument *doc)
|
|
{
|
|
gchar *mime_type = NULL;
|
|
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), g_strdup ("text/plain"));
|
|
|
|
if (doc->priv->content_type != NULL &&
|
|
!g_content_type_is_unknown (doc->priv->content_type))
|
|
{
|
|
mime_type = g_content_type_get_mime_type (doc->priv->content_type);
|
|
}
|
|
|
|
return mime_type != NULL ? mime_type : g_strdup ("text/plain");
|
|
}
|
|
|
|
/**
|
|
* _xed_document_set_readonly:
|
|
* @doc: a #XedDocument
|
|
* @readonly: the new setting.
|
|
*
|
|
* Sets whether the document is read-only.
|
|
*/
|
|
void
|
|
_xed_document_set_readonly (XedDocument *doc,
|
|
gboolean readonly)
|
|
{
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
readonly = readonly != FALSE;
|
|
|
|
if (doc->priv->readonly != readonly)
|
|
{
|
|
doc->priv->readonly = readonly;
|
|
g_object_notify (G_OBJECT (doc), "read-only");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
xed_document_get_readonly (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), TRUE);
|
|
|
|
return doc->priv->readonly;
|
|
}
|
|
|
|
static void
|
|
reset_temp_loading_data (XedDocument *doc)
|
|
{
|
|
/* the loader has been used, throw it away */
|
|
g_object_unref (doc->priv->loader);
|
|
doc->priv->loader = NULL;
|
|
|
|
doc->priv->requested_encoding = NULL;
|
|
doc->priv->requested_line_pos = 0;
|
|
}
|
|
|
|
static void
|
|
document_loader_loaded (XedDocumentLoader *loader,
|
|
const GError *error,
|
|
XedDocument *doc)
|
|
{
|
|
/* load was successful */
|
|
if (error == NULL ||
|
|
(error->domain == XED_DOCUMENT_ERROR &&
|
|
error->code == XED_DOCUMENT_ERROR_CONVERSION_FALLBACK))
|
|
{
|
|
GtkTextIter iter;
|
|
GFileInfo *info;
|
|
const gchar *content_type = NULL;
|
|
gboolean read_only = FALSE;
|
|
GTimeVal mtime = {0, 0};
|
|
|
|
info = xed_document_loader_get_info (loader);
|
|
|
|
if (info)
|
|
{
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
|
|
{
|
|
content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
|
|
}
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
|
|
{
|
|
g_file_info_get_modification_time (info, &mtime);
|
|
}
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
|
|
{
|
|
read_only = !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
|
|
}
|
|
}
|
|
|
|
doc->priv->mtime = mtime;
|
|
|
|
doc->priv->readonly = read_only;
|
|
|
|
g_get_current_time (&doc->priv->time_of_last_save_or_load);
|
|
|
|
doc->priv->externally_modified = FALSE;
|
|
doc->priv->deleted = FALSE;
|
|
|
|
set_encoding (doc, xed_document_loader_get_encoding (loader), (doc->priv->requested_encoding != NULL));
|
|
|
|
xed_document_set_content_type (doc, content_type);
|
|
|
|
xed_document_set_newline_type (doc, xed_document_loader_get_newline_type (loader));
|
|
|
|
/* move the cursor at the requested line if any */
|
|
if (doc->priv->requested_line_pos > 0)
|
|
{
|
|
/* line_pos - 1 because get_iter_at_line counts from 0 */
|
|
gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (doc),
|
|
&iter,
|
|
doc->priv->requested_line_pos - 1);
|
|
}
|
|
else
|
|
{
|
|
/* if enabled, to the position stored in the metadata */
|
|
if (g_settings_get_boolean (doc->priv->editor_settings, XED_SETTINGS_RESTORE_CURSOR_POSITION))
|
|
{
|
|
gchar *pos;
|
|
gint offset;
|
|
|
|
pos = xed_document_get_metadata (doc, XED_METADATA_ATTRIBUTE_POSITION);
|
|
|
|
offset = pos ? atoi (pos) : 0;
|
|
g_free (pos);
|
|
|
|
gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &iter, MAX (offset, 0));
|
|
|
|
/* 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_buffer_get_start_iter (GTK_TEXT_BUFFER (doc), &iter);
|
|
}
|
|
}
|
|
/* 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);
|
|
}
|
|
|
|
if (!doc->priv->language_set_by_user)
|
|
{
|
|
GtkSourceLanguage *language = guess_language (doc);
|
|
|
|
xed_debug_message (DEBUG_DOCUMENT, "Language: %s",
|
|
language != NULL ? gtk_source_language_get_name (language) : "None");
|
|
|
|
set_language (doc, language, FALSE);
|
|
}
|
|
}
|
|
|
|
/* special case creating a named new doc */
|
|
else if (doc->priv->create &&
|
|
(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND) &&
|
|
(g_file_has_uri_scheme (doc->priv->location, "file")))
|
|
{
|
|
reset_temp_loading_data (doc);
|
|
|
|
g_signal_emit (doc, document_signals[LOADED], 0, NULL);
|
|
|
|
return;
|
|
}
|
|
|
|
g_signal_emit (doc, document_signals[LOADED], 0, error);
|
|
|
|
reset_temp_loading_data (doc);
|
|
}
|
|
|
|
static void
|
|
document_loader_loading (XedDocumentLoader *loader,
|
|
gboolean completed,
|
|
const GError *error,
|
|
XedDocument *doc)
|
|
{
|
|
if (completed)
|
|
{
|
|
document_loader_loaded (loader, error, doc);
|
|
}
|
|
else
|
|
{
|
|
goffset size = 0;
|
|
goffset read;
|
|
GFileInfo *info;
|
|
|
|
info = xed_document_loader_get_info (loader);
|
|
|
|
if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
|
|
{
|
|
size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
|
|
}
|
|
|
|
read = xed_document_loader_get_bytes_read (loader);
|
|
|
|
g_signal_emit (doc, document_signals[LOADING], 0, read, size);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xed_document_load_real (XedDocument *doc,
|
|
GFile *location,
|
|
const XedEncoding *encoding,
|
|
gint line_pos,
|
|
gboolean create)
|
|
{
|
|
gchar *uri;
|
|
|
|
g_return_if_fail (doc->priv->loader == NULL);
|
|
|
|
uri = g_file_get_uri (location);
|
|
xed_debug_message (DEBUG_DOCUMENT, "load_real: uri = %s", uri);
|
|
g_free (uri);
|
|
|
|
/* create a loader. It will be destroyed when loading is completed */
|
|
doc->priv->loader = xed_document_loader_new (doc, location, encoding);
|
|
|
|
g_signal_connect (doc->priv->loader, "loading", G_CALLBACK (document_loader_loading), doc);
|
|
|
|
doc->priv->create = create;
|
|
doc->priv->requested_encoding = encoding;
|
|
doc->priv->requested_line_pos = line_pos;
|
|
|
|
xed_document_set_location (doc, location);
|
|
|
|
xed_document_loader_load (doc->priv->loader);
|
|
}
|
|
|
|
/**
|
|
* xed_document_load:
|
|
* @doc: the #XedDocument.
|
|
* @location: the location where to load the document from.
|
|
* @encoding: the #XedEncoding to encode the document.
|
|
* @line_pos: the line to show.
|
|
* @create: whether the document should be created if it doesn't exist.
|
|
*
|
|
* Load a document. This results in the "load" signal to be emitted.
|
|
*/
|
|
void
|
|
xed_document_load (XedDocument *doc,
|
|
GFile *location,
|
|
const XedEncoding *encoding,
|
|
gint line_pos,
|
|
gboolean create)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
g_return_if_fail (location != NULL);
|
|
g_return_if_fail (xed_utils_is_valid_location (location));
|
|
|
|
g_signal_emit (doc, document_signals[LOAD], 0, location, encoding, line_pos, create);
|
|
}
|
|
|
|
/**
|
|
* xed_document_load_cancel:
|
|
* @doc: the #XedDocument.
|
|
*
|
|
* Cancel load of a document.
|
|
*/
|
|
gboolean
|
|
xed_document_load_cancel (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
|
|
if (doc->priv->loader == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return xed_document_loader_cancel (doc->priv->loader);
|
|
}
|
|
|
|
static gboolean
|
|
has_invalid_chars (XedDocument *doc)
|
|
{
|
|
GtkTextBuffer *buffer;
|
|
GtkTextIter start;
|
|
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
if (doc->priv->error_tag == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
buffer = GTK_TEXT_BUFFER (doc);
|
|
|
|
gtk_text_buffer_get_start_iter (buffer, &start);
|
|
|
|
if (gtk_text_iter_begins_tag (&start, doc->priv->error_tag) ||
|
|
gtk_text_iter_forward_to_tag_toggle (&start, doc->priv->error_tag))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
document_saver_saving (XedDocumentSaver *saver,
|
|
gboolean completed,
|
|
const GError *error,
|
|
XedDocument *doc)
|
|
{
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
if (completed)
|
|
{
|
|
/* save was successful */
|
|
if (error == NULL)
|
|
{
|
|
GFile *location;
|
|
const gchar *content_type = NULL;
|
|
GTimeVal mtime = {0, 0};
|
|
GFileInfo *info;
|
|
|
|
location = xed_document_saver_get_location (saver);
|
|
set_location (doc, location);
|
|
g_object_unref (location);
|
|
|
|
info = xed_document_saver_get_info (saver);
|
|
|
|
if (info != NULL)
|
|
{
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
|
|
{
|
|
content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
|
|
}
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
|
|
{
|
|
g_file_info_get_modification_time (info, &mtime);
|
|
}
|
|
}
|
|
|
|
xed_document_set_content_type (doc, content_type);
|
|
doc->priv->mtime = mtime;
|
|
|
|
g_get_current_time (&doc->priv->time_of_last_save_or_load);
|
|
|
|
doc->priv->externally_modified = FALSE;
|
|
doc->priv->deleted = FALSE;
|
|
|
|
_xed_document_set_readonly (doc, FALSE);
|
|
|
|
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (doc), FALSE);
|
|
set_encoding (doc, doc->priv->requested_encoding, TRUE);
|
|
}
|
|
|
|
g_signal_emit (doc, document_signals[SAVED], 0, error);
|
|
|
|
/* the saver has been used, throw it away */
|
|
g_object_unref (doc->priv->saver);
|
|
doc->priv->saver = NULL;
|
|
}
|
|
else
|
|
{
|
|
goffset size = 0;
|
|
goffset written = 0;
|
|
|
|
size = xed_document_saver_get_file_size (saver);
|
|
written = xed_document_saver_get_bytes_written (saver);
|
|
|
|
xed_debug_message (DEBUG_DOCUMENT, "save progress: %" G_GINT64_FORMAT " of %" G_GINT64_FORMAT, written, size);
|
|
|
|
g_signal_emit (doc, document_signals[SAVING], 0, written, size);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xed_document_save_real (XedDocument *doc,
|
|
GFile *location,
|
|
const XedEncoding *encoding,
|
|
XedDocumentSaveFlags flags)
|
|
{
|
|
g_return_if_fail (doc->priv->saver == NULL);
|
|
|
|
if (!(flags & XED_DOCUMENT_SAVE_IGNORE_INVALID_CHARS) && has_invalid_chars (doc))
|
|
{
|
|
GError *error = NULL;
|
|
|
|
g_set_error_literal (&error,
|
|
XED_DOCUMENT_ERROR,
|
|
XED_DOCUMENT_ERROR_CONVERSION_FALLBACK,
|
|
"The document contains invalid characters");
|
|
|
|
g_signal_emit (doc, document_signals[SAVED], 0, error);
|
|
|
|
g_error_free (error);
|
|
}
|
|
else
|
|
{
|
|
/* create a saver, it will be destroyed once saving is complete */
|
|
doc->priv->saver = xed_document_saver_new (doc, location, encoding, doc->priv->newline_type, flags);
|
|
|
|
g_signal_connect (doc->priv->saver, "saving", G_CALLBACK (document_saver_saving), doc);
|
|
|
|
doc->priv->requested_encoding = encoding;
|
|
|
|
xed_document_saver_save (doc->priv->saver, &doc->priv->mtime);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xed_document_save:
|
|
* @doc: the #XedDocument.
|
|
* @flags: optionnal #XedDocumentSaveFlags.
|
|
*
|
|
* Save the document to its previous location. This results in the "save"
|
|
* signal to be emitted.
|
|
*/
|
|
void
|
|
xed_document_save (XedDocument *doc,
|
|
XedDocumentSaveFlags flags)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
g_return_if_fail (G_IS_FILE (doc->priv->location));
|
|
|
|
g_signal_emit (doc, document_signals[SAVE], 0, doc->priv->location, doc->priv->encoding, flags);
|
|
}
|
|
|
|
/**
|
|
* xed_document_save_as:
|
|
* @doc: the #XedDocument.
|
|
* @location: the location where to save the document.
|
|
* @encoding: the #XedEncoding to encode the document.
|
|
* @flags: optionnal #XedDocumentSaveFlags.
|
|
*
|
|
* Save the document to a new location. This results in the "save" signal
|
|
* to be emitted.
|
|
*/
|
|
void
|
|
xed_document_save_as (XedDocument *doc,
|
|
GFile *location,
|
|
const XedEncoding *encoding,
|
|
XedDocumentSaveFlags flags)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
g_return_if_fail (G_IS_FILE (location));
|
|
g_return_if_fail (encoding != NULL);
|
|
|
|
if (has_invalid_chars (doc))
|
|
{
|
|
g_set_error_literal (&error,
|
|
XED_DOCUMENT_ERROR,
|
|
XED_DOCUMENT_ERROR_CONVERSION_FALLBACK,
|
|
"The document contains invalid chars");
|
|
}
|
|
|
|
/* priv->mtime refers to the the old location (if any). Thus, it should be
|
|
* ignored when saving as. */
|
|
g_signal_emit (doc, document_signals[SAVE], 0, location, encoding, flags | XED_DOCUMENT_SAVE_IGNORE_MTIME, error);
|
|
|
|
if (error != NULL)
|
|
{
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
xed_document_is_untouched (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), TRUE);
|
|
|
|
return (doc->priv->location == NULL) && (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)));
|
|
}
|
|
|
|
gboolean
|
|
xed_document_is_untitled (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), TRUE);
|
|
|
|
return doc->priv->location == NULL;
|
|
}
|
|
|
|
gboolean
|
|
xed_document_is_local (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
|
|
if (doc->priv->location == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return g_file_has_uri_scheme (doc->priv->location, "file");
|
|
}
|
|
|
|
static void
|
|
check_file_on_disk (XedDocument *doc)
|
|
{
|
|
GFileInfo *info;
|
|
|
|
if (doc->priv->location == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
info = g_file_query_info (doc->priv->location,
|
|
G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
|
|
G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
NULL, NULL);
|
|
|
|
if (info != NULL)
|
|
{
|
|
/* While at it also check if permissions changed */
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
|
|
{
|
|
gboolean read_only;
|
|
|
|
read_only = !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
|
|
|
|
_xed_document_set_readonly (doc, read_only);
|
|
}
|
|
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
|
|
{
|
|
GTimeVal timeval;
|
|
|
|
g_file_info_get_modification_time (info, &timeval);
|
|
|
|
if (timeval.tv_sec > doc->priv->mtime.tv_sec ||
|
|
(timeval.tv_sec == doc->priv->mtime.tv_sec &&
|
|
timeval.tv_usec > doc->priv->mtime.tv_usec))
|
|
{
|
|
doc->priv->externally_modified = TRUE;
|
|
}
|
|
}
|
|
|
|
g_object_unref (info);
|
|
}
|
|
else
|
|
{
|
|
doc->priv->deleted = TRUE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
_xed_document_check_externally_modified (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
|
|
if (!doc->priv->externally_modified)
|
|
{
|
|
check_file_on_disk (doc);
|
|
}
|
|
|
|
return doc->priv->externally_modified;
|
|
}
|
|
|
|
gboolean
|
|
xed_document_get_deleted (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
|
|
if (!doc->priv->deleted)
|
|
{
|
|
check_file_on_disk (doc);
|
|
}
|
|
|
|
return doc->priv->deleted;
|
|
}
|
|
|
|
/*
|
|
* Deletion and external modification is only checked for local files.
|
|
*/
|
|
gboolean
|
|
_xed_document_needs_saving (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
|
|
if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (doc->priv->externally_modified || doc->priv->deleted)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (xed_document_is_local (doc))
|
|
{
|
|
check_file_on_disk (doc);
|
|
|
|
if (doc->priv->externally_modified || doc->priv->deleted)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If @line is bigger than the lines of the document, the cursor is moved
|
|
* to the last line and FALSE is returned.
|
|
*/
|
|
gboolean
|
|
xed_document_goto_line (XedDocument *doc,
|
|
gint line)
|
|
{
|
|
gboolean ret = TRUE;
|
|
guint line_count;
|
|
GtkTextIter iter;
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
g_return_val_if_fail (line >= -1, FALSE);
|
|
|
|
line_count = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc));
|
|
|
|
if (line >= line_count)
|
|
{
|
|
ret = FALSE;
|
|
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &iter);
|
|
}
|
|
else
|
|
{
|
|
gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (doc), &iter, line);
|
|
}
|
|
|
|
gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
xed_document_goto_line_offset (XedDocument *doc,
|
|
gint line,
|
|
gint line_offset)
|
|
{
|
|
gboolean ret = TRUE;
|
|
guint offset_count;
|
|
GtkTextIter iter;
|
|
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), FALSE);
|
|
g_return_val_if_fail (line >= -1, FALSE);
|
|
g_return_val_if_fail (line_offset >= -1, FALSE);
|
|
|
|
gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (doc), &iter, line);
|
|
|
|
offset_count = gtk_text_iter_get_chars_in_line (&iter);
|
|
if (line_offset > offset_count)
|
|
{
|
|
ret = FALSE;
|
|
}
|
|
else
|
|
{
|
|
gtk_text_iter_set_line_offset (&iter, line_offset);
|
|
}
|
|
|
|
gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
get_style_colors (XedDocument *doc,
|
|
const gchar *style_name,
|
|
gboolean *foreground_set,
|
|
GdkRGBA *foreground,
|
|
gboolean *background_set,
|
|
GdkRGBA *background,
|
|
gboolean *line_background_set,
|
|
GdkRGBA *line_background,
|
|
gboolean *bold_set,
|
|
gboolean *bold,
|
|
gboolean *italic_set,
|
|
gboolean *italic,
|
|
gboolean *underline_set,
|
|
gboolean *underline,
|
|
gboolean *strikethrough_set,
|
|
gboolean *strikethrough)
|
|
{
|
|
GtkSourceStyleScheme *style_scheme;
|
|
GtkSourceStyle *style;
|
|
gchar *line_bg;
|
|
gchar *bg;
|
|
gchar *fg;
|
|
|
|
style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (doc));
|
|
if (style_scheme == NULL)
|
|
{
|
|
goto fallback;
|
|
}
|
|
|
|
style = gtk_source_style_scheme_get_style (style_scheme, style_name);
|
|
if (style == NULL)
|
|
{
|
|
goto fallback;
|
|
}
|
|
|
|
g_object_get (style,
|
|
"foreground-set", foreground_set,
|
|
"foreground", &fg,
|
|
"background-set", background_set,
|
|
"background", &bg,
|
|
"line-background-set", line_background_set,
|
|
"line-background", &line_bg,
|
|
"bold-set", bold_set,
|
|
"bold", bold,
|
|
"italic-set", italic_set,
|
|
"italic", italic,
|
|
"underline-set", underline_set,
|
|
"underline", underline,
|
|
"strikethrough-set", strikethrough_set,
|
|
"strikethrough", strikethrough,
|
|
NULL);
|
|
|
|
if (*foreground_set)
|
|
{
|
|
if (fg == NULL || !gdk_rgba_parse (foreground, fg))
|
|
{
|
|
*foreground_set = FALSE;
|
|
}
|
|
}
|
|
|
|
if (*background_set)
|
|
{
|
|
if (bg == NULL || !gdk_rgba_parse (background, bg))
|
|
{
|
|
*background_set = FALSE;
|
|
}
|
|
}
|
|
|
|
if (*line_background_set)
|
|
{
|
|
if (line_bg == NULL || !gdk_rgba_parse (background, line_bg))
|
|
{
|
|
*line_background_set = FALSE;
|
|
}
|
|
}
|
|
|
|
g_free (fg);
|
|
g_free (bg);
|
|
g_free (line_bg);
|
|
|
|
return;
|
|
|
|
fallback:
|
|
xed_debug_message (DEBUG_DOCUMENT,
|
|
"Falling back to hard-coded colors "
|
|
"for the \"found\" text tag.");
|
|
|
|
gdk_rgba_parse (background, "#FFFF78");
|
|
*background_set = TRUE;
|
|
*foreground_set = FALSE;
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sync_tag_style (XedDocument *doc,
|
|
GtkTextTag *tag,
|
|
const gchar *style_name)
|
|
{
|
|
GdkRGBA fg;
|
|
GdkRGBA bg;
|
|
GdkRGBA line_bg;
|
|
gboolean fg_set;
|
|
gboolean bg_set;
|
|
gboolean line_bg_set;
|
|
gboolean bold;
|
|
gboolean italic;
|
|
gboolean underline;
|
|
gboolean strikethrough;
|
|
gboolean bold_set;
|
|
gboolean italic_set;
|
|
gboolean underline_set;
|
|
gboolean strikethrough_set;
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
g_return_if_fail (tag != NULL);
|
|
|
|
get_style_colors (doc,
|
|
style_name,
|
|
&fg_set, &fg,
|
|
&bg_set, &bg,
|
|
&line_bg_set, &line_bg,
|
|
&bold_set, &bold,
|
|
&italic_set, &italic,
|
|
&underline_set, &underline,
|
|
&strikethrough_set, &strikethrough);
|
|
|
|
g_object_freeze_notify (G_OBJECT (tag));
|
|
|
|
g_object_set (tag,
|
|
"foreground-rgba", fg_set ? &fg : NULL,
|
|
"background-rgba", bg_set ? &bg : NULL,
|
|
"paragraph-background-rgba", line_bg_set ? &line_bg : NULL,
|
|
"weight", bold_set && bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
|
|
"style", italic_set && italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
|
|
"underline", underline_set && underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE,
|
|
"strikethrough", strikethrough_set && strikethrough,
|
|
NULL);
|
|
|
|
g_object_thaw_notify (G_OBJECT (tag));
|
|
}
|
|
|
|
static void
|
|
text_tag_set_highest_priority (GtkTextTag *tag,
|
|
GtkTextBuffer *buffer)
|
|
{
|
|
GtkTextTagTable *table;
|
|
gint n;
|
|
|
|
table = gtk_text_buffer_get_tag_table (buffer);
|
|
n = gtk_text_tag_table_get_size (table);
|
|
gtk_text_tag_set_priority (tag, n - 1);
|
|
}
|
|
|
|
/**
|
|
* xed_document_set_language:
|
|
* @doc:
|
|
* @lang: (allow-none):
|
|
**/
|
|
void
|
|
xed_document_set_language (XedDocument *doc,
|
|
GtkSourceLanguage *lang)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
set_language (doc, lang, TRUE);
|
|
}
|
|
|
|
/**
|
|
* xed_document_get_language:
|
|
* @doc:
|
|
*
|
|
* Return value: (transfer none):
|
|
*/
|
|
GtkSourceLanguage *
|
|
xed_document_get_language (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
|
|
return gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc));
|
|
}
|
|
|
|
const XedEncoding *
|
|
xed_document_get_encoding (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
|
|
return doc->priv->encoding;
|
|
}
|
|
|
|
glong
|
|
_xed_document_get_seconds_since_last_save_or_load (XedDocument *doc)
|
|
{
|
|
GTimeVal current_time;
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), -1);
|
|
|
|
g_get_current_time (¤t_time);
|
|
|
|
return (current_time.tv_sec - doc->priv->time_of_last_save_or_load.tv_sec);
|
|
}
|
|
|
|
void
|
|
xed_document_set_newline_type (XedDocument *doc,
|
|
XedDocumentNewlineType newline_type)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
if (doc->priv->newline_type != newline_type)
|
|
{
|
|
doc->priv->newline_type = newline_type;
|
|
g_object_notify (G_OBJECT (doc), "newline-type");
|
|
}
|
|
}
|
|
|
|
XedDocumentNewlineType
|
|
xed_document_get_newline_type (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), 0);
|
|
|
|
return doc->priv->newline_type;
|
|
}
|
|
|
|
void
|
|
_xed_document_set_mount_operation_factory (XedDocument *doc,
|
|
XedMountOperationFactory callback,
|
|
gpointer userdata)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
doc->priv->mount_operation_factory = callback;
|
|
doc->priv->mount_operation_userdata = userdata;
|
|
}
|
|
|
|
GMountOperation *
|
|
_xed_document_create_mount_operation (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
|
|
if (doc->priv->mount_operation_factory == NULL)
|
|
{
|
|
return g_mount_operation_new ();
|
|
}
|
|
else
|
|
{
|
|
return doc->priv->mount_operation_factory (doc, doc->priv->mount_operation_userdata);
|
|
}
|
|
}
|
|
|
|
#ifndef ENABLE_GVFS_METADATA
|
|
gchar *
|
|
xed_document_get_metadata (XedDocument *doc,
|
|
const gchar *key)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
g_return_val_if_fail (key != NULL, NULL);
|
|
|
|
if (doc->priv->location != NULL)
|
|
{
|
|
return xed_metadata_manager_get (doc->priv->location, key);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
xed_document_set_metadata (XedDocument *doc,
|
|
const gchar *first_key,
|
|
...)
|
|
{
|
|
const gchar *key;
|
|
const gchar *value;
|
|
va_list var_args;
|
|
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
g_return_if_fail (first_key != NULL);
|
|
|
|
if (doc->priv->location == NULL)
|
|
{
|
|
/* Can't set metadata for untitled documents */
|
|
return;
|
|
}
|
|
|
|
va_start (var_args, first_key);
|
|
|
|
for (key = first_key; key; key = va_arg (var_args, const gchar *))
|
|
{
|
|
value = va_arg (var_args, const gchar *);
|
|
|
|
if (doc->priv->location != NULL)
|
|
{
|
|
xed_metadata_manager_set (doc->priv->location, key, value);
|
|
}
|
|
}
|
|
|
|
va_end (var_args);
|
|
}
|
|
|
|
#else
|
|
|
|
/**
|
|
* xed_document_get_metadata:
|
|
* @doc: a #XedDocument
|
|
* @key: name of the key
|
|
*
|
|
* Gets the metadata assigned to @key.
|
|
*
|
|
* Returns: the value assigned to @key.
|
|
*/
|
|
gchar *
|
|
xed_document_get_metadata (XedDocument *doc,
|
|
const gchar *key)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
g_return_val_if_fail (key != NULL, NULL);
|
|
|
|
if (doc->priv->metadata_info && g_file_info_has_attribute (doc->priv->metadata_info, key))
|
|
{
|
|
return g_strdup (g_file_info_get_attribute_string (doc->priv->metadata_info, key));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
set_attributes_cb (GFile *location,
|
|
GAsyncResult *result)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
g_file_set_attributes_finish (location, result, NULL, &error);
|
|
|
|
if (error != NULL)
|
|
{
|
|
g_warning ("Set document metadata failed: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xed_document_set_metadata:
|
|
* @doc: a #XedDocument
|
|
* @first_key: name of the first key to set
|
|
* @...: value for the first key, followed optionally by more key/value pairs,
|
|
* followed by %NULL.
|
|
*
|
|
* Sets metadata on a document.
|
|
*/
|
|
void
|
|
xed_document_set_metadata (XedDocument *doc,
|
|
const gchar *first_key,
|
|
...)
|
|
{
|
|
const gchar *key;
|
|
const gchar *value;
|
|
va_list var_args;
|
|
GFileInfo *info;
|
|
GFile *location;
|
|
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
g_return_if_fail (first_key != NULL);
|
|
|
|
info = g_file_info_new ();
|
|
|
|
va_start (var_args, first_key);
|
|
|
|
for (key = first_key; key; key = va_arg (var_args, const gchar *))
|
|
{
|
|
value = va_arg (var_args, const gchar *);
|
|
|
|
if (value != NULL)
|
|
{
|
|
g_file_info_set_attribute_string (info, key, value);
|
|
}
|
|
else
|
|
{
|
|
/* Unset the key */
|
|
g_file_info_remove_attribute (info, key);
|
|
}
|
|
}
|
|
|
|
va_end (var_args);
|
|
|
|
if (doc->priv->metadata_info != NULL)
|
|
{
|
|
g_file_info_copy_into (info, doc->priv->metadata_info);
|
|
}
|
|
|
|
location = xed_document_get_location (doc);
|
|
|
|
if (location != NULL)
|
|
{
|
|
g_file_set_attributes_async (location,
|
|
info,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
(GAsyncReadyCallback) set_attributes_cb,
|
|
NULL);
|
|
|
|
g_object_unref (location);
|
|
}
|
|
|
|
g_object_unref (info);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
sync_error_tag (XedDocument *doc,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
sync_tag_style (doc, doc->priv->error_tag, "def:error");
|
|
}
|
|
|
|
void
|
|
_xed_document_apply_error_style (XedDocument *doc,
|
|
GtkTextIter *start,
|
|
GtkTextIter *end)
|
|
{
|
|
GtkTextBuffer *buffer;
|
|
|
|
xed_debug (DEBUG_DOCUMENT);
|
|
|
|
buffer = GTK_TEXT_BUFFER (doc);
|
|
|
|
if (doc->priv->error_tag == NULL)
|
|
{
|
|
doc->priv->error_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (doc), "error-style", NULL);
|
|
|
|
sync_error_tag (doc, NULL, NULL);
|
|
|
|
g_signal_connect (doc, "notify::style-scheme",
|
|
G_CALLBACK (sync_error_tag), NULL);
|
|
}
|
|
|
|
/* make sure the 'error' tag has the priority over
|
|
* syntax highlighting tags */
|
|
text_tag_set_highest_priority (doc->priv->error_tag, GTK_TEXT_BUFFER (doc));
|
|
|
|
gtk_text_buffer_apply_tag (buffer, doc->priv->error_tag, start, end);
|
|
}
|
|
|
|
/**
|
|
* xed_document_set_search_context:
|
|
* @doc: a #XedDocument
|
|
* @search_context: (allow-none): the new #GtkSourceSearchContext
|
|
*
|
|
* Sets the new search context for the document.
|
|
*/
|
|
void
|
|
xed_document_set_search_context (XedDocument *doc,
|
|
GtkSourceSearchContext *search_context)
|
|
{
|
|
g_return_if_fail (XED_IS_DOCUMENT (doc));
|
|
|
|
g_clear_object (&doc->priv->search_context);
|
|
doc->priv->search_context = search_context;
|
|
|
|
if (search_context != NULL)
|
|
{
|
|
gboolean highlight = g_settings_get_boolean (doc->priv->editor_settings, XED_SETTINGS_SEARCH_HIGHLIGHTING);
|
|
|
|
gtk_source_search_context_set_highlight (search_context, highlight);
|
|
|
|
g_object_ref (search_context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xed_document_get_search_context:
|
|
* @doc: a #XedDocument
|
|
*
|
|
* Returns: the current search context of the document,
|
|
* or NULL if there is no search context
|
|
*/
|
|
GtkSourceSearchContext *
|
|
xed_document_get_search_context (XedDocument *doc)
|
|
{
|
|
g_return_val_if_fail (XED_IS_DOCUMENT (doc), NULL);
|
|
|
|
return doc->priv->search_context;
|
|
}
|