Mercurial > pidgin.yaz
changeset 18024:92e27556160c
propagate from branch 'im.pidgin.pidgin' (head f7d4b1ae759e694a319fc8d37f917e1c2b2e1d3b)
to branch 'im.pidgin.pidgin.2.1.0' (head dce75f5f530b1bbe99023326b59a284e5f3578dd)
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Wed, 30 May 2007 05:37:04 +0000 |
parents | 2188c95d0311 (diff) c52b7911a229 (current diff) |
children | 7790021b1270 |
files | ChangeLog configure.ac pidgin/gtkaccount.c pidgin/gtkblist.c pidgin/gtkconv.c pidgin/gtkimhtml.c pidgin/gtkprefs.c |
diffstat | 50 files changed, 1726 insertions(+), 132 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Wed May 30 05:35:59 2007 +0000 +++ b/ChangeLog Wed May 30 05:37:04 2007 +0000 @@ -1,5 +1,19 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.1.0 (??/??/????): + libpurple: + * Core changes to allow UIs to use second-granularity for scheduling. + Pidgin and Finch, which use the glib event loop, were changed to use + g_timeout_add_seconds() on glib >= 2.14 when possible. This allows + glib to better group our longer timers to increase power efficiency. + (Arjan van de Ven with Intel Corporation) + + Pidgin: + * Ensure only one copy of Pidgin is running with a given configuration + directory. The net effect of this is that trying to start Pidgin a + second time will raise the buddy list. (Gabriel Schulhof) + * Undo capability in the conversation window + version 2.0.2 (??/??/????): libpurple: * Moving an ICQ buddy from one group to another no longer
--- a/ChangeLog.API Wed May 30 05:35:59 2007 +0000 +++ b/ChangeLog.API Wed May 30 05:37:04 2007 +0000 @@ -1,5 +1,19 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.1.0 (??/??/????): + Added: + * PurpleEventLoopUiOps.timeout_add_seconds + UIs can now use better scheduling for whole-second timers. For + example, clients based on the glib event loop can now use + g_timeout_add_seconds(). + * pidgin_create_window() + * purple_core_ensure_single_instance() + This is for UIs to use to ensure only one copy is running. + * purple_dbus_is_owner() + * purple_timeout_add_seconds() + Callers should prefer this to purple_timeout_add() for timers + longer than 1 second away. Be aware of the rounding, though. + version 2.0.0 (5/3/2007): Please note all functions, defines, and data structures have been re-namespaced to match the new names of Pidgin, Finch, and libpurple.
--- a/configure.ac Wed May 30 05:35:59 2007 +0000 +++ b/configure.ac Wed May 30 05:37:04 2007 +0000 @@ -43,10 +43,10 @@ # # Make sure to update finch/libgnt/configure.ac with libgnt version changes. # -m4_define([purple_lt_current], [0]) +m4_define([purple_lt_current], [1]) m4_define([purple_major_version], [2]) -m4_define([purple_minor_version], [0]) -m4_define([purple_micro_version], [2]) +m4_define([purple_minor_version], [1]) +m4_define([purple_micro_version], [0]) m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) @@ -55,7 +55,7 @@ m4_define([gnt_lt_current], [0]) m4_define([gnt_major_version], [1]) m4_define([gnt_minor_version], [0]) -m4_define([gnt_micro_version], [2]) +m4_define([gnt_micro_version], [1]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/finch/finch.c Wed May 30 05:35:59 2007 +0000 +++ b/finch/finch.c Wed May 30 05:37:04 2007 +0000 @@ -156,11 +156,15 @@ gnt_input_add, g_source_remove, NULL, /* input_get_error */ +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else + NULL, +#endif /* padding */ NULL, NULL, - NULL, NULL };
--- a/libpurple/account.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/account.c Wed May 30 05:37:04 2007 +0000 @@ -417,7 +417,7 @@ schedule_accounts_save() { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/blist.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/blist.c Wed May 30 05:37:04 2007 +0000 @@ -365,7 +365,7 @@ purple_blist_schedule_save() { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/connection.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/connection.c Wed May 30 05:37:04 2007 +0000 @@ -72,7 +72,7 @@ if (on && !gc->keepalive) { purple_debug_info("connection", "Activating keepalive.\n"); - gc->keepalive = purple_timeout_add(30000, send_keepalive, gc); + gc->keepalive = purple_timeout_add_seconds(30, send_keepalive, gc); } else if (!on && gc->keepalive > 0) {
--- a/libpurple/conversation.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/conversation.c Wed May 30 05:37:04 2007 +0000 @@ -1010,7 +1010,7 @@ conv = purple_conv_im_get_conversation(im); name = purple_conversation_get_name(conv); - im->typing_timeout = purple_timeout_add(timeout * 1000, reset_typing_cb, conv); + im->typing_timeout = purple_timeout_add_seconds(timeout, reset_typing_cb, conv); } void
--- a/libpurple/core.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/core.c Wed May 30 05:37:04 2007 +0000 @@ -48,7 +48,11 @@ #include "util.h" #ifdef HAVE_DBUS +# define DBUS_API_SUBJECT_TO_CHANGE +# include <dbus/dbus.h> +# include "dbus-purple.h" # include "dbus-server.h" +# include "dbus-bindings.h" #endif struct PurpleCore @@ -276,6 +280,91 @@ return _ops; } +#ifdef HAVE_DBUS +static char *purple_dbus_owner_user_dir(void) +{ + DBusMessage *msg = NULL, *reply = NULL; + DBusConnection *dbus_connection = NULL; + DBusError dbus_error; + char *remote_user_dir = NULL; + + if ((dbus_connection = purple_dbus_get_connection()) == NULL) + return NULL; + + if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleUserDir")) == NULL) + return NULL; + + dbus_error_init(&dbus_error); + reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error); + dbus_message_unref(msg); + dbus_error_free(&dbus_error); + + if (reply) + { + dbus_error_init(&dbus_error); + dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_STRING, &remote_user_dir, DBUS_TYPE_INVALID); + remote_user_dir = g_strdup(remote_user_dir); + dbus_error_free(&dbus_error); + dbus_message_unref(reply); + } + + return remote_user_dir; +} + +static void purple_dbus_owner_show_buddy_list(void) +{ + DBusError dbus_error; + DBusMessage *msg = NULL, *reply = NULL; + DBusConnection *dbus_connection = NULL; + + if ((dbus_connection = purple_dbus_get_connection()) == NULL) + return; + + if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleBlistShow")) == NULL) + return; + + dbus_error_init(&dbus_error); + if ((reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error)) != NULL) + { + dbus_message_unref(msg); + } + dbus_error_free(&dbus_error); +} +#endif /* HAVE_DBUS */ + +gboolean +purple_core_ensure_single_instance() +{ + gboolean is_single_instance = TRUE; +#ifdef HAVE_DBUS + /* in the future, other mechanisms might have already set this to FALSE */ + if (is_single_instance) + { + if (!purple_dbus_is_owner()) + { + const char *user_dir = purple_user_dir(); + char *dbus_owner_user_dir = purple_dbus_owner_user_dir(); + + if (NULL == user_dir && NULL != dbus_owner_user_dir) + is_single_instance = TRUE; + else if (NULL != user_dir && NULL == dbus_owner_user_dir) + is_single_instance = TRUE; + else if (NULL == user_dir && NULL == dbus_owner_user_dir) + is_single_instance = FALSE; + else + is_single_instance = strcmp(dbus_owner_user_dir, user_dir); + + if (!is_single_instance) + purple_dbus_owner_show_buddy_list(); + + g_free(dbus_owner_user_dir); + } + } +#endif /* HAVE_DBUS */ + + return is_single_instance; +} + static gboolean move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative) {
--- a/libpurple/core.h Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/core.h Wed May 30 05:37:04 2007 +0000 @@ -121,6 +121,16 @@ */ gboolean purple_core_migrate(void); +/** + * Ensures that only one instance is running. + * + * @return A boolean such that @c TRUE indicates that this is the first instance, + * whereas @c FALSE indicates that there is another instance running. + * + * @since 2.1.0 + */ +gboolean purple_core_ensure_single_instance(void); + #ifdef __cplusplus } #endif
--- a/libpurple/dbus-server.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/dbus-server.c Wed May 30 05:37:04 2007 +0000 @@ -65,6 +65,12 @@ static GHashTable *map_id_type; static gchar *init_error; +static int dbus_request_name_reply = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + +gboolean purple_dbus_is_owner(void) +{ + return(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER == dbus_request_name_reply); +} /** * This function initializes the pointer-id traslation system. It @@ -592,6 +598,7 @@ return; } + dbus_request_name_reply = result = dbus_bus_request_name(purple_dbus_connection, DBUS_SERVICE_PURPLE, 0, &error);
--- a/libpurple/dbus-server.h Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/dbus-server.h Wed May 30 05:37:04 2007 +0000 @@ -169,6 +169,13 @@ void *purple_dbus_get_handle(void); /** + * Determines whether this instance owns the DBus service name + * + * @since 2.1.0 + */ +gboolean purple_dbus_is_owner(void); + +/** * Starts Purple's D-BUS server. It is responsible for handling DBUS * requests from other applications. */
--- a/libpurple/eventloop.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/eventloop.c Wed May 30 05:37:04 2007 +0000 @@ -35,6 +35,17 @@ return ops->timeout_add(interval, function, data); } +guint +purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) +{ + PurpleEventLoopUiOps *ops = purple_eventloop_get_ui_ops(); + + if (ops->timeout_add_seconds) + return ops->timeout_add_seconds(interval, function, data); + else + return ops->timeout_add(1000 * interval, function, data); +} + gboolean purple_timeout_remove(guint tag) {
--- a/libpurple/eventloop.h Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/eventloop.h Wed May 30 05:37:04 2007 +0000 @@ -48,7 +48,7 @@ struct _PurpleEventLoopUiOps { /** - * Creates a callback timer. + * Creates a callback timer with an interval measured in milliseconds. * @see g_timeout_add, purple_timeout_add **/ guint (*timeout_add)(guint interval, GSourceFunc function, gpointer data); @@ -81,7 +81,20 @@ */ int (*input_get_error)(int fd, int *error); - void (*_purple_reserved1)(void); + /** + * Creates a callback timer with an interval measured in seconds. + * + * This allows UIs to group timers for better power efficiency. For + * this reason, @a interval may be rounded by up to a second. + * + * Implementation of this UI op is optional. If it's not implemented, + * calls to purple_timeout_add_seconds() will be serviced by the + * timeout_add UI op. + * + * @see g_timeout_add_seconds, purple_timeout_add_seconds() + **/ + guint (*timeout_add_seconds)(guint interval, GSourceFunc function, gpointer data); + void (*_purple_reserved2)(void); void (*_purple_reserved3)(void); void (*_purple_reserved4)(void); @@ -93,10 +106,15 @@ /*@{*/ /** * Creates a callback timer. + * * The timer will repeat until the function returns @c FALSE. The * first call will be at the end of the first interval. + * + * If the timer is in a multiple of seconds, use purple_timeout_add_seconds() + * instead as it allows UIs to group timers for power efficiency. + * * @param interval The time between calls of the function, in - * milliseconds. + * milliseconds. * @param function The function to call. * @param data data to pass to @a function. * @return A handle to the timer which can be passed to @@ -105,6 +123,24 @@ guint purple_timeout_add(guint interval, GSourceFunc function, gpointer data); /** + * Creates a callback timer. + * + * The timer will repeat until the function returns @c FALSE. The + * first call will be at the end of the first interval. + * + * This function allows UIs to group timers for better power efficiency. For + * this reason, @a interval may be rounded by up to a second. + * + * @param interval The time between calls of the function, in + * seconds. + * @param function The function to call. + * @param data data to pass to @a function. + * @return A handle to the timer which can be passed to + * purple_timeout_remove to remove the timer. + */ +guint purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data); + +/** * Removes a timeout handler. * * @param handle The handle, as returned by purple_timeout_add.
--- a/libpurple/example/nullclient.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/example/nullclient.c Wed May 30 05:37:04 2007 +0000 @@ -108,11 +108,15 @@ glib_input_add, g_source_remove, NULL, +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else + NULL, +#endif /* padding */ NULL, NULL, - NULL, NULL }; /*** End of the eventloop functions. ***/
--- a/libpurple/idle.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/idle.c Wed May 30 05:37:04 2007 +0000 @@ -229,7 +229,11 @@ if (time_until_next_idle_event == 0) idle_timer = 0; else - idle_timer = purple_timeout_add(1000 * (time_until_next_idle_event + 1), check_idleness_timer, NULL); + { + /* +1 for the boundary, + * +1 more for g_timeout_add_seconds rounding. */ + idle_timer = purple_timeout_add_seconds(time_until_next_idle_event + 2, check_idleness_timer, NULL); + } return FALSE; } @@ -308,8 +312,10 @@ void purple_idle_init() { - /* Add the timer to check if we're idle */ - idle_timer = purple_timeout_add(1000 * (IDLEMARK + 1), check_idleness_timer, NULL); + /* Add the timer to check if we're idle. + * IDLEMARK + 1 as the boundary, + * +1 more for g_timeout_add_seconds rounding. */ + idle_timer = purple_timeout_add_seconds((IDLEMARK + 2), check_idleness_timer, NULL); purple_signal_connect(purple_conversations_get_handle(), "sent-im-msg", purple_idle_get_handle(),
--- a/libpurple/pounce.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/pounce.c Wed May 30 05:37:04 2007 +0000 @@ -273,7 +273,7 @@ schedule_pounces_save(void) { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/prefs.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/prefs.c Wed May 30 05:37:04 2007 +0000 @@ -226,7 +226,7 @@ schedule_prefs_save(void) { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/savedstatuses.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/savedstatuses.c Wed May 30 05:37:04 2007 +0000 @@ -357,7 +357,7 @@ schedule_save(void) { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/server.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/server.c Wed May 30 05:37:04 2007 +0000 @@ -92,7 +92,7 @@ /* because we're modifying or creating a lar, schedule the * function to expire them as the pref dictates */ - purple_timeout_add((SECS_BEFORE_RESENDING_AUTORESPONSE + 1) * 1000, expire_last_auto_responses, NULL); + purple_timeout_add_seconds((SECS_BEFORE_RESENDING_AUTORESPONSE + 1), expire_last_auto_responses, NULL); tmp = last_auto_responses;
--- a/libpurple/util.c Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/util.c Wed May 30 05:37:04 2007 +0000 @@ -155,6 +155,31 @@ return data; } +gchar * +purple_base16_encode_chunked(const guchar *data, gsize len) +{ + int i; + gchar *ascii = NULL; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(len > 0, NULL); + + /* For each byte of input, we need 2 bytes for the hex representation + * and 1 for the colon. + * The final colon will be replaced by a terminating NULL + */ + ascii = g_malloc(len * 3 + 1); + + for (i = 0; i < len; i++) + g_snprintf(&ascii[i * 3], 4, "%02hhx:", data[i]); + + /* Replace the final colon with NULL */ + ascii[len * 3 - 1] = 0; + + return ascii; +} + + /************************************************************************** * Base64 Functions **************************************************************************/
--- a/libpurple/util.h Wed May 30 05:35:59 2007 +0000 +++ b/libpurple/util.h Wed May 30 05:37:04 2007 +0000 @@ -118,6 +118,21 @@ */ guchar *purple_base16_decode(const char *str, gsize *ret_len); +/** + * Converts a chunk of binary data to a chunked base-16 representation + * (handy for key fingerprints) + * + * Example output: 01:23:45:67:89:AB:CD:EF + * + * @param data The data to convert. + * @param len The length of the data. + * + * @return The base-16 string in the ASCII chunked encoding. Must be + * g_free'd when no longer needed. + */ +gchar *purple_base16_encode_chunked(const guchar *data, gsize len); + + /*@}*/ /**************************************************************************/
--- a/pidgin/Makefile.am Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/Makefile.am Wed May 30 05:37:04 2007 +0000 @@ -109,6 +109,8 @@ gtksession.c \ gtksound.c \ gtksourceiter.c \ + gtksourceundomanager.c \ + gtksourceview-marshal.c \ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \ @@ -156,6 +158,8 @@ gtksession.h \ gtksound.h \ gtksourceiter.h \ + gtksourceundomanager.h \ + gtksourceview-marshal.h \ gtkstatusbox.h \ pidginstock.h \ gtkthemes.h \
--- a/pidgin/gtkaccount.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkaccount.c Wed May 30 05:37:04 2007 +0000 @@ -1465,17 +1465,8 @@ dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win), "account"); - - if (type == PIDGIN_ADD_ACCOUNT_DIALOG) - gtk_window_set_title(GTK_WINDOW(win), _("Add Account")); - else - gtk_window_set_title(GTK_WINDOW(win), _("Modify Account")); - - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); + dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"), + PIDGIN_HIG_BORDER, "account", FALSE); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(account_win_destroy_cb), dialog); @@ -2312,7 +2303,6 @@ GtkWidget *button; int width, height; - if (accounts_window != NULL) { gtk_window_present(GTK_WINDOW(accounts_window->window)); return; @@ -2323,11 +2313,8 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height"); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = win = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); - gtk_window_set_role(GTK_WINDOW(win), "accounts"); - gtk_window_set_title(GTK_WINDOW(win), _("Accounts")); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(accedit_win_destroy_cb), accounts_window);
--- a/pidgin/gtkblist.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkblist.c Wed May 30 05:37:04 2007 +0000 @@ -4194,7 +4194,7 @@ {"application/x-im-contact", 0, DRAG_BUDDY}, {"text/x-vcard", 0, DRAG_VCARD }}; if (gtkblist && gtkblist->window) { - purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible")); + purple_blist_set_visible(TRUE); return; } @@ -4203,9 +4203,7 @@ gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32); gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000); - gtkblist->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(gtkblist->window), "buddy_list"); - gtk_window_set_title(GTK_WINDOW(gtkblist->window), _("Buddy List")); + gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE); g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event", G_CALLBACK(blist_focus_cb), gtkblist); GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
--- a/pidgin/gtkconv.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkconv.c Wed May 30 05:37:04 2007 +0000 @@ -8049,10 +8049,7 @@ window_list = g_list_append(window_list, win); /* Create the window. */ - win->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win->window), "conversation"); - gtk_window_set_resizable(GTK_WINDOW(win->window), TRUE); - gtk_container_set_border_width(GTK_CONTAINER(win->window), 0); + win->window = pidgin_create_window(NULL, 0, "conversation", TRUE); GTK_WINDOW(win->window)->allow_shrink = TRUE; if (available_list == NULL) {
--- a/pidgin/gtkeventloop.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkeventloop.c Wed May 30 05:37:04 2007 +0000 @@ -120,7 +120,11 @@ pidgin_input_add, g_source_remove, NULL, /* input_get_error */ +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else NULL, +#endif NULL, NULL, NULL
--- a/pidgin/gtkft.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkft.c Wed May 30 05:37:04 2007 +0000 @@ -758,10 +758,7 @@ purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished"); /* Create the window. */ - dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(window), "file transfer"); - gtk_window_set_title(GTK_WINDOW(window), _("File Transfers")); - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); + dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/gtkimhtml.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkimhtml.c Wed May 30 05:37:04 2007 +0000 @@ -31,6 +31,8 @@ #include "util.h" #include "gtkimhtml.h" #include "gtksourceiter.h" +#include "gtksourceundomanager.h" +#include "gtksourceview-marshal.h" #include <gtk/gtk.h> #include <glib/gerror.h> #include <gdk/gdkkeysyms.h> @@ -136,6 +138,8 @@ CLEAR_FORMAT, UPDATE_FORMAT, MESSAGE_SEND, + UNDO, + REDO, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0 }; @@ -1126,6 +1130,23 @@ return FALSE; } +static void +gtk_imhtml_undo(GtkIMHtml *imhtml) { + g_return_if_fail(GTK_IS_IMHTML(imhtml)); + g_return_if_fail(imhtml->editable); + + gtk_source_undo_manager_undo(imhtml->undo_manager); +} + +static void +gtk_imhtml_redo(GtkIMHtml *imhtml) { + g_return_if_fail(GTK_IS_IMHTML(imhtml)); + g_return_if_fail(imhtml->editable); + + gtk_source_undo_manager_redo(imhtml->undo_manager); + +} + static gboolean imhtml_message_send(GtkIMHtml *imhtml) { return FALSE; @@ -1209,6 +1230,7 @@ g_queue_free(imhtml->animations); g_free(imhtml->protocol_name); g_free(imhtml->search_string); + g_object_unref(imhtml->undo_manager); G_OBJECT_CLASS(parent_class)->finalize (object); } @@ -1272,10 +1294,32 @@ NULL, 0, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals [UNDO] = g_signal_new ("undo", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkIMHtmlClass, undo), + NULL, + NULL, + gtksourceview_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [REDO] = g_signal_new ("redo", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkIMHtmlClass, redo), + NULL, + NULL, + gtksourceview_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + klass->toggle_format = imhtml_toggle_format; klass->message_send = imhtml_message_send; klass->clear_format = imhtml_clear_formatting; + klass->undo = gtk_imhtml_undo; + klass->redo = gtk_imhtml_redo; gobject_class->finalize = gtk_imhtml_finalize; widget_class->drag_motion = gtk_text_view_drag_motion; @@ -1300,12 +1344,17 @@ gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0); gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0); gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0); + 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_F14, 0, "undo", 0); + } static void gtk_imhtml_init (GtkIMHtml *imhtml) { GtkTextIter iter; imhtml->text_buffer = gtk_text_buffer_new(NULL); + imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer); gtk_text_buffer_get_end_iter (imhtml->text_buffer, &iter); gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
--- a/pidgin/gtkimhtml.h Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkimhtml.h Wed May 30 05:37:04 2007 +0000 @@ -27,6 +27,7 @@ #include <gtk/gtktextview.h> #include <gtk/gtktooltips.h> #include <gtk/gtkimage.h> +#include "gtksourceundomanager.h" #ifdef __cplusplus extern "C" { @@ -126,6 +127,7 @@ GSList *im_images; GtkIMHtmlFuncs *funcs; + GtkSourceUndoManager *undo_manager; }; struct _GtkIMHtmlClass { @@ -137,6 +139,8 @@ void (*clear_format)(GtkIMHtml *); void (*update_format)(GtkIMHtml *); gboolean (*message_send)(GtkIMHtml *); + void (*undo)(GtkIMHtml *); + void (*redo)(GtkIMHtml *); }; struct _GtkIMHtmlFontDetail {
--- a/pidgin/gtkmain.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkmain.c Wed May 30 05:37:04 2007 +0000 @@ -730,6 +730,15 @@ abort(); } + if (!purple_core_ensure_single_instance()) { + purple_core_quit(); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + } + + /* TODO: Move blist loading into purple_blist_init() */ purple_set_blist(purple_blist_new()); purple_blist_load();
--- a/pidgin/gtknotify.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtknotify.c Wed May 30 05:37:04 2007 +0000 @@ -577,10 +577,8 @@ char label_text[2048]; char *linked_text, *primary_esc, *secondary_esc; - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), title); + window = pidgin_create_window(title, PIDGIN_HIG_BORDER, NULL, TRUE); gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(formatted_close_cb), NULL); @@ -712,10 +710,8 @@ data->results = results; /* Create the window */ - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), (title ? title :_("Search Results"))); + pidgin_create_window(title ? title :_("Search Results"), PIDGIN_HIG_BORDER, NULL, TRUE); gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); g_signal_connect_swapped(G_OBJECT(window), "delete_event", G_CALLBACK(searchresults_close_cb), data);
--- a/pidgin/gtkpounce.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkpounce.c Wed May 30 05:37:04 2007 +0000 @@ -498,15 +498,9 @@ sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); /* Create the window. */ - dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = window = pidgin_create_window((cur_pounce == NULL ? _("New Buddy Pounce") : _("Edit Buddy Pounce")), + PIDGIN_HIG_BORDER, "buddy_pounce", FALSE) ; gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_role(GTK_WINDOW(window), "buddy_pounce"); - gtk_window_set_resizable(GTK_WINDOW(window), FALSE); - gtk_window_set_title(GTK_WINDOW(window), - (cur_pounce == NULL - ? _("New Buddy Pounce") : _("Edit Buddy Pounce"))); - - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog); @@ -1323,11 +1317,8 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/height"); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); - gtk_window_set_role(GTK_WINDOW(win), "pounces"); - gtk_window_set_title(GTK_WINDOW(win), _("Buddy Pounces")); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(pounces_manager_destroy_cb), dialog);
--- a/pidgin/gtkprefs.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkprefs.c Wed May 30 05:37:04 2007 +0000 @@ -1950,11 +1950,7 @@ /* Back to instant-apply! I win! BU-HAHAHA! */ /* Create the window */ - prefs = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(prefs), "preferences"); - gtk_window_set_title(GTK_WINDOW(prefs), _("Preferences")); - gtk_window_set_resizable (GTK_WINDOW(prefs), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(prefs), PIDGIN_HIG_BORDER); + prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE); g_signal_connect(G_OBJECT(prefs), "destroy", G_CALLBACK(delete_prefs), NULL);
--- a/pidgin/gtkprivacy.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkprivacy.c Wed May 30 05:37:04 2007 +0000 @@ -366,11 +366,7 @@ dialog = g_new0(PidginPrivacyDialog, 1); - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE); - gtk_window_set_role(GTK_WINDOW(dialog->win), "privacy"); - gtk_window_set_title(GTK_WINDOW(dialog->win), _("Privacy")); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), PIDGIN_HIG_BORDER); + dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", FALSE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(destroy_cb), dialog);
--- a/pidgin/gtkrequest.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkrequest.c Wed May 30 05:37:04 2007 +0000 @@ -1069,16 +1069,12 @@ data->cbs[0] = ok_cb; data->cbs[1] = cancel_cb; - data->dialog = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - - if (title != NULL) - gtk_window_set_title(GTK_WINDOW(win), title); + #ifdef _WIN32 - gtk_window_set_title(GTK_WINDOW(win), PIDGIN_ALERT_TITLE); -#endif - - gtk_window_set_role(GTK_WINDOW(win), "multifield"); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); + data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; +#else /* !_WIN32 */ + data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ; +#endif /* _WIN32 */ g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(destroy_multifield_cb), data);
--- a/pidgin/gtkroomlist.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkroomlist.c Wed May 30 05:37:04 2007 +0000 @@ -371,11 +371,7 @@ dialog->account = account; /* Create the window. */ - dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(window), "room list"); - gtk_window_set_title(GTK_WINDOW(window), _("Room List")); - - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); + dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/gtksavedstatuses.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtksavedstatuses.c Wed May 30 05:37:04 2007 +0000 @@ -551,11 +551,8 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height"); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = win = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); - gtk_window_set_role(GTK_WINDOW(win), "statuses"); - gtk_window_set_title(GTK_WINDOW(win), _("Saved Statuses")); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_window_destroy_cb), dialog); @@ -1085,11 +1082,7 @@ if (edit) dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status)); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win), "status"); - gtk_window_set_title(GTK_WINDOW(win), _("Status")); - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); + dialog->window = win = pidgin_create_window (_("Status"), PIDGIN_HIG_BORDER, "status", FALSE) ; g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_editor_destroy_cb), dialog); @@ -1423,13 +1416,9 @@ dialog->status_editor = status_editor; dialog->account = account; - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win), "substatus"); tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account)); - gtk_window_set_title(GTK_WINDOW(win), tmp); + dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", FALSE) ; g_free(tmp); - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(substatus_editor_destroy_cb), dialog);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceundomanager.c Wed May 30 05:37:04 2007 +0000 @@ -0,0 +1,1122 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gtksourceundomanager.c + * This file is part of GtkSourceView + * + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence + * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi + * Copyright (C) 2002-2005 Paolo Maggi + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <stdlib.h> +#include <string.h> + +#include "gtksourceundomanager.h" +#include "gtksourceview-marshal.h" + + +#define DEFAULT_MAX_UNDO_LEVELS 25 + + +typedef struct _GtkSourceUndoAction GtkSourceUndoAction; +typedef struct _GtkSourceUndoInsertAction GtkSourceUndoInsertAction; +typedef struct _GtkSourceUndoDeleteAction GtkSourceUndoDeleteAction; + +typedef enum { + GTK_SOURCE_UNDO_ACTION_INSERT, + GTK_SOURCE_UNDO_ACTION_DELETE +} GtkSourceUndoActionType; + +/* + * We use offsets instead of GtkTextIters because the last ones + * require to much memory in this context without giving us any advantage. + */ + +struct _GtkSourceUndoInsertAction +{ + gint pos; + gchar *text; + gint length; + gint chars; +}; + +struct _GtkSourceUndoDeleteAction +{ + gint start; + gint end; + gchar *text; + gboolean forward; +}; + +struct _GtkSourceUndoAction +{ + GtkSourceUndoActionType action_type; + + union { + GtkSourceUndoInsertAction insert; + GtkSourceUndoDeleteAction delete; + } action; + + gint order_in_group; + + /* It is TRUE whether the action can be merged with the following action. */ + guint mergeable : 1; + + /* It is TRUE whether the action is marked as "modified". + * An action is marked as "modified" if it changed the + * state of the buffer from "not modified" to "modified". Only the first + * action of a group can be marked as modified. + * There can be a single action marked as "modified" in the actions list. + */ + guint modified : 1; +}; + +/* INVALID is a pointer to an invalid action */ +#define INVALID ((void *) "IA") + +struct _GtkSourceUndoManagerPrivate +{ + GtkTextBuffer *document; + + GList* actions; + gint next_redo; + + gint actions_in_current_group; + + gint running_not_undoable_actions; + + gint num_of_groups; + + gint max_undo_levels; + + guint can_undo : 1; + guint can_redo : 1; + + /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1), + * the state of the buffer changed from "not modified" to "modified". + */ + guint modified_undoing_group : 1; + + /* Pointer to the action (in the action list) marked as "modified". + * It is NULL when no action is marked as "modified". + * It is INVALID when the action marked as "modified" has been removed + * from the action list (freeing the list or resizing it) */ + GtkSourceUndoAction *modified_action; +}; + +enum { + CAN_UNDO, + CAN_REDO, + LAST_SIGNAL +}; + +static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass); +static void gtk_source_undo_manager_init (GtkSourceUndoManager *um); +static void gtk_source_undo_manager_finalize (GObject *object); + +static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, + GtkTextIter *pos, + const gchar *text, + gint length, + GtkSourceUndoManager *um); +static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + GtkSourceUndoManager *um); +static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, + GtkSourceUndoManager *um); +static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, + GtkSourceUndoManager *um); + +static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um); + +static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action); +static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, + gint n); +static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um); + +static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action); + +static GObjectClass *parent_class = NULL; +static guint undo_manager_signals [LAST_SIGNAL] = { 0 }; + +GType +gtk_source_undo_manager_get_type (void) +{ + static GType undo_manager_type = 0; + + if (undo_manager_type == 0) + { + static const GTypeInfo our_info = + { + sizeof (GtkSourceUndoManagerClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_source_undo_manager_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkSourceUndoManager), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_source_undo_manager_init + }; + + undo_manager_type = g_type_register_static (G_TYPE_OBJECT, + "GtkSourceUndoManager", + &our_info, + 0); + } + + return undo_manager_type; +} + +static void +gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->finalize = gtk_source_undo_manager_finalize; + + klass->can_undo = NULL; + klass->can_redo = NULL; + + undo_manager_signals[CAN_UNDO] = + g_signal_new ("can_undo", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo), + NULL, NULL, + gtksourceview_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); + + undo_manager_signals[CAN_REDO] = + g_signal_new ("can_redo", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo), + NULL, NULL, + gtksourceview_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); +} + +static void +gtk_source_undo_manager_init (GtkSourceUndoManager *um) +{ + um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1); + + um->priv->actions = NULL; + um->priv->next_redo = 0; + + um->priv->can_undo = FALSE; + um->priv->can_redo = FALSE; + + um->priv->running_not_undoable_actions = 0; + + um->priv->num_of_groups = 0; + + um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS; + + um->priv->modified_action = NULL; + + um->priv->modified_undoing_group = FALSE; +} + +static void +gtk_source_undo_manager_finalize (GObject *object) +{ + GtkSourceUndoManager *um; + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object)); + + um = GTK_SOURCE_UNDO_MANAGER (object); + + g_return_if_fail (um->priv != NULL); + + if (um->priv->actions != NULL) + { + gtk_source_undo_manager_free_action_list (um); + } + + g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), + G_CALLBACK (gtk_source_undo_manager_delete_range_handler), + um); + + g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), + G_CALLBACK (gtk_source_undo_manager_insert_text_handler), + um); + + g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), + G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), + um); + + g_free (um->priv); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GtkSourceUndoManager* +gtk_source_undo_manager_new (GtkTextBuffer* buffer) +{ + GtkSourceUndoManager *um; + + um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL)); + + g_return_val_if_fail (um->priv != NULL, NULL); + um->priv->document = buffer; + + g_signal_connect (G_OBJECT (buffer), "insert_text", + G_CALLBACK (gtk_source_undo_manager_insert_text_handler), + um); + + g_signal_connect (G_OBJECT (buffer), "delete_range", + G_CALLBACK (gtk_source_undo_manager_delete_range_handler), + um); + + g_signal_connect (G_OBJECT (buffer), "begin_user_action", + G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), + um); + + g_signal_connect (G_OBJECT (buffer), "modified_changed", + G_CALLBACK (gtk_source_undo_manager_modified_changed_handler), + um); + return um; +} + +void +gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + ++um->priv->running_not_undoable_actions; +} + +static void +gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + g_return_if_fail (um->priv->running_not_undoable_actions > 0); + + --um->priv->running_not_undoable_actions; +} + +void +gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + gtk_source_undo_manager_end_not_undoable_action_internal (um); + + if (um->priv->running_not_undoable_actions == 0) + { + gtk_source_undo_manager_free_action_list (um); + + um->priv->next_redo = -1; + + if (um->priv->can_undo) + { + um->priv->can_undo = FALSE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_UNDO], + 0, + FALSE); + } + + if (um->priv->can_redo) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_REDO], + 0, + FALSE); + } + } +} + +gboolean +gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um) +{ + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); + g_return_val_if_fail (um->priv != NULL, FALSE); + + return um->priv->can_undo; +} + +gboolean +gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um) +{ + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); + g_return_val_if_fail (um->priv != NULL, FALSE); + + return um->priv->can_redo; +} + +static void +set_cursor (GtkTextBuffer *buffer, gint cursor) +{ + GtkTextIter iter; + + /* Place the cursor at the requested position */ + gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor); + gtk_text_buffer_place_cursor (buffer, &iter); +} + +static void +insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len) +{ + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); + gtk_text_buffer_insert (buffer, &iter, text, len); +} + +static void +delete_text (GtkTextBuffer *buffer, gint start, gint end) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + + if (end < 0) + gtk_text_buffer_get_end_iter (buffer, &end_iter); + else + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + + gtk_text_buffer_delete (buffer, &start_iter, &end_iter); +} + +static gchar* +get_chars (GtkTextBuffer *buffer, gint start, gint end) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + + if (end < 0) + gtk_text_buffer_get_end_iter (buffer, &end_iter); + else + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + + return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE); +} + +void +gtk_source_undo_manager_undo (GtkSourceUndoManager *um) +{ + GtkSourceUndoAction *undo_action; + gboolean modified = FALSE; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + g_return_if_fail (um->priv->can_undo); + + um->priv->modified_undoing_group = FALSE; + + gtk_source_undo_manager_begin_not_undoable_action (um); + + do + { + undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1); + g_return_if_fail (undo_action != NULL); + + /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */ + g_return_if_fail ((undo_action->order_in_group <= 1) || + ((undo_action->order_in_group > 1) && !undo_action->modified)); + + if (undo_action->order_in_group <= 1) + { + /* Set modified to TRUE only if the buffer did not change its state from + * "not modified" to "modified" undoing an action (with order_in_group > 1) + * in current group. */ + modified = (undo_action->modified && !um->priv->modified_undoing_group); + } + + switch (undo_action->action_type) + { + case GTK_SOURCE_UNDO_ACTION_DELETE: + insert_text ( + um->priv->document, + undo_action->action.delete.start, + undo_action->action.delete.text, + strlen (undo_action->action.delete.text)); + + if (undo_action->action.delete.forward) + set_cursor ( + um->priv->document, + undo_action->action.delete.start); + else + set_cursor ( + um->priv->document, + undo_action->action.delete.end); + + break; + + case GTK_SOURCE_UNDO_ACTION_INSERT: + delete_text ( + um->priv->document, + undo_action->action.insert.pos, + undo_action->action.insert.pos + + undo_action->action.insert.chars); + + set_cursor ( + um->priv->document, + undo_action->action.insert.pos); + break; + + default: + /* Unknown action type. */ + g_return_if_reached (); + } + + ++um->priv->next_redo; + + } while (undo_action->order_in_group > 1); + + if (modified) + { + --um->priv->next_redo; + gtk_text_buffer_set_modified (um->priv->document, FALSE); + ++um->priv->next_redo; + } + + gtk_source_undo_manager_end_not_undoable_action_internal (um); + + um->priv->modified_undoing_group = FALSE; + + if (!um->priv->can_redo) + { + um->priv->can_redo = TRUE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_REDO], + 0, + TRUE); + } + + if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1)) + { + um->priv->can_undo = FALSE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_UNDO], + 0, + FALSE); + } +} + +void +gtk_source_undo_manager_redo (GtkSourceUndoManager *um) +{ + GtkSourceUndoAction *undo_action; + gboolean modified = FALSE; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + g_return_if_fail (um->priv->can_redo); + + undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); + g_return_if_fail (undo_action != NULL); + + gtk_source_undo_manager_begin_not_undoable_action (um); + + do + { + if (undo_action->modified) + { + g_return_if_fail (undo_action->order_in_group <= 1); + modified = TRUE; + } + + --um->priv->next_redo; + + switch (undo_action->action_type) + { + case GTK_SOURCE_UNDO_ACTION_DELETE: + delete_text ( + um->priv->document, + undo_action->action.delete.start, + undo_action->action.delete.end); + + set_cursor ( + um->priv->document, + undo_action->action.delete.start); + + break; + + case GTK_SOURCE_UNDO_ACTION_INSERT: + set_cursor ( + um->priv->document, + undo_action->action.insert.pos); + + insert_text ( + um->priv->document, + undo_action->action.insert.pos, + undo_action->action.insert.text, + undo_action->action.insert.length); + + break; + + default: + /* Unknown action type */ + ++um->priv->next_redo; + g_return_if_reached (); + } + + if (um->priv->next_redo < 0) + undo_action = NULL; + else + undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); + + } while ((undo_action != NULL) && (undo_action->order_in_group > 1)); + + if (modified) + { + ++um->priv->next_redo; + gtk_text_buffer_set_modified (um->priv->document, FALSE); + --um->priv->next_redo; + } + + gtk_source_undo_manager_end_not_undoable_action_internal (um); + + if (um->priv->next_redo < 0) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); + } + + if (!um->priv->can_undo) + { + um->priv->can_undo = TRUE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE); + } +} + +static void +gtk_source_undo_action_free (GtkSourceUndoAction *action) +{ + if (action == NULL) + return; + + if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) + g_free (action->action.insert.text); + else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) + g_free (action->action.delete.text); + else + g_return_if_reached (); + + g_free (action); +} + +static void +gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um) +{ + GList *l; + + l = um->priv->actions; + + while (l != NULL) + { + GtkSourceUndoAction *action = l->data; + + if (action->order_in_group == 1) + --um->priv->num_of_groups; + + if (action->modified) + um->priv->modified_action = INVALID; + + gtk_source_undo_action_free (action); + + l = g_list_next (l); + } + + g_list_free (um->priv->actions); + um->priv->actions = NULL; +} + +static void +gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, + GtkTextIter *pos, + const gchar *text, + gint length, + GtkSourceUndoManager *um) +{ + GtkSourceUndoAction undo_action; + + if (um->priv->running_not_undoable_actions > 0) + return; + + g_return_if_fail (strlen (text) >= (guint)length); + + undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT; + + undo_action.action.insert.pos = gtk_text_iter_get_offset (pos); + undo_action.action.insert.text = (gchar*) text; + undo_action.action.insert.length = length; + undo_action.action.insert.chars = g_utf8_strlen (text, length); + + if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n')) + + undo_action.mergeable = FALSE; + else + undo_action.mergeable = TRUE; + + undo_action.modified = FALSE; + + gtk_source_undo_manager_add_action (um, &undo_action); +} + +static void +gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + GtkSourceUndoManager *um) +{ + GtkSourceUndoAction undo_action; + GtkTextIter insert_iter; + + if (um->priv->running_not_undoable_actions > 0) + return; + + undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE; + + gtk_text_iter_order (start, end); + + undo_action.action.delete.start = gtk_text_iter_get_offset (start); + undo_action.action.delete.end = gtk_text_iter_get_offset (end); + + undo_action.action.delete.text = get_chars ( + buffer, + undo_action.action.delete.start, + undo_action.action.delete.end); + + /* figure out if the user used the Delete or the Backspace key */ + gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, + gtk_text_buffer_get_insert (buffer)); + if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start) + undo_action.action.delete.forward = TRUE; + else + undo_action.action.delete.forward = FALSE; + + if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) || + (g_utf8_get_char (undo_action.action.delete.text ) == '\n')) + undo_action.mergeable = FALSE; + else + undo_action.mergeable = TRUE; + + undo_action.modified = FALSE; + + gtk_source_undo_manager_add_action (um, &undo_action); + + g_free (undo_action.action.delete.text); + +} + +static void +gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + if (um->priv->running_not_undoable_actions > 0) + return; + + um->priv->actions_in_current_group = 0; +} + +static void +gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action) +{ + GtkSourceUndoAction* action; + + if (um->priv->next_redo >= 0) + { + gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1); + } + + um->priv->next_redo = -1; + + if (!gtk_source_undo_manager_merge_action (um, undo_action)) + { + action = g_new (GtkSourceUndoAction, 1); + *action = *undo_action; + + if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) + action->action.insert.text = g_strdup (undo_action->action.insert.text); + else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) + action->action.delete.text = g_strdup (undo_action->action.delete.text); + else + { + g_free (action); + g_return_if_reached (); + } + + ++um->priv->actions_in_current_group; + action->order_in_group = um->priv->actions_in_current_group; + + if (action->order_in_group == 1) + ++um->priv->num_of_groups; + + um->priv->actions = g_list_prepend (um->priv->actions, action); + } + + gtk_source_undo_manager_check_list_size (um); + + if (!um->priv->can_undo) + { + um->priv->can_undo = TRUE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE); + } + + if (um->priv->can_redo) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); + } +} + +static void +gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, + gint n) +{ + gint i; + + if (um->priv->actions == NULL) + return; + + for (i = 0; i < n; i++) + { + GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data; + + if (action->order_in_group == 1) + --um->priv->num_of_groups; + + if (action->modified) + um->priv->modified_action = INVALID; + + gtk_source_undo_action_free (action); + + um->priv->actions = g_list_delete_link (um->priv->actions, + um->priv->actions); + + if (um->priv->actions == NULL) + return; + } +} + +static void +gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um) +{ + gint undo_levels; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + undo_levels = gtk_source_undo_manager_get_max_undo_levels (um); + + if (undo_levels < 1) + return; + + if (um->priv->num_of_groups > undo_levels) + { + GtkSourceUndoAction *undo_action; + GList *last; + + last = g_list_last (um->priv->actions); + undo_action = (GtkSourceUndoAction*) last->data; + + do + { + GList *tmp; + + if (undo_action->order_in_group == 1) + --um->priv->num_of_groups; + + if (undo_action->modified) + um->priv->modified_action = INVALID; + + gtk_source_undo_action_free (undo_action); + + tmp = g_list_previous (last); + um->priv->actions = g_list_delete_link (um->priv->actions, last); + last = tmp; + g_return_if_fail (last != NULL); + + undo_action = (GtkSourceUndoAction*) last->data; + + } while ((undo_action->order_in_group > 1) || + (um->priv->num_of_groups > undo_levels)); + } +} + +/** + * gtk_source_undo_manager_merge_action: + * @um: a #GtkSourceUndoManager. + * @undo_action: a #GtkSourceUndoAction. + * + * This function tries to merge the undo action at the top of + * the stack with a new undo action. 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 gboolean +gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action) +{ + GtkSourceUndoAction *last_action; + + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); + g_return_val_if_fail (um->priv != NULL, FALSE); + + if (um->priv->actions == NULL) + return FALSE; + + last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0); + + if (!last_action->mergeable) + return FALSE; + + if ((!undo_action->mergeable) || + (undo_action->action_type != last_action->action_type)) + { + last_action->mergeable = FALSE; + return FALSE; + } + + if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) + { + if ((last_action->action.delete.forward != undo_action->action.delete.forward) || + ((last_action->action.delete.start != undo_action->action.delete.start) && + (last_action->action.delete.start != undo_action->action.delete.end))) + { + last_action->mergeable = FALSE; + return FALSE; + } + + if (last_action->action.delete.start == undo_action->action.delete.start) + { + gchar *str; + +#define L (last_action->action.delete.end - last_action->action.delete.start - 1) +#define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i))) + + /* Deleted with the delete key */ + if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && + (g_utf8_get_char (undo_action->action.delete.text) != '\t') && + ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') || + (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t'))) + { + last_action->mergeable = FALSE; + return FALSE; + } + + str = g_strdup_printf ("%s%s", last_action->action.delete.text, + undo_action->action.delete.text); + + g_free (last_action->action.delete.text); + last_action->action.delete.end += (undo_action->action.delete.end - + undo_action->action.delete.start); + last_action->action.delete.text = str; + } + else + { + gchar *str; + + /* Deleted with the backspace key */ + if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && + (g_utf8_get_char (undo_action->action.delete.text) != '\t') && + ((g_utf8_get_char (last_action->action.delete.text) == ' ') || + (g_utf8_get_char (last_action->action.delete.text) == '\t'))) + { + last_action->mergeable = FALSE; + return FALSE; + } + + str = g_strdup_printf ("%s%s", undo_action->action.delete.text, + last_action->action.delete.text); + + g_free (last_action->action.delete.text); + last_action->action.delete.start = undo_action->action.delete.start; + last_action->action.delete.text = str; + } + } + else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) + { + gchar* str; + +#define I (last_action->action.insert.chars - 1) + + if ((undo_action->action.insert.pos != + (last_action->action.insert.pos + last_action->action.insert.chars)) || + ((g_utf8_get_char (undo_action->action.insert.text) != ' ') && + (g_utf8_get_char (undo_action->action.insert.text) != '\t') && + ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') || + (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t'))) + ) + { + last_action->mergeable = FALSE; + return FALSE; + } + + str = g_strdup_printf ("%s%s", last_action->action.insert.text, + undo_action->action.insert.text); + + g_free (last_action->action.insert.text); + last_action->action.insert.length += undo_action->action.insert.length; + last_action->action.insert.text = str; + last_action->action.insert.chars += undo_action->action.insert.chars; + + } + else + /* Unknown action inside undo merge encountered */ + g_return_val_if_reached (TRUE); + + return TRUE; +} + +gint +gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um) +{ + g_return_val_if_fail (um != NULL, 0); + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0); + + return um->priv->max_undo_levels; +} + +void +gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um, + gint max_undo_levels) +{ + gint old_levels; + + g_return_if_fail (um != NULL); + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + + old_levels = um->priv->max_undo_levels; + um->priv->max_undo_levels = max_undo_levels; + + if (max_undo_levels < 1) + return; + + if (old_levels > max_undo_levels) + { + /* strip redo actions first */ + while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels)) + { + gtk_source_undo_manager_free_first_n_actions (um, 1); + um->priv->next_redo--; + } + + /* now remove undo actions if necessary */ + gtk_source_undo_manager_check_list_size (um); + + /* emit "can_undo" and/or "can_redo" if appropiate */ + if (um->priv->next_redo < 0 && um->priv->can_redo) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); + } + + if (um->priv->can_undo && + um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1)) + { + um->priv->can_undo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE); + } + } +} + +static void +gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, + GtkSourceUndoManager *um) +{ + GtkSourceUndoAction *action; + GList *list; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + if (um->priv->actions == NULL) + return; + + list = g_list_nth (um->priv->actions, um->priv->next_redo + 1); + + if (list != NULL) + action = (GtkSourceUndoAction*) list->data; + else + action = NULL; + + if (gtk_text_buffer_get_modified (buffer) == FALSE) + { + if (action != NULL) + action->mergeable = FALSE; + + if (um->priv->modified_action != NULL) + { + if (um->priv->modified_action != INVALID) + um->priv->modified_action->modified = FALSE; + + um->priv->modified_action = NULL; + } + + return; + } + + if (action == NULL) + { + g_return_if_fail (um->priv->running_not_undoable_actions > 0); + + return; + } + + /* gtk_text_buffer_get_modified (buffer) == TRUE */ + + g_return_if_fail (um->priv->modified_action == NULL); + + if (action->order_in_group > 1) + um->priv->modified_undoing_group = TRUE; + + while (action->order_in_group > 1) + { + list = g_list_next (list); + g_return_if_fail (list != NULL); + + action = (GtkSourceUndoAction*) list->data; + g_return_if_fail (action != NULL); + } + + action->modified = TRUE; + um->priv->modified_action = action; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceundomanager.h Wed May 30 05:37:04 2007 +0000 @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gtksourceundomanager.h + * This file is part of GtkSourceView + * + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence + * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi + * Copyright (C) 2002, 2003 Paolo Maggi + * + * 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. * * + */ + +#ifndef __GTK_SOURCE_UNDO_MANAGER_H__ +#define __GTK_SOURCE_UNDO_MANAGER_H__ + +#include <gtk/gtktextbuffer.h> + +#define GTK_SOURCE_TYPE_UNDO_MANAGER (gtk_source_undo_manager_get_type ()) +#define GTK_SOURCE_UNDO_MANAGER(obj) (GTK_CHECK_CAST ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManager)) +#define GTK_SOURCE_UNDO_MANAGER_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) +#define GTK_SOURCE_IS_UNDO_MANAGER(obj) (GTK_CHECK_TYPE ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER)) +#define GTK_SOURCE_IS_UNDO_MANAGER_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER)) +#define GTK_SOURCE_UNDO_MANAGER_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) + + +typedef struct _GtkSourceUndoManager GtkSourceUndoManager; +typedef struct _GtkSourceUndoManagerClass GtkSourceUndoManagerClass; + +typedef struct _GtkSourceUndoManagerPrivate GtkSourceUndoManagerPrivate; + +struct _GtkSourceUndoManager +{ + GObject base; + + GtkSourceUndoManagerPrivate *priv; +}; + +struct _GtkSourceUndoManagerClass +{ + GObjectClass parent_class; + + /* Signals */ + void (*can_undo) (GtkSourceUndoManager *um, gboolean can_undo); + void (*can_redo) (GtkSourceUndoManager *um, gboolean can_redo); +}; + +GType gtk_source_undo_manager_get_type (void) G_GNUC_CONST; + +GtkSourceUndoManager* gtk_source_undo_manager_new (GtkTextBuffer *buffer); + +gboolean gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um); +gboolean gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um); + +void gtk_source_undo_manager_undo (GtkSourceUndoManager *um); +void gtk_source_undo_manager_redo (GtkSourceUndoManager *um); + +void gtk_source_undo_manager_begin_not_undoable_action + (GtkSourceUndoManager *um); +void gtk_source_undo_manager_end_not_undoable_action + (GtkSourceUndoManager *um); + +gint gtk_source_undo_manager_get_max_undo_levels + (GtkSourceUndoManager *um); +void gtk_source_undo_manager_set_max_undo_levels + (GtkSourceUndoManager *um, + gint undo_levels); + +#endif /* __GTK_SOURCE_UNDO_MANAGER_H__ */ + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceview-marshal.c Wed May 30 05:37:04 2007 +0000 @@ -0,0 +1,95 @@ +#include "gtksourceview-marshal.h" + +#include <glib-object.h> + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* VOID:VOID (gtksourceview-marshal.list:1) */ + +/* VOID:BOOLEAN (gtksourceview-marshal.list:2) */ + +/* VOID:BOXED (gtksourceview-marshal.list:3) */ + +/* VOID:BOXED,BOXED (gtksourceview-marshal.list:4) */ +void +gtksourceview_marshal_VOID__BOXED_BOXED (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__BOXED_BOXED) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_VOID__BOXED_BOXED callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_boxed (param_values + 1), + g_marshal_value_peek_boxed (param_values + 2), + data2); +} + +/* VOID:STRING (gtksourceview-marshal.list:5) */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceview-marshal.h Wed May 30 05:37:04 2007 +0000 @@ -0,0 +1,32 @@ + +#ifndef __gtksourceview_marshal_MARSHAL_H__ +#define __gtksourceview_marshal_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* VOID:VOID (gtksourceview-marshal.list:1) */ +#define gtksourceview_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID + +/* VOID:BOOLEAN (gtksourceview-marshal.list:2) */ +#define gtksourceview_marshal_VOID__BOOLEAN g_cclosure_marshal_VOID__BOOLEAN + +/* VOID:BOXED (gtksourceview-marshal.list:3) */ +#define gtksourceview_marshal_VOID__BOXED g_cclosure_marshal_VOID__BOXED + +/* VOID:BOXED,BOXED (gtksourceview-marshal.list:4) */ +extern void gtksourceview_marshal_VOID__BOXED_BOXED (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:STRING (gtksourceview-marshal.list:5) */ +#define gtksourceview_marshal_VOID__STRING g_cclosure_marshal_VOID__STRING + +G_END_DECLS + +#endif /* __gtksourceview_marshal_MARSHAL_H__ */ +
--- a/pidgin/gtkutils.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkutils.c Wed May 30 05:37:04 2007 +0000 @@ -127,6 +127,22 @@ } GtkWidget * +pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) +{ + GtkWindow *wnd = NULL; + + wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + if (title) + gtk_window_set_title(wnd, title); + gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width); + if (role) + gtk_window_set_role(wnd, role); + gtk_window_set_resizable(wnd, resizable); + + return GTK_WIDGET(wnd); +} + +GtkWidget * pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) { GtkWidget *frame;
--- a/pidgin/gtkutils.h Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkutils.h Wed May 30 05:37:04 2007 +0000 @@ -93,6 +93,18 @@ GtkWidget *pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret); /** + * Creates a new window + * + * @param title The window title, or @c NULL + * @param border_width The window's desired border width + * @param role A string indicating what the window is responsible for doing, or @c NULL + * @param resizable Whether the window should be resizable (@c TRUE) or not (@c FALSE) + * + * @since 2.1.0 + */ +GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable); + +/** * Toggles the sensitivity of a widget. * * @param widget @c NULL. Used for signal handlers.
--- a/pidgin/gtkwhiteboard.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/gtkwhiteboard.c Wed May 30 05:37:04 2007 +0000 @@ -28,6 +28,7 @@ #include "debug.h" #include "gtkwhiteboard.h" +#include "gtkutils.h" /****************************************************************************** * Prototypes @@ -143,21 +144,14 @@ gtkwb->brush_color = 0xff0000; } - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtkwb->window = window; - gtk_widget_set_name(window, wb->who); - /* Try and set window title as the name of the buddy, else just use their * username */ buddy = purple_find_buddy(wb->account, wb->who); - if (buddy != NULL) - gtk_window_set_title((GtkWindow*)(window), purple_buddy_get_contact_alias(buddy)); - else - gtk_window_set_title((GtkWindow*)(window), wb->who); - - gtk_window_set_resizable((GtkWindow*)(window), FALSE); + window = pidgin_create_window(buddy != NULL ? purple_buddy_get_contact_alias(buddy) : wb->who, 0, NULL, FALSE); + gtkwb->window = window; + gtk_widget_set_name(window, wb->who); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(whiteboard_close_cb), gtkwb);
--- a/pidgin/plugins/gevolution/add_buddy_dialog.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/plugins/gevolution/add_buddy_dialog.c Wed May 30 05:37:04 2007 +0000 @@ -442,10 +442,7 @@ if (username != NULL) dialog->username = g_strdup(username); - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(dialog->win), "add_buddy"); - gtk_window_set_title(GTK_WINDOW(dialog->win), _("Add Buddy")); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12); + dialog->win = pidgin_create_window(_("Add Buddy"), PIDGIN_HIG_BORDER, "add_buddy", TRUE); gtk_widget_set_size_request(dialog->win, -1, 400); g_signal_connect(G_OBJECT(dialog->win), "delete_event",
--- a/pidgin/plugins/gevolution/assoc-buddy.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/plugins/gevolution/assoc-buddy.c Wed May 30 05:37:04 2007 +0000 @@ -329,9 +329,7 @@ dialog->buddy = buddy; - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(dialog->win), "assoc_buddy"); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12); + dialog->win = pidgin_create_window(NULL, PIDGIN_HIG_BORDER, "assoc_buddy", TRUE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/plugins/gevolution/new_person_dialog.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/plugins/gevolution/new_person_dialog.c Wed May 30 05:37:04 2007 +0000 @@ -246,11 +246,7 @@ dialog->book = book; g_object_ref(book); - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(dialog->win), "new_person"); - gtk_window_set_title(GTK_WINDOW(dialog->win), _("New Person")); - gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12); + dialog->win = pidgin_create_window(_("New Person"), PIDGIN_HIG_BORDER, "new_person", FALSE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/plugins/ticker/ticker.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/plugins/ticker/ticker.c Wed May 30 05:37:04 2007 +0000 @@ -36,6 +36,7 @@ #include "gtkblist.h" #include "gtkplugin.h" +#include "gtkutils.h" #include "gtkticker.h" @@ -70,12 +71,10 @@ return; } - tickerwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); + tickerwindow = pidgin_create_window(_("Buddy Ticker"), 0, "ticker", TRUE); gtk_window_set_default_size(GTK_WINDOW(tickerwindow), 500, -1); g_signal_connect(G_OBJECT(tickerwindow), "delete_event", G_CALLBACK (buddy_ticker_destroy_window), NULL); - gtk_window_set_title (GTK_WINDOW(tickerwindow), _("Buddy Ticker")); - gtk_window_set_role (GTK_WINDOW(tickerwindow), "ticker"); ticker = gtk_ticker_new(); gtk_ticker_set_spacing(GTK_TICKER(ticker), 20);
--- a/pidgin/plugins/xmppconsole.c Wed May 30 05:35:59 2007 +0000 +++ b/pidgin/plugins/xmppconsole.c Wed May 30 05:37:04 2007 +0000 @@ -29,6 +29,7 @@ #if !GTK_CHECK_VERSION(2,4,0) #include "pidgincombobox.h" #endif +#include "gtkutils.h" typedef struct { PurpleConnection *gc; @@ -741,10 +742,8 @@ console = g_new0(XmppConsole, 1); - console->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(console->window), _("XMPP Console")); + console->window = pidgin_create_window(_("XMPP Console"), PIDGIN_HIG_BORDER, NULL, TRUE); g_signal_connect(G_OBJECT(console->window), "destroy", G_CALLBACK(console_destroy), NULL); - gtk_container_set_border_width(GTK_CONTAINER(console->window), 12); gtk_window_set_default_size(GTK_WINDOW(console->window), 580, 400); gtk_container_add(GTK_CONTAINER(console->window), vbox);