384 lines
10 KiB
C
384 lines
10 KiB
C
/*
|
|
* L3afpad - GTK+ based simple text editor
|
|
* Copyright (C) 2004-2005 Tarot Osuji
|
|
*
|
|
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <gdk/gdkkeysyms-compat.h>
|
|
#include "view.h"
|
|
#include "undo.h"
|
|
|
|
#define DV(x)
|
|
|
|
//"GTK_TEXT_VIEW(view)->overwrite_mode" can get overwrite_mode state
|
|
|
|
typedef struct {
|
|
gchar command;
|
|
gint start;
|
|
gint end;
|
|
gboolean seq; // sequency flag
|
|
gchar *str;
|
|
} UndoInfo;
|
|
|
|
enum {
|
|
INS = 0,
|
|
BS,
|
|
DEL
|
|
};
|
|
|
|
static GtkWidget *undo_w = NULL;
|
|
static GtkWidget *redo_w = NULL;
|
|
static GList *undo_list = NULL;
|
|
static GList *redo_list = NULL;
|
|
static GString *undo_gstr;
|
|
static UndoInfo *ui_tmp;
|
|
static guint modified_step;
|
|
static guint prev_keyval;
|
|
static gboolean seq_reserve = FALSE;
|
|
|
|
static void undo_flush_temporal_buffer(GtkTextBuffer *buffer);
|
|
|
|
static GList *undo_clear_info_list(GList *info_list)
|
|
{
|
|
while (g_list_length(info_list)) {
|
|
g_free(((UndoInfo *)info_list->data)->str);
|
|
g_free(info_list->data);
|
|
info_list = g_list_delete_link(info_list, info_list);
|
|
}
|
|
return info_list;
|
|
}
|
|
|
|
static void undo_append_undo_info(GtkTextBuffer *buffer, gchar command, gint start, gint end, gchar *str)
|
|
{
|
|
UndoInfo *ui = g_malloc(sizeof(UndoInfo));
|
|
|
|
ui->command = command;
|
|
ui->start = start;
|
|
ui->end = end;
|
|
// ui->seq = FALSE;
|
|
ui->seq = seq_reserve;
|
|
ui->str = str;
|
|
|
|
seq_reserve = FALSE;
|
|
|
|
undo_list = g_list_append(undo_list, ui);
|
|
DV(g_print("undo_cb: %d %s (%d-%d)\n", command, str, start, end));
|
|
}
|
|
|
|
static void undo_create_undo_info(GtkTextBuffer *buffer, gchar command, gint start, gint end)
|
|
{
|
|
GtkTextIter start_iter, end_iter;
|
|
gboolean seq_flag = FALSE;
|
|
gchar *str;
|
|
gint keyval = get_current_keyval();
|
|
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
|
|
str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
|
|
|
|
if (undo_gstr->len) {
|
|
if ((end - start == 1) && (command == ui_tmp->command)) {
|
|
switch (keyval) {
|
|
case GDK_BackSpace:
|
|
if (end == ui_tmp->start)
|
|
seq_flag = TRUE;
|
|
break;
|
|
case GDK_Delete:
|
|
if (start == ui_tmp->start)
|
|
seq_flag = TRUE;
|
|
break;
|
|
case GDK_Tab:
|
|
case GDK_space:
|
|
if (start == ui_tmp->end)
|
|
seq_flag = TRUE;
|
|
break;
|
|
default:
|
|
if (start == ui_tmp->end)
|
|
if (keyval && keyval < 0xF000)
|
|
switch (prev_keyval) {
|
|
case GDK_Return:
|
|
case GDK_Tab:
|
|
case GDK_space:
|
|
break;
|
|
default:
|
|
seq_flag = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (seq_flag) {
|
|
switch (command) {
|
|
case BS:
|
|
undo_gstr = g_string_prepend(undo_gstr, str);
|
|
ui_tmp->start--;
|
|
break;
|
|
default:
|
|
undo_gstr = g_string_append(undo_gstr, str);
|
|
ui_tmp->end++;
|
|
}
|
|
redo_list = undo_clear_info_list(redo_list);
|
|
prev_keyval = keyval;
|
|
gtk_widget_set_sensitive(undo_w, TRUE);
|
|
gtk_widget_set_sensitive(redo_w, FALSE);
|
|
return;
|
|
}
|
|
undo_append_undo_info(buffer, ui_tmp->command, ui_tmp->start, ui_tmp->end, g_strdup(undo_gstr->str));
|
|
undo_gstr = g_string_erase(undo_gstr, 0, -1);
|
|
}
|
|
|
|
if (!keyval && prev_keyval)
|
|
undo_set_sequency(TRUE);
|
|
|
|
if (end - start == 1 &&
|
|
((keyval && keyval < 0xF000) ||
|
|
keyval == GDK_BackSpace || keyval == GDK_Delete || keyval == GDK_Tab)) {
|
|
ui_tmp->command = command;
|
|
ui_tmp->start = start;
|
|
ui_tmp->end = end;
|
|
undo_gstr = g_string_erase(undo_gstr, 0, -1);
|
|
g_string_append(undo_gstr, str);
|
|
} else
|
|
undo_append_undo_info(buffer, command, start, end, g_strdup(str));
|
|
|
|
redo_list = undo_clear_info_list(redo_list);
|
|
prev_keyval = keyval;
|
|
clear_current_keyval();
|
|
// keyevent_setval(0);
|
|
gtk_widget_set_sensitive(undo_w, TRUE);
|
|
gtk_widget_set_sensitive(redo_w, FALSE);
|
|
}
|
|
|
|
static void cb_insert_text(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *str)
|
|
{
|
|
gint start, end;
|
|
|
|
DV( g_print("insert-text\n"));
|
|
end = gtk_text_iter_get_offset(iter);
|
|
start = end - g_utf8_strlen(str, -1);
|
|
|
|
undo_create_undo_info(buffer, INS, start, end);
|
|
}
|
|
|
|
static void cb_delete_range(GtkTextBuffer *buffer, GtkTextIter *start_iter, GtkTextIter *end_iter)
|
|
{
|
|
gint start, end;
|
|
gchar command;
|
|
|
|
DV( g_print("delete-range\n"));
|
|
start = gtk_text_iter_get_offset(start_iter);
|
|
end = gtk_text_iter_get_offset(end_iter);
|
|
|
|
if (get_current_keyval() == GDK_BackSpace)
|
|
command = BS;
|
|
else
|
|
command = DEL;
|
|
undo_create_undo_info(buffer, command, start, end);
|
|
}
|
|
|
|
void undo_reset_modified_step(GtkTextBuffer *buffer)
|
|
{
|
|
undo_flush_temporal_buffer(buffer);
|
|
modified_step = g_list_length(undo_list);
|
|
DV(g_print("undo_reset_modified_step: Reseted modified_step by %d\n", modified_step));
|
|
}
|
|
|
|
static void undo_check_modified_step(GtkTextBuffer *buffer)
|
|
{
|
|
gboolean flag;
|
|
|
|
flag = (modified_step == g_list_length(undo_list));
|
|
//g_print("%d - %d = %d\n", modified_step, g_list_length(undo_list), flag);
|
|
if (gtk_text_buffer_get_modified(buffer) == flag)
|
|
gtk_text_buffer_set_modified(buffer, !flag);
|
|
//g_print("change!\n");}
|
|
}
|
|
/*
|
|
static void undo_check_modified_step(GtkTextBuffer *buffer)
|
|
{
|
|
if (modified_step == g_list_length(undo_list))
|
|
gtk_text_buffer_set_modified(buffer, FALSE);
|
|
}
|
|
*//* // ????? "modified-changed" signal isn't emitted properly...
|
|
#include "window.h"
|
|
static void undo_check_modified_step(GtkTextBuffer *buffer)
|
|
{
|
|
if (modified_step == g_list_length(undo_list)) {
|
|
gtk_text_buffer_set_modified(buffer, FALSE);
|
|
set_main_window_title();
|
|
}
|
|
}
|
|
*/
|
|
static void cb_begin_user_action(GtkTextBuffer *buffer)
|
|
{
|
|
g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
|
|
G_CALLBACK(cb_insert_text), NULL);
|
|
g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
|
|
G_CALLBACK(cb_delete_range), NULL);
|
|
DV(g_print("begin-user-action(unblock_func)"));
|
|
DV(g_print(": keyval = 0x%X\n", get_current_keyval()));
|
|
}
|
|
|
|
static void cb_end_user_action(GtkTextBuffer *buffer)
|
|
{
|
|
g_signal_handlers_block_by_func(G_OBJECT(buffer),
|
|
G_CALLBACK(cb_insert_text), NULL);
|
|
g_signal_handlers_block_by_func(G_OBJECT(buffer),
|
|
G_CALLBACK(cb_delete_range), NULL);
|
|
DV(g_print("end-user-action(block_func)\n"));
|
|
}
|
|
|
|
void undo_clear_all(GtkTextBuffer *buffer)
|
|
{
|
|
undo_list = undo_clear_info_list(undo_list);
|
|
redo_list = undo_clear_info_list(redo_list);
|
|
undo_reset_modified_step(buffer);
|
|
gtk_widget_set_sensitive(undo_w, FALSE);
|
|
gtk_widget_set_sensitive(redo_w, FALSE);
|
|
|
|
ui_tmp->command = INS;
|
|
undo_gstr = g_string_erase(undo_gstr, 0, -1);
|
|
prev_keyval = 0;
|
|
}
|
|
|
|
void undo_init(GtkWidget *view, GtkWidget *undo_button, GtkWidget *redo_button)
|
|
{
|
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
|
|
|
|
undo_w = undo_button;
|
|
redo_w = redo_button;
|
|
|
|
g_signal_connect_after(G_OBJECT(buffer), "insert-text",
|
|
G_CALLBACK(cb_insert_text), NULL);
|
|
g_signal_connect(G_OBJECT(buffer), "delete-range",
|
|
G_CALLBACK(cb_delete_range), NULL);
|
|
g_signal_connect_after(G_OBJECT(buffer), "begin-user-action",
|
|
G_CALLBACK(cb_begin_user_action), NULL);
|
|
g_signal_connect(G_OBJECT(buffer), "end-user-action",
|
|
G_CALLBACK(cb_end_user_action), NULL);
|
|
cb_end_user_action(buffer);
|
|
|
|
ui_tmp = g_malloc(sizeof(UndoInfo));
|
|
undo_gstr = g_string_new("");
|
|
|
|
undo_clear_all(buffer);
|
|
}
|
|
|
|
void undo_set_sequency(gboolean seq)
|
|
{
|
|
if (g_list_length(undo_list))
|
|
((UndoInfo *)g_list_last(undo_list)->data)->seq = seq;
|
|
DV(g_print("<undo_set_sequency: %d>\n", seq));
|
|
}
|
|
|
|
void undo_set_sequency_reserve(void)
|
|
{
|
|
seq_reserve = TRUE;
|
|
}
|
|
|
|
static void undo_flush_temporal_buffer(GtkTextBuffer *buffer)
|
|
{
|
|
if (undo_gstr->len) {
|
|
undo_append_undo_info(buffer, ui_tmp->command,
|
|
ui_tmp->start, ui_tmp->end, g_strdup(undo_gstr->str));
|
|
undo_gstr = g_string_erase(undo_gstr, 0, -1);
|
|
}
|
|
}
|
|
|
|
gboolean undo_undo_real(GtkTextBuffer *buffer)
|
|
{
|
|
GtkTextIter start_iter, end_iter;
|
|
UndoInfo *ui;
|
|
|
|
undo_flush_temporal_buffer(buffer);
|
|
if (g_list_length(undo_list)) {
|
|
// undo_block_signal(buffer);
|
|
ui = g_list_last(undo_list)->data;
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ui->start);
|
|
switch (ui->command) {
|
|
case INS:
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, ui->end);
|
|
gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
|
|
break;
|
|
default:
|
|
gtk_text_buffer_insert(buffer, &start_iter, ui->str, -1);
|
|
}
|
|
redo_list = g_list_append(redo_list, ui);
|
|
undo_list = g_list_delete_link(undo_list, g_list_last(undo_list));
|
|
DV(g_print("cb_edit_undo: undo left = %d, redo left = %d\n",
|
|
g_list_length(undo_list), g_list_length(redo_list)));
|
|
// undo_unblock_signal(buffer);
|
|
if (g_list_length(undo_list)) {
|
|
if (((UndoInfo *)g_list_last(undo_list)->data)->seq)
|
|
return TRUE;
|
|
} else
|
|
gtk_widget_set_sensitive(undo_w, FALSE);
|
|
gtk_widget_set_sensitive(redo_w, TRUE);
|
|
if (ui->command == DEL)
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ui->start);
|
|
gtk_text_buffer_place_cursor(buffer, &start_iter);
|
|
scroll_to_cursor(buffer, 0.05);
|
|
}
|
|
undo_check_modified_step(buffer);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean undo_redo_real(GtkTextBuffer *buffer)
|
|
{
|
|
GtkTextIter start_iter, end_iter;
|
|
UndoInfo *ri;
|
|
|
|
if (g_list_length(redo_list)) {
|
|
// undo_block_signal(buffer);
|
|
ri = g_list_last(redo_list)->data;
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ri->start);
|
|
switch (ri->command) {
|
|
case INS:
|
|
gtk_text_buffer_insert(buffer, &start_iter, ri->str, -1);
|
|
break;
|
|
default:
|
|
gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, ri->end);
|
|
gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
|
|
}
|
|
undo_list = g_list_append(undo_list, ri);
|
|
redo_list = g_list_delete_link(redo_list, g_list_last(redo_list));
|
|
DV(g_print("cb_edit_redo: undo left = %d, redo left = %d\n",
|
|
g_list_length(undo_list), g_list_length(redo_list)));
|
|
// undo_unblock_signal(buffer);
|
|
if (ri->seq) {
|
|
undo_set_sequency(TRUE);
|
|
return TRUE;
|
|
}
|
|
if (!g_list_length(redo_list))
|
|
gtk_widget_set_sensitive(redo_w, FALSE);
|
|
gtk_widget_set_sensitive(undo_w, TRUE);
|
|
gtk_text_buffer_place_cursor(buffer, &start_iter);
|
|
scroll_to_cursor(buffer, 0.05);
|
|
}
|
|
undo_check_modified_step(buffer);
|
|
return FALSE;
|
|
}
|
|
|
|
void undo_undo(GtkTextBuffer *buffer)
|
|
{
|
|
while (undo_undo_real(buffer)) {};
|
|
}
|
|
|
|
void undo_redo(GtkTextBuffer *buffer)
|
|
{
|
|
while (undo_redo_real(buffer)) {};
|
|
}
|