changeset 18032:fbebe15c91a7

propagate from branch 'im.pidgin.pidgin' (head bdd3cb040100fe925456b862d03cc6c5a61648ef) to branch 'im.pidgin.pidgin.2.1.0' (head d848f911176cfb2100a9f60b1d6bacdfe86e956e)
author Luke Schierer <lschiere@pidgin.im>
date Sat, 02 Jun 2007 13:09:49 +0000
parents 79ecaab86f91 (current diff) 62a71bb085ef (diff)
children c3eb94e69ae7
files ChangeLog libpurple/dbus-server.c pidgin/gtkaccount.c pidgin/gtkblist.c pidgin/gtkconv.c pidgin/gtkmain.c pidgin/gtkprefs.c pidgin/gtkutils.c pidgin/pixmaps/emotes/default/22/theme pidgin/pixmaps/emotes/none/theme
diffstat 55 files changed, 1806 insertions(+), 154 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Jun 02 06:26:24 2007 +0000
+++ b/ChangeLog	Sat Jun 02 13:09:49 2007 +0000
@@ -1,5 +1,21 @@
 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)
+	* No longer linkifies screennames containing @ signs in join/part
+	  notifications in chats
+
+	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 (??/??/????):
 	Pidgin:
 	* Added a custom conversation font option to preferences
--- a/ChangeLog.API	Sat Jun 02 06:26:24 2007 +0000
+++ b/ChangeLog.API	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/configure.ac	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/finch/finch.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/account.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/blist.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/connection.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/conversation.c	Sat Jun 02 13:09:49 2007 +0000
@@ -108,8 +108,12 @@
 
 	type = purple_conversation_get_type(conv);
 
-	/* Always linkfy the text for display */
-	displayed = purple_markup_linkify(message);
+	/* Always linkfy the text for display, unless we're
+	 * explicitly asked to do otheriwse*/
+	if(msgflags & PURPLE_MESSAGE_NO_LINKIFY)
+		displayed = g_strdup(message);
+	else
+		displayed = purple_markup_linkify(message);
 
 	if ((conv->features & PURPLE_CONNECTION_HTML) &&
 		!(msgflags & PURPLE_MESSAGE_RAW))
@@ -1010,7 +1014,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
@@ -1576,7 +1580,9 @@
 			}
 			g_free(escaped);
 
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			purple_conversation_write(conv, NULL, tmp,
+					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+					time(NULL));
 			g_free(tmp);
 		}
 
@@ -1700,7 +1706,9 @@
 			g_free(escaped2);
 		}
 
-		purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+		purple_conversation_write(conv, NULL, tmp,
+				PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+				time(NULL));
 	}
 }
 
@@ -1777,7 +1785,9 @@
 			}
 			g_free(escaped);
 
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			purple_conversation_write(conv, NULL, tmp,
+					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+					time(NULL));
 			g_free(tmp);
 		}
 
--- a/libpurple/conversation.h	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/conversation.h	Sat Jun 02 13:09:49 2007 +0000
@@ -116,7 +116,9 @@
 	PURPLE_MESSAGE_RAW         = 0x0800, /**< "Raw" message - don't
 	                                        apply formatting         */
 	PURPLE_MESSAGE_IMAGES      = 0x1000, /**< Message contains images  */
-	PURPLE_MESSAGE_NOTIFY      = 0x2000  /**< Message is a notification */
+	PURPLE_MESSAGE_NOTIFY      = 0x2000, /**< Message is a notification */
+	PURPLE_MESSAGE_NO_LINKIFY  = 0x4000  /**< Message should not be auto-
+										   linkified */
 
 } PurpleMessageFlags;
 
