[gedit] Add invalid char support.



commit dc31390ad2d252082d555f57f65a80f3da7af027
Author: Ignacio Casal Quinteiro <icq gnome org>
Date:   Sun Dec 12 16:04:09 2010 +0100

    Add invalid char support.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=502947

 gedit/gedit-document-loader.c        |   25 ++--
 gedit/gedit-document-output-stream.c |  207 ++++++++++++++++++++++------------
 gedit/gedit-document.c               |  151 ++++++++++++++++++++-----
 gedit/gedit-document.h               |    8 +-
 gedit/gedit-io-error-info-bar.c      |  104 +++++++++++++++--
 gedit/gedit-io-error-info-bar.h      |    2 +
 gedit/gedit-tab.c                    |   83 +++++++++++---
 tests/document-output-stream.c       |   17 ++-
 8 files changed, 456 insertions(+), 141 deletions(-)
---
diff --git a/gedit/gedit-document-loader.c b/gedit/gedit-document-loader.c
index fb30151..0371af0 100644
--- a/gedit/gedit-document-loader.c
+++ b/gedit/gedit-document-loader.c
@@ -491,6 +491,18 @@ close_input_stream_ready_cb (GInputStream *stream,
 		return;
 	}
 
+	/* Check if we needed some fallback char, if so, check if there was
+	   a previous error and if not set a fallback used error */
+	if ((gedit_document_output_stream_get_num_fallbacks (GEDIT_DOCUMENT_OUTPUT_STREAM (async->loader->priv->output)) != 0) &&
+	    async->loader->priv->error == NULL)
+	{
+		g_set_error_literal (&async->loader->priv->error,
+		                     GEDIT_DOCUMENT_ERROR,
+		                     GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
+		                     "There was a conversion error and it was "
+		                     "needed to use a fallback char");
+	}
+
 	loader_load_completed_or_failed (async->loader, async);
 }
 
@@ -621,18 +633,7 @@ async_read_cb (GInputStream *stream,
 		loader->priv->auto_detected_newline_type =
 			gedit_document_output_stream_detect_newline_type (GEDIT_DOCUMENT_OUTPUT_STREAM (loader->priv->output));
 
-		/* Check if we needed some fallback char, if so, check if there was
-		   a previous error and if not set a fallback used error */
-		/* FIXME Uncomment this when we want to manage conversion fallback */
-		/*if ((gedit_document_output_stream_get_num_fallbacks (GEDIT_DOCUMENT_OUTPUT_STREAM (loader->priv->output)) != 0) &&
-		    loader->priv->error == NULL)
-		{
-			g_set_error_literal (&loader->priv->error,
-					     GEDIT_DOCUMENT_ERROR,
-					     GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
-					     "There was a conversion error and it was "
-					     "needed to use a fallback char");
-		}*/
+		
 
 		write_complete (async);
 
diff --git a/gedit/gedit-document-output-stream.c b/gedit/gedit-document-output-stream.c
index 52d7fad..02e6c9d 100644
--- a/gedit/gedit-document-output-stream.c
+++ b/gedit/gedit-document-output-stream.c
@@ -57,6 +57,9 @@ struct _GeditDocumentOutputStreamPrivate
 	GSList *encodings;
 	GSList *current_encoding;
 
+	gint error_offset;
+	guint n_fallback_errors;
+
 	guint is_utf8 : 1;
 	guint use_first : 1;
 
@@ -221,6 +224,8 @@ gedit_document_output_stream_init (GeditDocumentOutputStream *stream)
 	stream->priv->encodings = NULL;
 	stream->priv->current_encoding = NULL;
 
+	stream->priv->error_offset = -1;
+
 	stream->priv->is_initialized = FALSE;
 	stream->priv->is_closed = FALSE;
 	stream->priv->is_utf8 = FALSE;
@@ -244,7 +249,10 @@ get_encoding (GeditDocumentOutputStream *stream)
 		return (const GeditEncoding *)stream->priv->current_encoding->data;
 	}
 
