diff options
author | hiro <hiro@ee746299-78ed-0310-b773-934348b2243d> | 2005-01-12 11:22:08 +0000 |
---|---|---|
committer | hiro <hiro@ee746299-78ed-0310-b773-934348b2243d> | 2005-01-12 11:22:08 +0000 |
commit | b9ca7b1ef5cd1f96ae6e28ae78d12c1e3258c23f (patch) | |
tree | 1203adec5f70af1ddd49868528d8d3a5b9004329 /src/undo.c |
Initial import of Sylpheed (GTK2 version).
git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@1 ee746299-78ed-0310-b773-934348b2243d
Diffstat (limited to 'src/undo.c')
-rw-r--r-- | src/undo.c | 646 |
1 files changed, 646 insertions, 0 deletions
diff --git a/src/undo.c b/src/undo.c new file mode 100644 index 00000000..3828aebb --- /dev/null +++ b/src/undo.c @@ -0,0 +1,646 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* code ported from gedit */ +/* This is for my patient girlfirend Regina */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <gtk/gtktextview.h> + +#include <string.h> /* for strlen */ +#include <stdlib.h> /* for mbstowcs */ + +#include "undo.h" +#include "utils.h" +#include "prefs_common.h" + +typedef struct _UndoInfo UndoInfo; + +struct _UndoInfo +{ + UndoAction action; + gchar *text; + gint start_pos; + gint end_pos; + gfloat window_position; + gint mergeable; +}; + +static void undo_free_list (GList **list_pointer); +static void undo_check_size (UndoMain *undostruct); +static gint undo_merge (GList *list, + guint start_pos, + guint end_pos, + gint action, + const guchar *text); +static void undo_add (const gchar *text, + gint start_pos, + gint end_pos, + UndoAction action, + UndoMain *undostruct); +static gint undo_get_selection (GtkTextView *textview, + guint *start, + guint *end); +static void undo_insert_text_cb (GtkTextBuffer *textbuf, + GtkTextIter *iter, + gchar *new_text, + gint new_text_length, + UndoMain *undostruct); +static void undo_delete_text_cb (GtkTextBuffer *textbuf, + GtkTextIter *start, + GtkTextIter *end, + UndoMain *undostruct); + +static void undo_paste_clipboard_cb (GtkTextView *textview, + UndoMain *undostruct); + +void undo_undo (UndoMain *undostruct); +void undo_redo (UndoMain *undostruct); + + +UndoMain *undo_init(GtkWidget *text) +{ + UndoMain *undostruct; + GtkTextView *textview = GTK_TEXT_VIEW(text); + GtkTextBuffer *textbuf; + + g_return_val_if_fail(text != NULL, NULL); + + textbuf = gtk_text_view_get_buffer(textview); + + undostruct = g_new(UndoMain, 1); + undostruct->textview = textview; + undostruct->undo = NULL; + undostruct->redo = NULL; + undostruct->paste = 0; + undostruct->undo_state = FALSE; + undostruct->redo_state = FALSE; + + g_signal_connect(G_OBJECT(text), "insert-text", + G_CALLBACK(undo_insert_text_cb), undostruct); + g_signal_connect(G_OBJECT(text), "delete-text", + G_CALLBACK(undo_delete_text_cb), undostruct); + g_signal_connect(G_OBJECT(text), "paste-clipboard", + G_CALLBACK(undo_paste_clipboard_cb), undostruct); + + return undostruct; +} + +void undo_destroy (UndoMain *undostruct) +{ + undo_free_list(&undostruct->undo); + undo_free_list(&undostruct->redo); + g_free(undostruct); +} + +static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos, + UndoAction action, gfloat window_position) +{ + UndoInfo *undoinfo; + undoinfo = g_new (UndoInfo, 1); + undoinfo->text = text; + undoinfo->start_pos = start_pos; + undoinfo->end_pos = end_pos; + undoinfo->action = action; + undoinfo->window_position = window_position; + return undoinfo; +} + +static void undo_object_free(UndoInfo *undo) +{ + g_free (undo->text); + g_free (undo); +} + +/** + * undo_free_list: + * @list_pointer: list to be freed + * + * frees and undo structure list + **/ +static void undo_free_list(GList **list_pointer) +{ + UndoInfo *undo; + GList *cur, *list = *list_pointer; + + if (list == NULL) return; + + for (cur = list; cur != NULL; cur = cur->next) { + undo = (UndoInfo *)cur->data; + undo_object_free(undo); + } + + g_list_free(list); + *list_pointer = NULL; +} + +void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func, + gpointer data) +{ + g_return_if_fail(undostruct != NULL); + + undostruct->change_state_func = func; + undostruct->change_state_data = data; +} + +/** + * undo_check_size: + * @compose: document to check + * + * Checks that the size of compose->undo does not excede settings->undo_levels and + * frees any undo level above sett->undo_level. + * + **/ +static void undo_check_size(UndoMain *undostruct) +{ + UndoInfo *last_undo; + guint length; + + if (prefs_common.undolevels < 1) return; + + /* No need to check for the redo list size since the undo + list gets freed on any call to compose_undo_add */ + length = g_list_length(undostruct->undo); + if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) { + last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data; + undostruct->undo = g_list_remove(undostruct->undo, last_undo); + undo_object_free(last_undo); + } +} + +/** + * undo_merge: + * @last_undo: + * @start_pos: + * @end_pos: + * @action: + * + * This function tries to merge the undo object at the top of + * the stack with a new set of data. So when we undo for example + * typing, we can undo the whole word and not each letter by itself + * + * Return Value: TRUE is merge was sucessful, FALSE otherwise + **/ +static gint undo_merge(GList *list, guint start_pos, guint end_pos, + gint action, const guchar *text) +{ + guchar *temp_string; + UndoInfo *last_undo; + + /* This are the cases in which we will NOT merge : + 1. if (last_undo->mergeable == FALSE) + [mergeable = FALSE when the size of the undo data was not 1. + or if the data was size = 1 but = '\n' or if the undo object + has been "undone" already ] + 2. The size of text is not 1 + 3. If the new merging data is a '\n' + 4. If the last char of the undo_last data is a space/tab + and the new char is not a space/tab ( so that we undo + words and not chars ) + 5. If the type (action) of undo is different from the last one + Chema */ + + if (list == NULL) return FALSE; + + last_undo = list->data; + + if (!last_undo->mergeable) return FALSE; + + if (end_pos - start_pos != 1 || + text[0] == '\n' || + action != last_undo->action || + action == UNDO_ACTION_REPLACE_INSERT || + action == UNDO_ACTION_REPLACE_DELETE) { + last_undo->mergeable = FALSE; + return FALSE; + } + + if (action == UNDO_ACTION_DELETE) { + gboolean checkit = TRUE; + + if (last_undo->start_pos != end_pos && + last_undo->start_pos != start_pos) { + last_undo->mergeable = FALSE; + return FALSE; + } else if (last_undo->start_pos == start_pos) { + /* Deleted with the delete key */ + if (text[0] != ' ' && text[0] != '\t' && + (last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == ' ' || + last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == '\t')) + checkit = FALSE; + + temp_string = g_strdup_printf("%s%s", last_undo->text, text); + last_undo->end_pos++; + g_free(last_undo->text); + last_undo->text = temp_string; + } else { + /* Deleted with the backspace key */ + if (text[0] != ' ' && text[0] != '\t' && + (last_undo->text[0] == ' ' || + last_undo->text[0] == '\t')) + checkit = FALSE; + + temp_string = g_strdup_printf("%s%s", text, last_undo->text); + last_undo->start_pos = start_pos; + g_free(last_undo->text); + last_undo->text = temp_string; + } + + if (!checkit) { + last_undo->mergeable = FALSE; + return FALSE; + } + } else if (action == UNDO_ACTION_INSERT) { + if (last_undo->end_pos != start_pos) { + last_undo->mergeable = FALSE; + return FALSE; + } else { + temp_string = g_strdup_printf("%s%s", last_undo->text, text); + g_free(last_undo->text); + last_undo->end_pos = end_pos; + last_undo->text = temp_string; + } + } else + debug_print("Unknown action [%i] inside undo merge encountered", action); + + return TRUE; +} + +/** + * compose_undo_add: + * @text: + * @start_pos: + * @end_pos: + * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE + * @compose: + * @view: The view so that we save the scroll bar position. + * + * Adds text to the undo stack. It also performs test to limit the number + * of undo levels and deltes the redo list + **/ + +static void undo_add(const gchar *text, + gint start_pos, gint end_pos, + UndoAction action, UndoMain *undostruct) +{ + UndoInfo *undoinfo; + GtkAdjustment *vadj; + + g_return_if_fail(text != NULL); + g_return_if_fail(end_pos >= start_pos); + + undo_free_list(&undostruct->redo); + + /* Set the redo sensitivity */ + undostruct->change_state_func(undostruct, + UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE, + undostruct->change_state_data); + + if (undostruct->paste != 0) { + if (action == UNDO_ACTION_INSERT) + action = UNDO_ACTION_REPLACE_INSERT; + else + action = UNDO_ACTION_REPLACE_DELETE; + undostruct->paste = undostruct->paste + 1; + if (undostruct->paste == 3) + undostruct->paste = 0; + } + + if (undo_merge(undostruct->undo, start_pos, end_pos, action, text)) + return; + + undo_check_size(undostruct); + + vadj = GTK_ADJUSTMENT(GTK_TEXT_VIEW(undostruct->textview)->vadjustment); + undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action, + vadj->value); + + if (end_pos - start_pos != 1 || text[0] == '\n') + undoinfo->mergeable = FALSE; + else + undoinfo->mergeable = TRUE; + + undostruct->undo = g_list_prepend(undostruct->undo, undoinfo); + + undostruct->change_state_func(undostruct, + UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, + undostruct->change_state_data); +} + +/** + * undo_undo: + * @w: not used + * @data: not used + * + * Executes an undo request on the current document + **/ +void undo_undo(UndoMain *undostruct) +{ + UndoInfo *undoinfo; + GtkTextView *textview; + GtkTextBuffer *buffer; + GtkTextIter iter, start_iter, end_iter; + GtkTextMark *mark; + + g_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); + undoinfo->mergeable = FALSE; + undostruct->redo = g_list_prepend(undostruct->redo, undoinfo); + undostruct->undo = g_list_remove(undostruct->undo, undoinfo); + + textview = undostruct->textview; + buffer = gtk_text_view_get_buffer(textview); + + undo_block(undostruct); + + /* Check if there is a selection active */ + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + gtk_text_buffer_place_cursor(buffer, &iter); + + /* Move the view (scrollbars) to the correct position */ + gtk_adjustment_set_value(GTK_ADJUSTMENT(textview->vadjustment), + 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_insert(buffer, &iter, undoinfo->text, -1); + debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text); + break; + case UNDO_ACTION_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); + debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos); + break; + case 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); + debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text); + /* "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); + debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text); + break; + case UNDO_ACTION_REPLACE_DELETE: + g_warning("This should not happen. UNDO_REPLACE_DELETE"); + break; + default: + g_assert_not_reached(); + break; + } + + undostruct->change_state_func(undostruct, + UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE, + undostruct->change_state_data); + + if (undostruct->undo == NULL) + undostruct->change_state_func(undostruct, + UNDO_STATE_FALSE, + UNDO_STATE_UNCHANGED, + undostruct->change_state_data); + + undo_unblock(undostruct); +} + +/** + * undo_redo: + * @w: not used + * @data: not used + * + * executes a redo request on the current document + **/ +void undo_redo(UndoMain *undostruct) +{ + UndoInfo *redoinfo; + GtkTextView *textview; + GtkTextBuffer *buffer; + GtkTextIter iter, start_iter, end_iter; + GtkTextMark *mark; + + g_return_if_fail(undostruct != NULL); + + if (undostruct->redo == NULL) return; + + redoinfo = (UndoInfo *)undostruct->redo->data; + g_return_if_fail (redoinfo != NULL); + undostruct->undo = g_list_prepend(undostruct->undo, redoinfo); + undostruct->redo = g_list_remove(undostruct->redo, redoinfo); + + textview = undostruct->textview; + buffer = gtk_text_view_get_buffer(textview); + + undo_block(undostruct); + + /* Check if there is a selection active */ + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + gtk_text_buffer_place_cursor(buffer, &iter); + + /* Move the view to the right position. */ + gtk_adjustment_set_value(textview->vadjustment, + redoinfo->window_position); + + switch (redoinfo->action) { + case UNDO_ACTION_INSERT: + gtk_text_buffer_get_iter_at_offset + (buffer, &iter, redoinfo->start_pos); + gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1); + debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text); + break; + case UNDO_ACTION_DELETE: + 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); + debug_print("UNDO_ACTION_INSERT %d\n", + redoinfo->end_pos-redoinfo->start_pos); + break; + case UNDO_ACTION_REPLACE_DELETE: + 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); + debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text); + /* "pull" another data structure from the list */ + redoinfo = (UndoInfo *)undostruct->redo->data; + g_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); + 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"); + break; + default: + g_assert_not_reached(); + break; + } + + undostruct->change_state_func(undostruct, + UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, + undostruct->change_state_data); + + if (undostruct->redo == NULL) + undostruct->change_state_func(undostruct, + UNDO_STATE_UNCHANGED, + UNDO_STATE_FALSE, + undostruct->change_state_data); + + undo_unblock(undostruct); +} + +void undo_block(UndoMain *undostruct) +{ + GtkTextBuffer *buffer; + + g_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); + g_signal_handlers_block_by_func + (buffer, undo_delete_text_cb, undostruct); + g_signal_handlers_block_by_func + (buffer, undo_paste_clipboard_cb, undostruct); +} + +void undo_unblock(UndoMain *undostruct) +{ + GtkTextBuffer *buffer; + + g_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); + g_signal_handlers_unblock_by_func + (buffer, undo_delete_text_cb, undostruct); + g_signal_handlers_unblock_by_func + (buffer, undo_paste_clipboard_cb, undostruct); +} + +void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter, + gchar *new_text, gint new_text_length, + UndoMain *undostruct) +{ + gchar *text_to_insert; + gint pos; + + if (prefs_common.undolevels <= 0) return; + + pos = gtk_text_iter_get_offset(iter); + + Xstrndup_a(text_to_insert, new_text, new_text_length, return); + undo_add(text_to_insert, pos, pos + g_utf8_strlen(text_to_insert, -1), + UNDO_ACTION_INSERT, undostruct); +} + +void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start, + GtkTextIter *end, UndoMain *undostruct) +{ + gchar *text_to_delete; + gint start_pos, end_pos; + + if (prefs_common.undolevels <= 0) return; + + text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE); + if (!text_to_delete || !*text_to_delete) return; + + start_pos = gtk_text_iter_get_offset(start); + end_pos = gtk_text_iter_get_offset(end); + + undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE, + undostruct); + g_free(text_to_delete); +} + +void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct) +{ + debug_print("before Paste: %d\n", undostruct->paste); + if (prefs_common.undolevels > 0) + if (undo_get_selection(textview, NULL, NULL)) + undostruct->paste = TRUE; + debug_print("after Paste: %d\n", undostruct->paste); +} + +/** + * undo_get_selection: + * @text: Text to get the selection from + * @start: return here the start position of the selection + * @end: return here the end position of the selection + * + * Gets the current selection for View + * + * Return Value: TRUE if there is a selection active, FALSE if not + **/ +static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end) +{ + GtkTextBuffer *buffer; + GtkTextIter start_iter, end_iter; + guint start_pos, end_pos; + + buffer = gtk_text_view_get_buffer(textview); + gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter); + + start_pos = gtk_text_iter_get_offset(&start_iter); + end_pos = gtk_text_iter_get_offset(&end_iter); + + /* The user can select from end to start too. If so, swap it*/ + if (end_pos < start_pos) { + guint swap_pos; + swap_pos = end_pos; + end_pos = start_pos; + start_pos = swap_pos; + } + + if (start != NULL) + *start = start_pos; + + if (end != NULL) + *end = end_pos; + + if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos)) + return TRUE; + else + return FALSE; +} |