--- a/libpurple/core.c	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/core.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/core.h	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/dbus-server.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/dbus-server.h	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/eventloop.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/eventloop.h	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/example/nullclient.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/idle.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/pounce.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/prefs.c	Sat Jun 02 13:09:49 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/protocols/irc/irc.c	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Sat Jun 02 13:09:49 2007 +0000
@@ -814,7 +814,8 @@
 
 static PurplePluginProtocolInfo prpl_info =
 {
-	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL,
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 	NULL,					/* user_splits */
 	NULL,					/* protocol_options */
 	NO_BUDDY_ICONS,		/* icon_spec */
--- a/libpurple/protocols/jabber/libxmpp.c	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sat Jun 02 13:09:49 2007 +0000
@@ -43,9 +43,11 @@
 {
 #ifdef HAVE_CYRUS_SASL
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
-	OPT_PROTO_MAIL_CHECK | OPT_PROTO_PASSWORD_OPTIONAL,
+	OPT_PROTO_MAIL_CHECK | OPT_PROTO_PASSWORD_OPTIONAL |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 #else
-	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK,
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 #endif
 	NULL,							/* user_splits */
 	NULL,							/* protocol_options */
--- a/libpurple/protocols/silc/silc.c	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/protocols/silc/silc.c	Sat Jun 02 13:09:49 2007 +0000
@@ -1728,10 +1728,11 @@
 {
 #ifdef HAVE_SILCMIME_H
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
-	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE,
+	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 #else
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
-	OPT_PROTO_PASSWORD_OPTIONAL,
+	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_SLASH_COMMANDS_NATIVE,
 #endif
 	NULL,						/* user_splits */
 	NULL,						/* protocol_options */
--- a/libpurple/prpl.h	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/prpl.h	Sat Jun 02 13:09:49 2007 +0000
@@ -158,6 +158,12 @@
 	 */
 	OPT_PROTO_REGISTER_NOSCREENNAME = 0x00000200,
 
+	/**
+	 * Indicates that slash commands are native to this protocol.
+	 * Used as a hint that unknown commands should not be sent as messages.
+	 */
+	OPT_PROTO_SLASH_COMMANDS_NATIVE = 0x00000400,
+
 } PurpleProtocolOptions;
 
 /**
--- a/libpurple/savedstatuses.c	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/savedstatuses.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/server.c	Sat Jun 02 13:09:49 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;
 
@@ -233,8 +233,9 @@
 			char *tmp = g_strdup_printf(_("%s is now known as %s.\n"),
 										who, alias);
 
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM,
-									time(NULL));
+			purple_conversation_write(conv, NULL, tmp,
+					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+					time(NULL));
 
 			g_free(tmp);
 		}
--- a/libpurple/util.c	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/util.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/libpurple/util.h	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/Makefile.am	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkaccount.c	Sat Jun 02 13:09:49 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);
@@ -2319,7 +2310,6 @@
 	GtkWidget *button;
 	int width, height;
 
-
 	if (accounts_window != NULL) {
 		gtk_window_present(GTK_WINDOW(accounts_window->window));
 		return;
@@ -2330,11 +2320,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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkblist.c	Sat Jun 02 13:09:49 2007 +0000
@@ -4208,7 +4208,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;
 	}
 
@@ -4217,9 +4217,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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkconv.c	Sat Jun 02 13:09:49 2007 +0000
@@ -477,6 +477,7 @@
 	char *cmd;
 	const char *prefix;
 	GtkTextIter start;
+	gboolean retval = FALSE;
 
 	gtkconv = PIDGIN_CONVERSATION(conv);
 	prefix = pidgin_get_cmd_prefix();
@@ -500,24 +501,50 @@
 		gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &end);
 		markup = gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv->entry), &start, &end);
 		status = purple_cmd_do_command(conv, cmdline, markup, &error);
-		g_free(cmd);
 		g_free(markup);
 
 		switch (status) {
 			case PURPLE_CMD_STATUS_OK:
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_NOT_FOUND:
-				return FALSE;
+				{
+					PurplePluginProtocolInfo *prpl_info = NULL;
+					PurpleConnection *gc;
+
+					if ((gc = purple_conversation_get_gc(conv)))
+						prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+					if ((prpl_info != NULL) && (prpl_info->options & OPT_PROTO_SLASH_COMMANDS_NATIVE)) {
+						char *firstspace;
+						char *slash;
+
+						firstspace = strchr(cmdline, ' ');
+						if (firstspace != NULL) {
+							slash = strrchr(firstspace, '/');
+						} else {
+							slash = strchr(cmdline, '/');
+						}
+
+						if (slash == NULL) {
+							purple_conversation_write(conv, "", _("Unknown command."), PURPLE_MESSAGE_NO_LOG, time(NULL));
+							retval = TRUE;
+						}
+					}
+					break;
+				}
 			case PURPLE_CMD_STATUS_WRONG_ARGS:
 				purple_conversation_write(conv, "", _("Syntax Error:  You typed the wrong number of arguments "
 								    "to that command."),
 						PURPLE_MESSAGE_NO_LOG, time(NULL));
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_FAILED:
 				purple_conversation_write(conv, "", error ? error : _("Your command failed for an unknown reason."),
 						PURPLE_MESSAGE_NO_LOG, time(NULL));
 				g_free(error);
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_WRONG_TYPE:
 				if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 					purple_conversation_write(conv, "", _("That command only works in chats, not IMs."),
@@ -525,16 +552,18 @@
 				else
 					purple_conversation_write(conv, "", _("That command only works in IMs, not chats."),
 							PURPLE_MESSAGE_NO_LOG, time(NULL));
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_WRONG_PRPL:
 				purple_conversation_write(conv, "", _("That command doesn't work on this protocol."),
 						PURPLE_MESSAGE_NO_LOG, time(NULL));
-				return TRUE;
+				retval = TRUE;
+				break;
 		}
 	}
 
 	g_free(cmd);
-	return FALSE;
+	return retval;
 }
 
 static void
@@ -5066,7 +5095,11 @@
 	g_return_if_fail(gc != NULL);
 
 	/* Make sure URLs are clickable */
-	displaying = purple_markup_linkify(message);
+	if(flags & PURPLE_MESSAGE_NO_LINKIFY)
+		displaying = g_strdup(message);
+	else
+		displaying = purple_markup_linkify(message);
+
 	plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
 							pidgin_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ?
 							"displaying-im-msg" : "displaying-chat-msg"),
@@ -8056,10 +8089,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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkeventloop.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkft.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkimhtml.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkimhtml.h	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkmain.c	Sat Jun 02 13:09:49 2007 +0000
@@ -733,6 +733,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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtknotify.c	Sat Jun 02 13:09:49 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")));
+	window = 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkpounce.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkprefs.c	Sat Jun 02 13:09:49 2007 +0000
@@ -1987,11 +1987,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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkprivacy.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkrequest.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkroomlist.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtksavedstatuses.c	Sat Jun 02 13:09:49 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	Sat Jun 02 13:09:49 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	Sat Jun 02 13:09:49 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	Sat Jun 02 13:09:49 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	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkutils.c	Sat Jun 02 13:09:49 2007 +0000
@@ -130,6 +130,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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkutils.h	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/gtkwhiteboard.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/plugins/gevolution/add_buddy_dialog.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/plugins/gevolution/assoc-buddy.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/plugins/gevolution/new_person_dialog.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/plugins/ticker/ticker.c	Sat Jun 02 13:09:49 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	Sat Jun 02 06:26:24 2007 +0000
+++ b/pidgin/plugins/xmppconsole.c	Sat Jun 02 13:09:49 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);