-	return NULL;
+	stream->priv->use_first = TRUE;
+	stream->priv->current_encoding = stream->priv->encodings;
+
+	return (const GeditEncoding *)stream->priv->current_encoding->data;
 }
 
 static gboolean
@@ -497,77 +505,135 @@ gedit_document_output_stream_get_guessed (GeditDocumentOutputStream *stream)
 guint
 gedit_document_output_stream_get_num_fallbacks (GeditDocumentOutputStream *stream)
 {
-	g_return_val_if_fail (GEDIT_IS_DOCUMENT_OUTPUT_STREAM (stream), FALSE);
+	g_return_val_if_fail (GEDIT_IS_DOCUMENT_OUTPUT_STREAM (stream), 0);
+
+	return stream->priv->n_fallback_errors;
+}
 
-	if (stream->priv->charset_conv == NULL)
+static void
+apply_error_tag (GeditDocumentOutputStream *stream)
+{
+	GtkTextIter start;
+
+	if (stream->priv->error_offset == -1)
 	{
-		return FALSE;
+		return;
 	}
 
-	return g_charset_converter_get_num_fallbacks (stream->priv->charset_conv) != 0;
+	gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (stream->priv->doc),
+	                                    &start, stream->priv->error_offset);
+
+	_gedit_document_apply_error_style (stream->priv->doc,
+	                                   &start, &stream->priv->pos);
+
+	stream->priv->error_offset = -1;
 }
 
