/*
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Claws Mail team
+ * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
*
* 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
+ * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
*/
/* code ported from gedit */
#ifdef HAVE_CONFIG_H
# include "config.h"
+#include "claws-features.h"
#endif
#include <glib.h>
gint mergeable;
};
+struct _UndoWrap
+{
+ gint lock;
+ gchar *pre_wrap_content;
+ gint start_pos;
+ gint end_pos;
+ gint len_change;
+};
+
static void undo_free_list (GList **list_pointer);
static void undo_check_size (UndoMain *undostruct);
static gint undo_merge (GList *list,
GtkTextView *textview = GTK_TEXT_VIEW(text);
GtkTextBuffer *textbuf = gtk_text_view_get_buffer(textview);
- g_return_val_if_fail(text != NULL, NULL);
+ cm_return_val_if_fail(text != NULL, NULL);
- undostruct = g_new(UndoMain, 1);
+ undostruct = g_new0(UndoMain, 1);
undostruct->textview = textview;
undostruct->undo = NULL;
undostruct->redo = NULL;
void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
gpointer data)
{
- g_return_if_fail(undostruct != NULL);
+ cm_return_if_fail(undostruct != NULL);
undostruct->change_state_func = func;
undostruct->change_state_data = data;
UndoInfo *undoinfo;
GtkAdjustment *vadj;
- g_return_if_fail(text != NULL);
- g_return_if_fail(end_pos >= start_pos);
+ cm_return_if_fail(text != NULL);
+ cm_return_if_fail(end_pos >= start_pos);
undo_free_list(&undostruct->redo);
undostruct->change_state_data);
if (undostruct->paste != 0) {
- if (action == UNDO_ACTION_INSERT)
+ if (action == UNDO_ACTION_INSERT)
action = UNDO_ACTION_REPLACE_INSERT;
else
action = UNDO_ACTION_REPLACE_DELETE;
undo_check_size(undostruct);
- vadj = GTK_ADJUSTMENT(GTK_TEXT_VIEW(undostruct->textview)->vadjustment);
+ vadj = GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(
+ GTK_TEXT_VIEW(undostruct->textview)));
undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
- vadj->value);
+ gtk_adjustment_get_value(vadj));
if (end_pos - start_pos != 1 || text[0] == '\n')
undoinfo->mergeable = FALSE;
GtkTextIter iter, start_iter, end_iter;
GtkTextMark *mark;
- g_return_if_fail(undostruct != NULL);
+ cm_return_if_fail(undostruct != NULL);
if (undostruct->undo == NULL) return;
/* The undo data we need is always at the top op the
stack. So, therefore, the first one */
undoinfo = (UndoInfo *)undostruct->undo->data;
- g_return_if_fail(undoinfo != NULL);
+ cm_return_if_fail(undoinfo != NULL);
undoinfo->mergeable = FALSE;
undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
/* Move the view (scrollbars) to the correct position */
gtk_adjustment_set_value
- (GTK_ADJUSTMENT(textview->vadjustment),
+ (GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(textview)),
undoinfo->window_position);
-
+
switch (undoinfo->action) {
case UNDO_ACTION_DELETE:
gtk_text_buffer_get_iter_at_offset(buffer, &iter, undoinfo->start_pos);
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
- /* "pull" another data structure from the list */
- undoinfo = (UndoInfo *)undostruct->undo->data;
- g_return_if_fail(undoinfo != NULL);
- undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
- undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
- g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
- gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
+ /* "pull" previous matching DELETE data structure from the list */
+ if (undostruct->undo){
+ undoinfo = (UndoInfo *)undostruct->undo->data;
+ undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
+ undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
+ cm_return_if_fail(undoinfo != NULL);
+ cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
+ gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
+ }
break;
case UNDO_ACTION_REPLACE_DELETE:
- g_warning("This should not happen. UNDO_REPLACE_DELETE");
+ gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
+ gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
+ /* "pull" previous matching INSERT data structure from the list */
+ if (undostruct->undo){
+ undoinfo = (UndoInfo *)undostruct->undo->data;
+ undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
+ undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
+ cm_return_if_fail(undoinfo != NULL);
+ cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_INSERT);
+ gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
+ gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
+ gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+ }
break;
default:
g_assert_not_reached();
break;
}
-
+
undostruct->change_state_func(undostruct,
UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
undostruct->change_state_data);
GtkTextIter iter, start_iter, end_iter;
GtkTextMark *mark;
- g_return_if_fail(undostruct != NULL);
+ cm_return_if_fail(undostruct != NULL);
if (undostruct->redo == NULL) return;
redoinfo = (UndoInfo *)undostruct->redo->data;
- g_return_if_fail (redoinfo != NULL);
+ cm_return_if_fail (redoinfo != NULL);
undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
gtk_text_buffer_place_cursor(buffer, &iter);
/* Move the view to the right position. */
- gtk_adjustment_set_value(textview->vadjustment,
+ gtk_adjustment_set_value(gtk_text_view_get_vadjustment(textview),
redoinfo->window_position);
switch (redoinfo->action) {
gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text);
- /* "pull" another data structure from the list */
+ /* "pull" previous matching INSERT data structure from the list */
redoinfo = (UndoInfo *)undostruct->redo->data;
- g_return_if_fail(redoinfo != NULL);
+ cm_return_if_fail(redoinfo != NULL);
undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
- g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
+ cm_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1);
break;
case UNDO_ACTION_REPLACE_INSERT:
- g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
+ gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
+ gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
+ /* "pull" previous matching DELETE structure from the list */
+ redoinfo = (UndoInfo *)undostruct->redo->data;
+ /* Do nothing if we redo from a middle-click button
+ * and next action is not UNDO_ACTION_REPLACE_DELETE */
+ if (redoinfo && redoinfo->action == UNDO_ACTION_REPLACE_DELETE) {
+ undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
+ undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
+ gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
+ gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
+ gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+ }
break;
default:
g_assert_not_reached();
{
GtkTextBuffer *buffer;
- g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
+ cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
buffer = gtk_text_view_get_buffer(undostruct->textview);
g_signal_handlers_block_by_func(buffer, undo_insert_text_cb, undostruct);
{
GtkTextBuffer *buffer;
- g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
+ cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
buffer = gtk_text_view_get_buffer(undostruct->textview);
g_signal_handlers_unblock_by_func(buffer, undo_insert_text_cb, undostruct);
undostruct);
}
+/* Init the WrapInfo structure */
+static void init_wrap_undo(UndoMain *undostruct)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+
+ cm_return_if_fail(undostruct != NULL);
+ cm_return_if_fail(undostruct->wrap_info == NULL);
+
+ undostruct->wrap_info = g_new0(UndoWrap, 1);
+
+ /* Save the whole buffer as original contents. We'll retain the
+ * changed region when exiting wrap mode.
+ */
+ buffer = gtk_text_view_get_buffer(undostruct->textview);
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ undostruct->wrap_info->pre_wrap_content
+ = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+
+ undostruct->wrap_info->lock = 0;
+
+ /* start_pos == -1 means nothing changed yet. */
+ undostruct->wrap_info->start_pos = -1;
+ undostruct->wrap_info->end_pos = -1;
+ undostruct->wrap_info->len_change = 0;
+}
+
+static void end_wrap_undo(UndoMain *undostruct)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ gchar *old_contents = NULL;
+ gchar *cur_contents = NULL;
+ gchar *new_contents = NULL;
+
+ cm_return_if_fail(undostruct != NULL);
+ cm_return_if_fail(undostruct->wrap_info != NULL);
+
+ /* If start_pos is still == -1, it means nothing changed. */
+ if (undostruct->wrap_info->start_pos == -1)
+ goto cleanup;
+
+ cm_return_if_fail(undostruct->wrap_info->end_pos > undostruct->wrap_info->start_pos);
+ cm_return_if_fail(undostruct->wrap_info->end_pos - undostruct->wrap_info->len_change > undostruct->wrap_info->start_pos);
+
+ /* get the whole new (wrapped) contents */
+ buffer = gtk_text_view_get_buffer(undostruct->textview);
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ cur_contents = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+
+ debug_print("wrapping done from %d to %d, len change: %d\n",
+ undostruct->wrap_info->start_pos,
+ undostruct->wrap_info->end_pos,
+ undostruct->wrap_info->len_change);
+
+ /* keep the relevant old unwrapped part, which is what
+ * was between start_pos & end_pos - len_change
+ */
+ old_contents = g_utf8_substring(
+ undostruct->wrap_info->pre_wrap_content,
+ undostruct->wrap_info->start_pos,
+ undostruct->wrap_info->end_pos
+ - undostruct->wrap_info->len_change);
+
+ /* and get the changed contents, from start_pos to end_pos. */
+ new_contents = g_utf8_substring(
+ cur_contents,
+ undostruct->wrap_info->start_pos,
+ undostruct->wrap_info->end_pos);
+
+ /* add the deleted (unwrapped) text to the undo pile */
+ undo_add(old_contents,
+ undostruct->wrap_info->start_pos,
+ undostruct->wrap_info->end_pos
+ - undostruct->wrap_info->len_change,
+ UNDO_ACTION_REPLACE_DELETE,
+ undostruct);
+
+ /* add the inserted (wrapped) text to the undo pile */
+ undo_add(new_contents,
+ undostruct->wrap_info->start_pos,
+ undostruct->wrap_info->end_pos,
+ UNDO_ACTION_REPLACE_INSERT,
+ undostruct);
+
+ g_free(old_contents);
+ g_free(cur_contents);
+ g_free(new_contents);
+cleanup:
+ g_free(undostruct->wrap_info->pre_wrap_content);
+ g_free(undostruct->wrap_info);
+ undostruct->wrap_info = NULL;
+}
+
+static void update_wrap_undo(UndoMain *undostruct, const gchar *text, int start,
+ int end, UndoAction action)
+{
+ gint len = end - start;
+
+ /* If we don't yet have a start position, or farther than
+ * current, store it.
+ */
+ if (undostruct->wrap_info->start_pos == -1
+ || start < undostruct->wrap_info->start_pos) {
+ undostruct->wrap_info->start_pos = start;
+ }
+
+ if (action == UNDO_ACTION_INSERT) {
+ /* If inserting, the end of the region is at the end of the
+ * change, and the total length of the changed region
+ * increases.
+ */
+ if (end > undostruct->wrap_info->end_pos) {
+ undostruct->wrap_info->end_pos = end;
+ }
+ undostruct->wrap_info->len_change += len;
+ } else if (action == UNDO_ACTION_DELETE) {
+ /* If deleting, the end of the region is at the start of the
+ * change, and the total length of the changed region
+ * decreases.
+ */
+ if (start > undostruct->wrap_info->end_pos) {
+ undostruct->wrap_info->end_pos = start;
+ }
+ undostruct->wrap_info->len_change -= len;
+ }
+}
+
+/* Set wrapping mode, in which changes are agglomerated until
+ * the end of wrapping mode.
+ */
void undo_wrapping(UndoMain *undostruct, gboolean wrap)
{
- debug_print("undo wrapping now %d\n", wrap);
- undostruct->wrap = wrap;
+ if (wrap) {
+ /* Start (or go deeper in) wrapping mode */
+ if (undostruct->wrap_info == NULL)
+ init_wrap_undo(undostruct);
+ undostruct->wrap_info->lock++;
+ } else if (undostruct->wrap_info != NULL) {
+ /* exit (& possible stop) one level of wrapping mode */
+ undostruct->wrap_info->lock--;
+ if (undostruct->wrap_info->lock == 0)
+ end_wrap_undo(undostruct);
+ } else {
+ g_warning("undo already out of wrap mode");
+ }
}
void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter,
{
gchar *text_to_insert;
gint pos;
+ glong utf8_len;
+
if (prefs_common.undolevels <= 0) return;
pos = gtk_text_iter_get_offset(iter);
- if (undostruct->wrap && undostruct->undo) {
- UndoInfo *last_undo = undostruct->undo->data;
- if (last_undo && (last_undo->action == UNDO_ACTION_INSERT
- || last_undo->action == UNDO_ACTION_REPLACE_INSERT)
- && last_undo->start_pos < pos && last_undo->end_pos > pos) {
- GtkTextIter start,end;
- last_undo->end_pos += g_utf8_strlen(new_text, -1);
- gtk_text_buffer_get_iter_at_offset(textbuf, &start, last_undo->start_pos);
- gtk_text_buffer_get_iter_at_offset(textbuf, &end, last_undo->end_pos);
- g_free(last_undo->text);
- last_undo->text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
- debug_print("add:undo upd %d-%d\n", last_undo->start_pos, last_undo->end_pos);
- return;
- } else debug_print("add:last: %d, %d-%d (%d)\n", last_undo->action,
- last_undo->start_pos, last_undo->end_pos, pos);
- }
Xstrndup_a(text_to_insert, new_text, new_text_length, return);
- debug_print("add:undo add %d-%ld\n", pos, pos + g_utf8_strlen(text_to_insert, -1));
- undo_add(text_to_insert, pos, pos + g_utf8_strlen(text_to_insert, -1),
+ utf8_len = g_utf8_strlen(text_to_insert, -1);
+
+ if (undostruct->wrap_info != NULL) {
+ update_wrap_undo(undostruct, text_to_insert,
+ pos, pos + utf8_len, UNDO_ACTION_INSERT);
+ return;
+ }
+
+ debug_print("add:undo add %d-%ld\n", pos, utf8_len);
+ undo_add(text_to_insert, pos, pos + utf8_len,
UNDO_ACTION_INSERT, undostruct);
}
start_pos = gtk_text_iter_get_offset(start);
end_pos = gtk_text_iter_get_offset(end);
- if (undostruct->wrap && undostruct->undo) {
- UndoInfo *last_undo = undostruct->undo->data;
- if (last_undo && (last_undo->action == UNDO_ACTION_INSERT
- || last_undo->action == UNDO_ACTION_REPLACE_INSERT)
- && last_undo->start_pos < start_pos && last_undo->end_pos > end_pos) {
- GtkTextIter start,end;
- last_undo->end_pos -= g_utf8_strlen(text_to_delete, -1);
- gtk_text_buffer_get_iter_at_offset(textbuf, &start, last_undo->start_pos);
- gtk_text_buffer_get_iter_at_offset(textbuf, &end, last_undo->end_pos);
- g_free(last_undo->text);
- last_undo->text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
- debug_print("del:undo upd %d-%d\n", last_undo->start_pos, last_undo->end_pos);
- return;
- } else debug_print("del:last: %d, %d-%d (%d)\n", last_undo->action,
- last_undo->start_pos, last_undo->end_pos, start_pos);
-
+ if (undostruct->wrap_info != NULL) {
+ update_wrap_undo(undostruct, text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE);
+ return;
}
debug_print("del:undo add %d-%d\n", start_pos, end_pos);
undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,