846 lines
26 KiB
C
846 lines
26 KiB
C
/*
|
|
* mooentry.c
|
|
*
|
|
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
*
|
|
* 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.
|
|
*
|
|
* See COPYING file that comes with this distribution.
|
|
*/
|
|
|
|
#include MOO_MARSHALS_H
|
|
#include "mooutils/mooentry.h"
|
|
#include "mooutils/mooutils-gobject.h"
|
|
#include <gtk/gtkbindings.h>
|
|
#include <gtk/gtkimagemenuitem.h>
|
|
#include <gtk/gtkseparatormenuitem.h>
|
|
#include <gtk/gtkstock.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <string.h>
|
|
|
|
|
|
struct _MooEntryPrivate {
|
|
MooUndoMgr *undo_mgr;
|
|
gboolean enable_undo;
|
|
gboolean enable_undo_menu;
|
|
guint use_ctrl_u : 1;
|
|
guint grab_selection : 1;
|
|
guint fool_entry : 1;
|
|
guint empty : 1;
|
|
};
|
|
|
|
static guint INSERT_ACTION_TYPE;
|
|
static guint DELETE_ACTION_TYPE;
|
|
|
|
|
|
static void moo_entry_class_init (MooEntryClass *klass);
|
|
static void moo_entry_editable_init (GtkEditableClass *klass);
|
|
|
|
static void moo_entry_init (MooEntry *entry);
|
|
static void moo_entry_finalize (GObject *object);
|
|
static void moo_entry_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void moo_entry_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static gboolean moo_entry_button_release (GtkWidget *widget,
|
|
GdkEventButton *event);
|
|
|
|
static void moo_entry_delete_to_start (MooEntry *entry);
|
|
|
|
static void moo_entry_populate_popup (GtkEntry *entry,
|
|
GtkMenu *menu);
|
|
|
|
static void moo_entry_delete_from_cursor(GtkEntry *entry,
|
|
GtkDeleteType type,
|
|
gint count);
|
|
static void moo_entry_cut_clipboard (GtkEntry *entry);
|
|
static void moo_entry_paste_clipboard (GtkEntry *entry);
|
|
|
|
static void moo_entry_do_insert_text (GtkEditable *editable,
|
|
const gchar *text,
|
|
gint length,
|
|
gint *position);
|
|
static void moo_entry_do_delete_text (GtkEditable *editable,
|
|
gint start_pos,
|
|
gint end_pos);
|
|
static void moo_entry_set_selection_bounds (GtkEditable *editable,
|
|
gint start_pos,
|
|
gint end_pos);
|
|
static gboolean moo_entry_get_selection_bounds (GtkEditable *editable,
|
|
gint *start_pos,
|
|
gint *end_pos);
|
|
static void moo_entry_changed (GtkEditable *editable);
|
|
|
|
static void init_undo_actions (void);
|
|
static gpointer insert_action_new (GtkEditable *editable,
|
|
const gchar *text,
|
|
gint length,
|
|
gint *position);
|
|
static gpointer delete_action_new (GtkEditable *editable,
|
|
gint start_pos,
|
|
gint end_pos);
|
|
|
|
|
|
GType
|
|
moo_entry_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type)
|
|
{
|
|
static const GTypeInfo info =
|
|
{
|
|
sizeof (MooEntryClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
(GClassInitFunc) moo_entry_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (MooEntry),
|
|
0,
|
|
(GInstanceInitFunc) moo_entry_init,
|
|
NULL
|
|
};
|
|
|
|
static const GInterfaceInfo editable_info =
|
|
{
|
|
(GInterfaceInitFunc) moo_entry_editable_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
type = g_type_register_static (GTK_TYPE_ENTRY, "MooEntry", &info, 0);
|
|
g_type_add_interface_static (type, GTK_TYPE_EDITABLE, &editable_info);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_UNDO_MANAGER,
|
|
PROP_ENABLE_UNDO,
|
|
PROP_ENABLE_UNDO_MENU,
|
|
PROP_GRAB_SELECTION,
|
|
PROP_EMPTY
|
|
};
|
|
|
|
enum {
|
|
UNDO,
|
|
REDO,
|
|
BEGIN_USER_ACTION,
|
|
END_USER_ACTION,
|
|
DELETE_TO_START,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint signals[NUM_SIGNALS];
|
|
static GtkEditableClass *parent_editable_iface;
|
|
static gpointer moo_entry_parent_class;
|
|
|
|
static void
|
|
moo_entry_class_init (MooEntryClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
GtkEntryClass *entry_class = GTK_ENTRY_CLASS (klass);
|
|
GtkBindingSet *binding_set;
|
|
|
|
init_undo_actions ();
|
|
|
|
gobject_class->finalize = moo_entry_finalize;
|
|
gobject_class->set_property = moo_entry_set_property;
|
|
gobject_class->get_property = moo_entry_get_property;
|
|
|
|
widget_class->button_release_event = moo_entry_button_release;
|
|
|
|
entry_class->populate_popup = moo_entry_populate_popup;
|
|
entry_class->delete_from_cursor = moo_entry_delete_from_cursor;
|
|
entry_class->cut_clipboard = moo_entry_cut_clipboard;
|
|
entry_class->paste_clipboard = moo_entry_paste_clipboard;
|
|
|
|
klass->undo = moo_entry_undo;
|
|
klass->redo = moo_entry_redo;
|
|
|
|
moo_entry_parent_class = g_type_class_peek_parent (klass);
|
|
parent_editable_iface = g_type_interface_peek (moo_entry_parent_class, GTK_TYPE_EDITABLE);
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_UNDO_MANAGER,
|
|
g_param_spec_object ("undo-manager",
|
|
"undo-manager",
|
|
"undo-manager",
|
|
MOO_TYPE_UNDO_MGR,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_ENABLE_UNDO,
|
|
g_param_spec_boolean ("enable-undo",
|
|
"enable-undo",
|
|
"enable-undo",
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_ENABLE_UNDO_MENU,
|
|
g_param_spec_boolean ("enable-undo-menu",
|
|
"enable-undo-menu",
|
|
"enable-undo-menu",
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_GRAB_SELECTION,
|
|
g_param_spec_boolean ("grab-selection",
|
|
"grab-selection",
|
|
"grab-selection",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_EMPTY,
|
|
g_param_spec_boolean ("empty",
|
|
"empty",
|
|
"empty",
|
|
TRUE,
|
|
G_PARAM_READABLE));
|
|
|
|
signals[UNDO] = g_signal_lookup ("undo", GTK_TYPE_ENTRY);
|
|
|
|
if (!signals[UNDO])
|
|
signals[UNDO] =
|
|
g_signal_new ("undo",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET (MooEntryClass, undo),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[REDO] = g_signal_lookup ("redo", GTK_TYPE_ENTRY);
|
|
|
|
if (!signals[REDO])
|
|
signals[REDO] =
|
|
g_signal_new ("redo",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET (MooEntryClass, redo),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[DELETE_TO_START] =
|
|
moo_signal_new_cb ("delete-to-start",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_CALLBACK (moo_entry_delete_to_start),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
binding_set = gtk_binding_set_by_class (klass);
|
|
gtk_binding_entry_add_signal (binding_set, GDK_z,
|
|
GDK_CONTROL_MASK,
|
|
"undo", 0);
|
|
gtk_binding_entry_add_signal (binding_set, GDK_z,
|
|
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
|
"redo", 0);
|
|
gtk_binding_entry_add_signal (binding_set, GDK_u,
|
|
GDK_CONTROL_MASK,
|
|
"delete-to-start", 0);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_editable_init (GtkEditableClass *klass)
|
|
{
|
|
klass->do_insert_text = moo_entry_do_insert_text;
|
|
klass->do_delete_text = moo_entry_do_delete_text;
|
|
klass->set_selection_bounds = moo_entry_set_selection_bounds;
|
|
klass->get_selection_bounds = moo_entry_get_selection_bounds;
|
|
klass->changed = moo_entry_changed;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_init (MooEntry *entry)
|
|
{
|
|
entry->priv = g_new0 (MooEntryPrivate, 1);
|
|
entry->priv->undo_mgr = moo_undo_mgr_new (entry);
|
|
entry->priv->use_ctrl_u = TRUE;
|
|
entry->priv->empty = TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MooEntry *entry = MOO_ENTRY (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ENABLE_UNDO:
|
|
entry->priv->enable_undo = g_value_get_boolean (value);
|
|
g_object_notify (object, "enable-undo");
|
|
break;
|
|
|
|
case PROP_ENABLE_UNDO_MENU:
|
|
entry->priv->enable_undo_menu = g_value_get_boolean (value);
|
|
g_object_notify (object, "enable-undo-menu");
|
|
break;
|
|
|
|
case PROP_GRAB_SELECTION:
|
|
entry->priv->grab_selection = g_value_get_boolean (value) ? TRUE : FALSE;
|
|
g_object_notify (object, "grab-selection");
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MooEntry *entry = MOO_ENTRY (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ENABLE_UNDO:
|
|
g_value_set_boolean (value, entry->priv->enable_undo);
|
|
break;
|
|
|
|
case PROP_ENABLE_UNDO_MENU:
|
|
g_value_set_boolean (value, entry->priv->enable_undo_menu);
|
|
break;
|
|
|
|
case PROP_UNDO_MANAGER:
|
|
g_value_set_object (value, entry->priv->undo_mgr);
|
|
break;
|
|
|
|
case PROP_GRAB_SELECTION:
|
|
g_value_set_boolean (value, entry->priv->grab_selection ? TRUE : FALSE);
|
|
break;
|
|
|
|
case PROP_EMPTY:
|
|
g_value_set_boolean (value, GTK_ENTRY(entry)->text_length == 0);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_finalize (GObject *object)
|
|
{
|
|
MooEntry *entry = MOO_ENTRY (object);
|
|
|
|
g_object_unref (entry->priv->undo_mgr);
|
|
g_free (entry->priv);
|
|
|
|
G_OBJECT_CLASS (moo_entry_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_changed (GtkEditable *editable)
|
|
{
|
|
MooEntry *entry = MOO_ENTRY (editable);
|
|
GtkEntry *gtkentry = GTK_ENTRY (editable);
|
|
gboolean empty = gtkentry->text_length == 0;
|
|
|
|
if ((empty && !entry->priv->empty) ||
|
|
(!empty && entry->priv->empty))
|
|
{
|
|
entry->priv->empty = empty;
|
|
g_object_notify (G_OBJECT (entry), "empty");
|
|
}
|
|
|
|
if (parent_editable_iface->changed)
|
|
parent_editable_iface->changed (editable);
|
|
}
|
|
|
|
|
|
void
|
|
moo_entry_undo (MooEntry *entry)
|
|
{
|
|
g_return_if_fail (MOO_IS_ENTRY (entry));
|
|
if (entry->priv->enable_undo && moo_undo_mgr_can_undo (entry->priv->undo_mgr))
|
|
moo_undo_mgr_undo (entry->priv->undo_mgr);
|
|
}
|
|
|
|
|
|
void
|
|
moo_entry_redo (MooEntry *entry)
|
|
{
|
|
g_return_if_fail (MOO_IS_ENTRY (entry));
|
|
if (entry->priv->enable_undo && moo_undo_mgr_can_redo (entry->priv->undo_mgr))
|
|
moo_undo_mgr_redo (entry->priv->undo_mgr);
|
|
}
|
|
|
|
|
|
void
|
|
moo_entry_begin_undo_group (MooEntry *entry)
|
|
{
|
|
g_return_if_fail (MOO_IS_ENTRY (entry));
|
|
moo_undo_mgr_start_group (entry->priv->undo_mgr);
|
|
}
|
|
|
|
|
|
void
|
|
moo_entry_end_undo_group (MooEntry *entry)
|
|
{
|
|
g_return_if_fail (MOO_IS_ENTRY (entry));
|
|
moo_undo_mgr_end_group (entry->priv->undo_mgr);
|
|
}
|
|
|
|
|
|
void
|
|
moo_entry_clear_undo (MooEntry *entry)
|
|
{
|
|
g_return_if_fail (MOO_IS_ENTRY (entry));
|
|
moo_undo_mgr_clear (entry->priv->undo_mgr);
|
|
}
|
|
|
|
|
|
MooUndoMgr*
|
|
moo_entry_get_undo_mgr (MooEntry *entry)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_ENTRY (entry), NULL);
|
|
return entry->priv->undo_mgr;
|
|
}
|
|
|
|
|
|
GtkWidget*
|
|
moo_entry_new (void)
|
|
{
|
|
return g_object_new (MOO_TYPE_ENTRY, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_populate_popup (GtkEntry *gtkentry,
|
|
GtkMenu *menu)
|
|
{
|
|
GtkWidget *item;
|
|
MooEntry *entry = MOO_ENTRY (gtkentry);
|
|
|
|
if (!entry->priv->enable_undo_menu)
|
|
return;
|
|
|
|
item = gtk_separator_menu_item_new ();
|
|
gtk_widget_show (item);
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
|
|
|
|
item = gtk_image_menu_item_new_from_stock (GTK_STOCK_REDO, NULL);
|
|
gtk_widget_show (item);
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
|
|
gtk_widget_set_sensitive (item, entry->priv->enable_undo &&
|
|
moo_undo_mgr_can_redo (entry->priv->undo_mgr));
|
|
g_signal_connect_swapped (item, "activate", G_CALLBACK (moo_entry_redo), entry);
|
|
|
|
item = gtk_image_menu_item_new_from_stock (GTK_STOCK_UNDO, NULL);
|
|
gtk_widget_show (item);
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
|
|
gtk_widget_set_sensitive (item, entry->priv->enable_undo &&
|
|
moo_undo_mgr_can_undo (entry->priv->undo_mgr));
|
|
g_signal_connect_swapped (item, "activate", G_CALLBACK (moo_entry_undo), entry);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_delete_from_cursor (GtkEntry *entry,
|
|
GtkDeleteType type,
|
|
gint count)
|
|
{
|
|
moo_undo_mgr_new_group (MOO_ENTRY(entry)->priv->undo_mgr);
|
|
GTK_ENTRY_CLASS(moo_entry_parent_class)->delete_from_cursor (entry, type, count);
|
|
}
|
|
|
|
static void
|
|
moo_entry_cut_clipboard (GtkEntry *entry)
|
|
{
|
|
moo_undo_mgr_new_group (MOO_ENTRY(entry)->priv->undo_mgr);
|
|
GTK_ENTRY_CLASS(moo_entry_parent_class)->cut_clipboard (entry);
|
|
}
|
|
|
|
static void
|
|
moo_entry_paste_clipboard (GtkEntry *entry)
|
|
{
|
|
moo_undo_mgr_new_group (MOO_ENTRY(entry)->priv->undo_mgr);
|
|
GTK_ENTRY_CLASS(moo_entry_parent_class)->paste_clipboard (entry);
|
|
}
|
|
|
|
static void
|
|
moo_entry_do_insert_text (GtkEditable *editable,
|
|
const gchar *text,
|
|
gint length,
|
|
gint *position)
|
|
{
|
|
if (length < 0)
|
|
length = strlen (text);
|
|
|
|
if (*position < 0)
|
|
*position = GTK_ENTRY(editable)->text_length;
|
|
|
|
if (length > 0)
|
|
{
|
|
moo_undo_mgr_add_action (MOO_ENTRY(editable)->priv->undo_mgr,
|
|
INSERT_ACTION_TYPE,
|
|
insert_action_new (editable, text, length, position));
|
|
parent_editable_iface->do_insert_text (editable, text, length, position);
|
|
}
|
|
}
|
|
|
|
static void
|
|
moo_entry_do_delete_text (GtkEditable *editable,
|
|
gint start_pos,
|
|
gint end_pos)
|
|
{
|
|
if (start_pos == end_pos)
|
|
return;
|
|
|
|
g_return_if_fail (start_pos >= 0);
|
|
|
|
if (end_pos < 0)
|
|
{
|
|
end_pos = GTK_ENTRY(editable)->text_length;
|
|
}
|
|
else if (start_pos > end_pos)
|
|
{
|
|
int tmp = start_pos;
|
|
start_pos = end_pos;
|
|
end_pos = tmp;
|
|
}
|
|
|
|
if (start_pos < end_pos)
|
|
{
|
|
moo_undo_mgr_add_action (MOO_ENTRY(editable)->priv->undo_mgr,
|
|
DELETE_ACTION_TYPE,
|
|
delete_action_new (editable, start_pos, end_pos));
|
|
parent_editable_iface->do_delete_text (editable, start_pos, end_pos);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_entry_delete_to_start (MooEntry *entry)
|
|
{
|
|
if (entry->priv->use_ctrl_u)
|
|
gtk_editable_delete_text (GTK_EDITABLE (entry),
|
|
0, gtk_editable_get_position (GTK_EDITABLE (entry)));
|
|
}
|
|
|
|
|
|
/*********************************************************************/
|
|
/* Working around idiotic gtk selection business
|
|
* TODO: make stealing primary optional, independent of
|
|
* clearing selection
|
|
*/
|
|
|
|
/* GtkEdiatble::delete_text and GtkWidget::realize might also require this hack */
|
|
|
|
static void
|
|
moo_entry_set_selection_bounds (GtkEditable *editable,
|
|
gint start_pos,
|
|
gint end_pos)
|
|
{
|
|
if (!MOO_ENTRY(editable)->priv->grab_selection)
|
|
MOO_ENTRY(editable)->priv->fool_entry = TRUE;
|
|
|
|
parent_editable_iface->set_selection_bounds (editable, start_pos, end_pos);
|
|
MOO_ENTRY(editable)->priv->fool_entry = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
moo_entry_get_selection_bounds (GtkEditable *editable,
|
|
gint *start_pos,
|
|
gint *end_pos)
|
|
{
|
|
if (MOO_ENTRY(editable)->priv->fool_entry)
|
|
return FALSE;
|
|
else
|
|
return parent_editable_iface->get_selection_bounds (editable, start_pos, end_pos);
|
|
}
|
|
|
|
static gboolean
|
|
moo_entry_button_release (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
gboolean result;
|
|
|
|
if (!MOO_ENTRY(widget)->priv->grab_selection)
|
|
MOO_ENTRY(widget)->priv->fool_entry = TRUE;
|
|
|
|
result = GTK_WIDGET_CLASS(moo_entry_parent_class)->button_release_event (widget, event);
|
|
MOO_ENTRY(widget)->priv->fool_entry = FALSE;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*********************************************************************/
|
|
/* Undo/redo
|
|
*/
|
|
|
|
typedef struct {
|
|
int pos;
|
|
char *text;
|
|
int length;
|
|
int chars;
|
|
} InsertAction;
|
|
|
|
typedef struct {
|
|
int start;
|
|
int end;
|
|
char *text;
|
|
gboolean forward;
|
|
} DeleteAction;
|
|
|
|
static void insert_action_undo (InsertAction *action,
|
|
GtkEditable *editable);
|
|
static void insert_action_redo (InsertAction *action,
|
|
GtkEditable *editable);
|
|
static gboolean insert_action_merge (InsertAction *action,
|
|
InsertAction *what);
|
|
static void insert_action_destroy (InsertAction *action);
|
|
|
|
static void delete_action_undo (DeleteAction *action,
|
|
GtkEditable *editable);
|
|
static void delete_action_redo (DeleteAction *action,
|
|
GtkEditable *editable);
|
|
static gboolean delete_action_merge (DeleteAction *action,
|
|
DeleteAction *what);
|
|
static void delete_action_destroy (DeleteAction *action);
|
|
|
|
|
|
static MooUndoActionClass InsertActionClass = {
|
|
(MooUndoActionUndo) insert_action_undo,
|
|
(MooUndoActionRedo) insert_action_redo,
|
|
(MooUndoActionMerge) insert_action_merge,
|
|
(MooUndoActionDestroy) insert_action_destroy
|
|
};
|
|
|
|
static MooUndoActionClass DeleteActionClass = {
|
|
(MooUndoActionUndo) delete_action_undo,
|
|
(MooUndoActionRedo) delete_action_redo,
|
|
(MooUndoActionMerge) delete_action_merge,
|
|
(MooUndoActionDestroy) delete_action_destroy
|
|
};
|
|
|
|
|
|
static void
|
|
init_undo_actions (void)
|
|
{
|
|
INSERT_ACTION_TYPE = moo_undo_action_register (&InsertActionClass);
|
|
DELETE_ACTION_TYPE = moo_undo_action_register (&DeleteActionClass);
|
|
}
|
|
|
|
|
|
static gpointer
|
|
insert_action_new (G_GNUC_UNUSED GtkEditable *editable,
|
|
const gchar *text,
|
|
gint length,
|
|
gint *position)
|
|
{
|
|
InsertAction *action;
|
|
|
|
if (length < 0)
|
|
length = strlen (text);
|
|
|
|
g_return_val_if_fail (length > 0, NULL);
|
|
|
|
action = g_new0 (InsertAction, 1);
|
|
|
|
action->pos = *position;
|
|
action->text = g_strndup (text, length);
|
|
action->length = length;
|
|
action->chars = g_utf8_strlen (text, length);
|
|
|
|
return action;
|
|
}
|
|
|
|
|
|
static gpointer
|
|
delete_action_new (GtkEditable *editable,
|
|
gint start_pos,
|
|
gint end_pos)
|
|
{
|
|
DeleteAction *action;
|
|
|
|
g_return_val_if_fail (start_pos < end_pos, NULL);
|
|
|
|
action = g_new0 (DeleteAction, 1);
|
|
|
|
action->start = start_pos;
|
|
action->end = end_pos;
|
|
|
|
action->text = gtk_editable_get_chars (editable, start_pos, end_pos);
|
|
|
|
/* figure out if the user used the Delete or the Backspace key */
|
|
if (gtk_editable_get_position (editable) <= action->start)
|
|
action->forward = TRUE;
|
|
else
|
|
action->forward = FALSE;
|
|
|
|
return action;
|
|
}
|
|
|
|
|
|
static void
|
|
insert_action_undo (InsertAction *action,
|
|
GtkEditable *editable)
|
|
{
|
|
gtk_editable_delete_text (editable, action->pos, action->pos + action->chars);
|
|
gtk_editable_set_position (editable, action->pos);
|
|
}
|
|
|
|
|
|
static void
|
|
delete_action_undo (DeleteAction *action,
|
|
GtkEditable *editable)
|
|
{
|
|
int pos_here = action->start;
|
|
|
|
gtk_editable_insert_text (editable, action->text, -1, &pos_here);
|
|
|
|
if (action->forward)
|
|
gtk_editable_set_position (editable, action->start);
|
|
else
|
|
gtk_editable_set_position (editable, action->end);
|
|
}
|
|
|
|
|
|
static void
|
|
insert_action_redo (InsertAction *action,
|
|
GtkEditable *editable)
|
|
{
|
|
int pos_here = action->pos;
|
|
gtk_editable_insert_text (editable, action->text, action->length, &pos_here);
|
|
gtk_editable_set_position (editable, action->pos + action->chars);
|
|
}
|
|
|
|
|
|
static void
|
|
delete_action_redo (DeleteAction *action,
|
|
GtkEditable *editable)
|
|
{
|
|
gtk_editable_delete_text (editable, action->start, action->end);
|
|
gtk_editable_set_position (editable, action->start);
|
|
}
|
|
|
|
|
|
static void
|
|
insert_action_destroy (InsertAction *action)
|
|
{
|
|
if (action)
|
|
{
|
|
g_free (action->text);
|
|
g_free (action);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
delete_action_destroy (DeleteAction *action)
|
|
{
|
|
if (action)
|
|
{
|
|
g_free (action->text);
|
|
g_free (action);
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
insert_action_merge (InsertAction *last_action,
|
|
InsertAction *action)
|
|
{
|
|
char *tmp;
|
|
|
|
if (action->pos != (last_action->pos + last_action->chars) ||
|
|
(action->text[0] != ' ' && action->text[0] != '\t' &&
|
|
(last_action->text[last_action->length-1] == ' ' ||
|
|
last_action->text[last_action->length-1] == '\t')))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
tmp = g_strconcat (last_action->text, action->text, NULL);
|
|
g_free (last_action->text);
|
|
last_action->length += action->length;
|
|
last_action->text = tmp;
|
|
last_action->chars += action->chars;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
delete_action_merge (DeleteAction *last_action,
|
|
DeleteAction *action)
|
|
{
|
|
char *tmp;
|
|
|
|
if (last_action->forward != action->forward ||
|
|
(last_action->start != action->start &&
|
|
last_action->start != action->end))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (last_action->start == action->start)
|
|
{
|
|
char *text_end = g_utf8_offset_to_pointer (last_action->text,
|
|
last_action->end - last_action->start - 1);
|
|
|
|
/* Deleted with the delete key */
|
|
if (action->text[0] != ' ' && action->text[0] != '\t' &&
|
|
(*text_end == ' ' || *text_end == '\t'))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
tmp = g_strconcat (last_action->text, action->text, NULL);
|
|
g_free (last_action->text);
|
|
last_action->end += (action->end - action->start);
|
|
last_action->text = tmp;
|
|
}
|
|
else
|
|
{
|
|
/* Deleted with the backspace key */
|
|
if (action->text[0] != ' ' && action->text[0] != '\t' &&
|
|
(last_action->text[0] == ' ' || last_action->text[0] == '\t'))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
tmp = g_strconcat (action->text, last_action->text, NULL);
|
|
g_free (last_action->text);
|
|
last_action->start = action->start;
|
|
last_action->text = tmp;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|