-static gboolean
+static void
+insert_fallback (GeditDocumentOutputStream *stream,
+                 const gchar               *buffer)
+{
+	guint8 out[4];
+	guint8 v;
+	const gchar hex[] = "0123456789ABCDEF";
+
+	/* if we are here is because we are pointing to an invalid char
+	 * so we substitute it by an hex value */
+	v = *(guint8 *)buffer;
+	out[0] = '\\';
+	out[1] = hex[(v & 0xf0) >> 4];
+	out[2] = hex[(v & 0x0f) >> 0];
+	out[3] = '\0';
+
+	gtk_text_buffer_insert (GTK_TEXT_BUFFER (stream->priv->doc),
+	                        &stream->priv->pos, (const gchar *)out, 3);
+
+	stream->priv->n_fallback_errors++;
+}
+
+static void
 validate_and_insert (GeditDocumentOutputStream *stream,
                      const gchar               *buffer,
                      gsize                      count)
 {
-	const gchar *end;
-	gsize nvalid;
-	gboolean valid;
+	GtkTextBuffer *text_buffer;
+	GtkTextIter   *iter;
 	gsize len;
 
+	text_buffer = GTK_TEXT_BUFFER (stream->priv->doc);
+	iter = &stream->priv->pos;
 	len = count;
 
-	/* validate */
-	valid = g_utf8_validate (buffer, len, &end);
-	nvalid = end - buffer;
-
-	if (!valid)
+	while (len != 0)
 	{
-		gsize remainder;
+		const gchar *end;
+		gboolean valid;
+		gsize nvalid;
 
-		remainder = len - nvalid;
+		/* validate */
+		valid = g_utf8_validate (buffer, len, &end);
+		nvalid = end - buffer;
 
-		if ((remainder < MAX_UNICHAR_LEN) &&
-		    (g_utf8_get_char_validated (buffer + nvalid, remainder) == (gunichar)-2))
+		/* Note: this is a workaround for a 'bug' in GtkTextBuffer where
+		   inserting first a \r and then in a second insert, a \n,
+		   will result in two lines being added instead of a single
+		   one */
+
+		if (valid)
 		{
-			stream->priv->buffer = g_strndup (end, remainder);
-			stream->priv->buflen = remainder;
-			len -= remainder;
+			gchar *ptr;
+
+			ptr = g_utf8_find_prev_char (buffer, buffer + len);
+
+			if (ptr && *ptr == '\r' && ptr - buffer == len - 1)
+			{
+				stream->priv->buffer = g_new (gchar, 1);
+				stream->priv->buffer[0] = '\r';
+				stream->priv->buflen = 1;
+
+				/* Decrease also the len so in the check
+				   nvalid == len we get out of this method */
+				--nvalid;
+				--len;
+			}
 		}
-		else
+
+		/* if we've got any valid char we must tag the invalid chars */
+		if (nvalid > 0)
 		{
-			return FALSE;
+			apply_error_tag (stream);
 		}
-	}
-	else
-	{
-		gchar *ptr;
 
-		/* Note: this is a workaround for a 'bug' in GtkTextBuffer where
-		   inserting first a \r and then in a second insert, a \n,
-		   will result in two lines being added instead of a single
-		   one */
+		gtk_text_buffer_insert (text_buffer, iter, buffer, nvalid);
 
-		ptr = g_utf8_find_prev_char (buffer, buffer + len);
+		/* If we inserted all return */
+		if (nvalid == len)
+		{
+			break;
+		}
+
+		buffer += nvalid;
+		len = len - nvalid;
 
-		if (ptr && *ptr == '\r' && ptr - buffer == len - 1)
+		if ((len < MAX_UNICHAR_LEN) &&
+		    (g_utf8_get_char_validated (buffer, len) == (gunichar)-2))
 		{
-			stream->priv->buffer = g_new (gchar, 1);
-			stream->priv->buffer[0] = '\r';
-			stream->priv->buflen = 1;
+			stream->priv->buffer = g_strndup (end, len);
+			stream->priv->buflen = len;
 
-			--len;
+			break;
 		}
-	}
 
-	gtk_text_buffer_insert (GTK_TEXT_BUFFER (stream->priv->doc),
-	                        &stream->priv->pos,
-	                        buffer,
-	                        len);
+		/* we need the start of the chunk of invalid chars */
+		if (stream->priv->error_offset == -1)
+		{
+			stream->priv->error_offset = gtk_text_iter_get_offset (&stream->priv->pos);
+		}
 
-	return TRUE;
+		insert_fallback (stream, buffer);
+		buffer++;
+		len--;
+	}
 }
 
 /* If the last char is a newline, remove it from the buffer (otherwise
@@ -775,21 +841,7 @@ gedit_document_output_stream_write (GOutputStream            *stream,
 		freetext = TRUE;
 	}
 
-	if (!validate_and_insert (ostream, text, len))
-	{
-		/* TODO: we could escape invalid text and tag it in red
-		 * and make the doc readonly.
-		 */
-		g_set_error (error, G_IO_ERROR,
-		             G_IO_ERROR_INVALID_DATA,
-		             _("Invalid UTF-8 sequence in input"));
-
-		if (freetext)
-		{
-			g_free (text);
-		}
-		return -1;
-	}
+	validate_and_insert (ostream, text, len);
 
 	if (freetext)
 	{
@@ -813,12 +865,33 @@ gedit_document_output_stream_flush (GOutputStream  *stream,
 		return TRUE;
 	}
 
-	if (ostream->priv->buflen == 0)
+	if (ostream->priv->buflen > 0 && *ostream->priv->buffer != '\r')
 	{
-		return TRUE;
+		/* If we reached here is because the last insertion was a half
+		   correct char, which has to be inserted as fallback */
+		gchar *text;
+
+		if (ostream->priv->error_offset == -1)
+		{
+			ostream->priv->error_offset = gtk_text_iter_get_offset (&ostream->priv->pos);
+		}
+
+		text = ostream->priv->buffer;
+		while (ostream->priv->buflen != 0)
+		{
+			insert_fallback (ostream, text);
+			text++;
+			ostream->priv->buflen--;
+		}
+
+		g_free (ostream->priv->buffer);
+		ostream->priv->buffer = NULL;
 	}
 	else if (ostream->priv->buflen == 1 && *ostream->priv->buffer == '\r')
 	{
+		/* The previous chars can be invalid */
+		apply_error_tag (ostream);
+
 		/* See special case above, flush this */
 		gtk_text_buffer_insert (GTK_TEXT_BUFFER (ostream->priv->doc),
 		                        &ostream->priv->pos,
@@ -828,19 +901,11 @@ gedit_document_output_stream_flush (GOutputStream  *stream,
 		g_free (ostream->priv->buffer);
 		ostream->priv->buffer = NULL;
 		ostream->priv->buflen = 0;
-
-		return TRUE;
 	}
-	else
-	{
-		/* Conversion error */
-		g_set_error (error,
-		             G_IO_ERROR,
-		             G_IO_ERROR_INVALID_DATA,
-		             _("Incomplete UTF-8 sequence in input"));
 
-		return FALSE;
-	}
+	apply_error_tag (ostream);
+
+	return TRUE;
 }
 
 static gboolean
diff --git a/gedit/gedit-document.c b/gedit/gedit-document.c
index 0f84a2f..ae2783c 100644
--- a/gedit/gedit-document.c
+++ b/gedit/gedit-document.c
@@ -138,15 +138,17 @@ struct _GeditDocumentPrivate
 	GeditTextRegion *to_search_region;
 	GtkTextTag      *found_tag;
 
+	GtkTextTag      *error_tag;
+
 	/* Mount operation factory */
 	GeditMountOperationFactory  mount_operation_factory;
 	gpointer		    mount_operation_userdata;
 
-	gint readonly : 1;
-	gint last_save_was_manually : 1; 
-	gint language_set_by_user : 1;
-	gint stop_cursor_moved_emission : 1;
-	gint dispose_has_run : 1;
+	guint readonly : 1;
+	guint last_save_was_manually : 1;
+	guint language_set_by_user : 1;
+	guint stop_cursor_moved_emission : 1;
+	guint dispose_has_run : 1;
 };
 
 enum {
@@ -1336,15 +1338,15 @@ set_readonly (GeditDocument *doc,
 }
 
 /**
- * gedit_document_set_readonly:
+ * _gedit_document_set_readonly:
  * @doc: a #GeditDocument
- * @readonly: %TRUE to se the document as read-only
+ * @readonly: %TRUE to set the document as read-only
  *
  * If @readonly is %TRUE sets @doc as read-only.
  */
 void
 _gedit_document_set_readonly (GeditDocument *doc,
-			      gboolean       readonly)
+                              gboolean       readonly)
 {
 	gedit_debug (DEBUG_DOCUMENT);
 
@@ -1704,6 +1706,29 @@ gedit_document_load_cancel (GeditDocument *doc)
 	return gedit_document_loader_cancel (doc->priv->loader);
 }
 
+static gboolean
+has_invalid_chars (GeditDocument *doc)
+{
+	GtkTextBuffer *buffer;
+	GtkTextIter start;
+
+	g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE);
+
+	gedit_debug (DEBUG_DOCUMENT);
+
+	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 (GeditDocumentSaver *saver,
 		       gboolean            completed,
@@ -1790,25 +1815,42 @@ gedit_document_save_real (GeditDocument                *doc,
 {
 	g_return_if_fail (doc->priv->saver == NULL);
 
-	/* create a saver, it will be destroyed once saving is complete */
-	doc->priv->saver = gedit_document_saver_new (doc,
-	                                             location,
-	                                             encoding,
-	                                             newline_type,
-	                                             compression_type,
-	                                             flags);
-
-	g_signal_connect (doc->priv->saver,
-			  "saving",
-			  G_CALLBACK (document_saver_saving),
-			  doc);
+	if (!(flags & GEDIT_DOCUMENT_SAVE_IGNORE_INVALID_CHARS) && has_invalid_chars (doc))
+	{
+		GError *error = NULL;
 
-	doc->priv->requested_encoding = encoding;
-	doc->priv->newline_type = newline_type;
-	doc->priv->compression_type = compression_type;
+		g_set_error_literal (&error,
+		                     GEDIT_DOCUMENT_ERROR,
+		                     GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
+		                     "The document contains invalid characters");
+
+		g_signal_emit (doc,
+		               document_signals[SAVED],
+		               0,
+		               error);
+	}
+	else
+	{
+		/* create a saver, it will be destroyed once saving is complete */
+		doc->priv->saver = gedit_document_saver_new (doc,
+		                                             location,
+		                                             encoding,
+		                                             newline_type,
+		                                             compression_type,
+		                                             flags);
+
+		g_signal_connect (doc->priv->saver,
+		                  "saving",
+		                  G_CALLBACK (document_saver_saving),
+		                  doc);
+
+		doc->priv->requested_encoding = encoding;
+		doc->priv->newline_type = newline_type;
+		doc->priv->compression_type = compression_type;
 
-	gedit_document_saver_save (doc->priv->saver,
-				   &doc->priv->mtime);
+		gedit_document_saver_save (doc->priv->saver,
+		                           &doc->priv->mtime);
+	}
 }
 
 /**
@@ -1855,10 +1897,20 @@ gedit_document_save_as (GeditDocument                *doc,
 			GeditDocumentCompressionType  compression_type,
 			GeditDocumentSaveFlags        flags)
 {
+	GError *error = NULL;
+
 	g_return_if_fail (GEDIT_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,
+		                     GEDIT_DOCUMENT_ERROR,
+		                     GEDIT_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,
@@ -1868,10 +1920,11 @@ gedit_document_save_as (GeditDocument                *doc,
 		       encoding,
 		       newline_type,
 		       compression_type,
-		       flags | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME);
+		       flags | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME,
+		       error);
 }
 
-gboolean	 
+gboolean
 gedit_document_is_untouched (GeditDocument *doc)
 {
 	g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE);
@@ -3100,4 +3153,48 @@ gedit_document_set_metadata (GeditDocument *doc,
 }
 #endif
 
+static void
+sync_error_tag (GeditDocument *doc,
+                GParamSpec    *pspec,
+                gpointer       data)
+{
+	sync_tag_style (doc, doc->priv->error_tag, "def:error");
+}
+
+void
+_gedit_document_apply_error_style (GeditDocument *doc,
+                                   GtkTextIter   *start,
+                                   GtkTextIter   *end)
+{
+	GtkTextBuffer *buffer;
+
+	gedit_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),
+		                                                   "invalid-char-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);
+}
+
 /* ex:set ts=8 noet: */
diff --git a/gedit/gedit-document.h b/gedit/gedit-document.h
index 1be3a27..70c69cd 100644
--- a/gedit/gedit-document.h
+++ b/gedit/gedit-document.h
@@ -109,7 +109,8 @@ typedef enum
 {
 	GEDIT_DOCUMENT_SAVE_IGNORE_MTIME 	= 1 << 0,
 	GEDIT_DOCUMENT_SAVE_IGNORE_BACKUP	= 1 << 1,
-	GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP	= 1 << 2
+	GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP	= 1 << 2,
+	GEDIT_DOCUMENT_SAVE_IGNORE_INVALID_CHARS= 1 << 3
 } GeditDocumentSaveFlags;
 
 /* Private structure type */
@@ -323,6 +324,11 @@ void		 _gedit_document_set_readonly 	(GeditDocument       *doc,
 glong		 _gedit_document_get_seconds_since_last_save_or_load 
 						(GeditDocument       *doc);
 
+void		 _gedit_document_apply_error_style
+                                                (GeditDocument *doc,
+                                                 GtkTextIter   *start,
+                                                 GtkTextIter   *end);
+
 /* Note: this is a sync stat: use only on local files */
 gboolean	_gedit_document_check_externally_modified
 						(GeditDocument       *doc);
diff --git a/gedit/gedit-io-error-info-bar.c b/gedit/gedit-io-error-info-bar.c
index ac8dba5..531d758 100644
--- a/gedit/gedit-io-error-info-bar.c
+++ b/gedit/gedit-io-error-info-bar.c
@@ -505,23 +505,19 @@ create_conversion_error_info_bar (const gchar *primary_text,
 		 different from other main menu access keys (Open, Edit, View...) */
 					 _("Edit Any_way"),
 					 GTK_RESPONSE_YES);
-		gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
-		/* Translators: the access key chosen for this string should be
-		 different from other main menu access keys (Open, Edit, View...) */
-					 _("D_on't Edit"),
-					 GTK_RESPONSE_NO);
 		gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
 					       GTK_MESSAGE_WARNING);
 	}
 	else
 	{
-		gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
-					 GTK_STOCK_CANCEL,
-					 GTK_RESPONSE_CANCEL);
 		gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
 					       GTK_MESSAGE_ERROR);
 	}
 
+	gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+				 GTK_STOCK_CANCEL,
+				 GTK_RESPONSE_CANCEL);
+
 	hbox_content = gtk_hbox_new (FALSE, 8);
 
 	image = gtk_image_new_from_stock ("gtk-dialog-error", GTK_ICON_SIZE_DIALOG);
@@ -631,8 +627,8 @@ gedit_io_loading_error_info_bar_new (GFile               *location,
 		error_message = g_strdup_printf (_("There was a problem opening the file %s."),
 						 uri_for_display);
 		message_details = g_strconcat (_("The file you opened has some invalid characters. "
-					       "If you continue editing this file you could make this "
-					       "document useless."), "\n",
+					       "If you continue editing this file you could corrupt this "
+					       "document."), "\n",
 					       _("You can also choose another character encoding and try again."),
 					       NULL);
 		edit_anyway = TRUE;
@@ -1225,4 +1221,92 @@ gedit_externally_modified_info_bar_new (GFile    *location,
 	return info_bar;
 }
 
+GtkWidget *
+gedit_invalid_character_info_bar_new (GFile *location)
+{
+	GtkWidget *info_bar;
+	GtkWidget *hbox_content;
+	GtkWidget *image;
+	GtkWidget *vbox;
+	GtkWidget *primary_label;
+	GtkWidget *secondary_label;
+	gchar *primary_markup;
+	gchar *secondary_markup;
+	gchar *primary_text;
+	gchar *full_formatted_uri;
+	gchar *uri_for_display;
+	gchar *temp_uri_for_display;
+	const gchar *secondary_text;
+
+	g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+	full_formatted_uri = g_file_get_parse_name (location);
+
+	/* Truncate the URI so it doesn't get insanely wide. Note that even
+	 * though the dialog uses wrapped text, if the URI doesn't contain
+	 * white space then the text-wrapping code is too stupid to wrap it.
+	 */
+	temp_uri_for_display = gedit_utils_str_middle_truncate (full_formatted_uri,
+								MAX_URI_IN_DIALOG_LENGTH);
+	g_free (full_formatted_uri);
+
+	uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+	g_free (temp_uri_for_display);
+
+	info_bar = gtk_info_bar_new ();
+	
+	info_bar_add_stock_button_with_text (GTK_INFO_BAR (info_bar),
+					     _("S_ave Anyway"),
+					     GTK_STOCK_SAVE,
+					     GTK_RESPONSE_YES);
+	gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+				 _("D_on't Save"),
+				 GTK_RESPONSE_CANCEL);
+	gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
+				       GTK_MESSAGE_WARNING);
+
+	hbox_content = gtk_hbox_new (FALSE, 8);
+
+	image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
+	gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+	gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+	vbox = gtk_vbox_new (FALSE, 6);
+	gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+	primary_text = g_strdup_printf (_("Some invalid chars have been detected while saving %s"),
+					uri_for_display);
+
+	g_free (uri_for_display);
+
+	primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+	g_free (primary_text);
+	primary_label = gtk_label_new (primary_markup);
+	g_free (primary_markup);
+	gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+	gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+	gtk_widget_set_can_focus (primary_label, TRUE);
+	gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+	secondary_text = _("If you continue saving this file you can corrupt the document. "
+	                   " Save anyway?");
+	secondary_markup = g_strdup_printf ("<small>%s</small>",
+					    secondary_text);
+	secondary_label = gtk_label_new (secondary_markup);
+	g_free (secondary_markup);
+	gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+	gtk_widget_set_can_focus (secondary_label, TRUE);
+	gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+	gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+	gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+
+	gtk_widget_show_all (hbox_content);
+	set_contents (info_bar, hbox_content);
+
+	return info_bar;
+}
+
 /* ex:set ts=8 noet: */
diff --git a/gedit/gedit-io-error-info-bar.h b/gedit/gedit-io-error-info-bar.h
index d91e4ab..ecf5ff6 100644
--- a/gedit/gedit-io-error-info-bar.h
+++ b/gedit/gedit-io-error-info-bar.h
@@ -63,6 +63,8 @@ GtkWidget	*gedit_unrecoverable_saving_error_info_bar_new		(GFile               *
 GtkWidget	*gedit_externally_modified_info_bar_new		 	(GFile               *location,
 									 gboolean             document_modified);
 
+GtkWidget	*gedit_invalid_character_info_bar_new			(GFile               *location);
+
 G_END_DECLS
 
 #endif  /* __GEDIT_IO_ERROR_INFO_BAR_H__  */
diff --git a/gedit/gedit-tab.c b/gedit/gedit-tab.c
index 5443868..dc51748 100644
--- a/gedit/gedit-tab.c
+++ b/gedit/gedit-tab.c
@@ -597,11 +597,9 @@ io_loading_error_info_bar_response (GtkWidget *info_bar,
 			break;
 		case GTK_RESPONSE_YES:
 			/* This means that we want to edit the document anyway */
-			set_info_bar (tab, NULL);
-			_gedit_document_set_readonly (doc, FALSE);
-			break;
-		case GTK_RESPONSE_NO:
-			/* We don't want to edit the document just show it */
+			tab->priv->not_editable = FALSE;
+			gtk_text_view_set_editable (GTK_TEXT_VIEW (view),
+			                            TRUE);
 			set_info_bar (tab, NULL);
 			break;
 		default:
@@ -1067,11 +1065,13 @@ document_loaded (GeditDocument *document,
 		{
 			GtkWidget *emsg;
 
-			_gedit_document_set_readonly (document, TRUE);
+			/* Set the tab as not editable as we have an error, the
+			   user can decide to make it editable again */
+			tab->priv->not_editable = TRUE;
 
 			emsg = gedit_io_loading_error_info_bar_new (location,
-									tab->priv->tmp_encoding,
-									error);
+								    tab->priv->tmp_encoding,
+								    error);
 
 			set_info_bar (tab, emsg);
 
@@ -1208,8 +1208,8 @@ end_saving (GeditTab *tab)
 
 static void 
 unrecoverable_saving_error_info_bar_response (GtkWidget        *info_bar,
-						  gint              response_id,
-						  GeditTab         *tab)
+                                              gint              response_id,
+                                              GeditTab         *tab)
 {
 	GeditView *view;
 	
@@ -1228,6 +1228,41 @@ unrecoverable_saving_error_info_bar_response (GtkWidget        *info_bar,
 }
 
 static void 
+invalid_character_info_bar_response (GtkWidget *info_bar,
+                                     gint       response_id,
+                                     GeditTab  *tab)
+{
+	if (response_id == GTK_RESPONSE_YES)
+	{
+		GeditDocument *doc;
+
+		doc = gedit_tab_get_document (tab);
+		g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+
+		set_info_bar (tab, NULL);
+
+		g_return_if_fail (tab->priv->tmp_save_location != NULL);
+		g_return_if_fail (tab->priv->tmp_encoding != NULL);
+
+		gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING);
+
+		/* don't bug the user again with this... */
+		tab->priv->save_flags |= GEDIT_DOCUMENT_SAVE_IGNORE_INVALID_CHARS;
+
+		g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+		
+		/* Force saving */
+		gedit_document_save (doc, tab->priv->save_flags);
+	}
+	else
+	{
+		unrecoverable_saving_error_info_bar_response (info_bar,
+								  response_id,
+								  tab);
+	}
+}
+
+static void 
 no_backup_error_info_bar_response (GtkWidget *info_bar,
 				       gint       response_id,
 				       GeditTab  *tab)
@@ -1357,9 +1392,12 @@ document_saved (GeditDocument *document,
 	g_return_if_fail (tab->priv->tmp_save_location != NULL);
 	g_return_if_fail (tab->priv->tmp_encoding != NULL);
 	g_return_if_fail (tab->priv->auto_save_timeout <= 0);
-	
-	g_timer_destroy (tab->priv->timer);
-	tab->priv->timer = NULL;
+
+	if (tab->priv->timer != NULL)
+	{
+		g_timer_destroy (tab->priv->timer);
+		tab->priv->timer = NULL;
+	}
 	tab->priv->times_called = 0;
 	
 	set_info_bar (tab, NULL);
@@ -1402,6 +1440,21 @@ document_saved (GeditDocument *document,
 					  G_CALLBACK (no_backup_error_info_bar_response),
 					  tab);
 		}
+		else if (error->domain == GEDIT_DOCUMENT_ERROR &&
+		         error->code == GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK)
+		{
+			/* 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 */
+			emsg = gedit_invalid_character_info_bar_new (tab->priv->tmp_save_location);
+			g_return_if_fail (emsg != NULL);
+
+			set_info_bar (tab, emsg);
+
+			g_signal_connect (emsg,
+			                  "response",
+			                  G_CALLBACK (invalid_character_info_bar_response),
+			                  tab);
+		}
 		else if (error->domain == GEDIT_DOCUMENT_ERROR ||
 			 (error->domain == G_IO_ERROR &&
 			  error->code != G_IO_ERROR_INVALID_DATA &&
@@ -2223,11 +2276,11 @@ _gedit_tab_save (GeditTab *tab)
 
 	/* uri used in error messages, will be freed in document_saved */
 	tab->priv->tmp_save_location = gedit_document_get_location (doc);
-	tab->priv->tmp_encoding = gedit_document_get_encoding (doc); 
+	tab->priv->tmp_encoding = gedit_document_get_encoding (doc);
 
 	if (tab->priv->auto_save_timeout > 0)
 		remove_auto_save_timeout (tab);
-		
+
 	gedit_document_save (doc, save_flags);
 }
 
diff --git a/tests/document-output-stream.c b/tests/document-output-stream.c
index 591a434..5b7a313 100644
--- a/tests/document-output-stream.c
+++ b/tests/document-output-stream.c
@@ -62,11 +62,6 @@ test_consecutive_write (const gchar *inbuf,
 
 	g_assert_no_error (err);
 
-	g_object_get (G_OBJECT (doc), "text", &b, NULL);
-
-	g_assert_cmpstr (inbuf, ==, b);
-	g_free (b);
-
 	type = gedit_document_output_stream_detect_newline_type (GEDIT_DOCUMENT_OUTPUT_STREAM (out));
 	g_assert (type == newline_type);
 
@@ -152,6 +147,17 @@ test_boundary ()
 	g_object_unref (out);
 }
 
+static void
+test_invalid_utf8 ()
+{
+	test_consecutive_write ("foobar\n\xef\xbf\xbe", "foobar\n\\EF\\BF\\BE", 10,
+	                        GEDIT_DOCUMENT_NEWLINE_TYPE_LF);
+	test_consecutive_write ("foobar\n\xef\xbf\xbezzzzzz\n", "foobar\n\\EF\\BF\\BEzzzzzz", 10,
+	                        GEDIT_DOCUMENT_NEWLINE_TYPE_LF);
+	test_consecutive_write ("\xef\xbf\xbezzzzzz\n", "\\EF\\BF\\BEzzzzzz", 10,
+	                        GEDIT_DOCUMENT_NEWLINE_TYPE_LF);
+}
+
 /* SMART CONVERSION */
 
 #define TEXT_TO_CONVERT "this is some text to make the tests"
@@ -395,6 +401,7 @@ int main (int   argc,
 	g_test_add_func ("/document-output-stream/consecutive_tnewline", test_consecutive_tnewline);
 	g_test_add_func ("/document-output-stream/big-char", test_big_char);
 	g_test_add_func ("/document-output-stream/test-boundary", test_boundary);
+	g_test_add_func ("/document-output-stream/test-invalid-utf8", test_invalid_utf8);
 
 	g_test_add_func ("/document-output-stream/smart conversion: utf8-utf8", test_utf8_utf8);
 	g_test_add_func ("/document-output-stream/smart conversion: guessed", test_guessed);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]