changeset 25526:162a767a20d4

propagate from branch 'im.pidgin.pidgin' (head 414f0770611139752d484f4831cb59038456c9bf) to branch 'im.pidgin.pidgin.yaz' (head 14b9a68d9ec80c997ca0986d8626805b8e347174)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sun, 01 Jul 2007 08:41:01 +0000
parents 8d561be489f5 (current diff) 238086459dc4 (diff)
children 0f53f60b2018
files libpurple/protocols/gg/lib/pubdir50.c libpurple/util.c libpurple/util.h pidgin/gtkconv.c pidgin/gtkimhtml.c pidgin/gtkprefs.c pidgin/gtkutils.c pidgin/plugins/spellchk.c
diffstat 54 files changed, 1016 insertions(+), 296 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Fri Jun 29 13:52:07 2007 +0000
+++ b/COPYRIGHT	Sun Jul 01 08:41:01 2007 +0000
@@ -324,6 +324,7 @@
 Joe Shaw
 Scott Shedden
 Dossy Shiobara
+Michael Shkutkov
 Ettore Simone
 John Silvestri
 Craig Slusher
--- a/ChangeLog	Fri Jun 29 13:52:07 2007 +0000
+++ b/ChangeLog	Sun Jul 01 08:41:01 2007 +0000
@@ -11,6 +11,7 @@
 	  notifications in chats
 	* With the HTML logger, images in conversations are now saved.
 	  NOTE: Saved images are not yet displayed when loading logs.
+	* Added support for QIP logs to the Log Reader plugin (Michael Shkutkov)
 
 	Pidgin:
 	* Ensure only one copy of Pidgin is running with a given configuration
@@ -18,6 +19,13 @@
 	  second time will raise the buddy list.  (Gabriel Schulhof)
 	* Undo capability in the conversation window
 
+	Finch:
+	* There's support for workspaces now (details in the manpage)
+	* There's a new custom window manager, Irssi
+	* Some improvements for tab-completion, tooltip and the password entries
+	* Some bugs regarding search results fixed
+	* A new DBus-script to create a docklet for finch
+
 version 2.0.2 (06/14/2007):
 	Pidgin:
 	* Added a custom conversation font option to preferences
--- a/ChangeLog.API	Fri Jun 29 13:52:07 2007 +0000
+++ b/ChangeLog.API	Sun Jul 01 08:41:01 2007 +0000
@@ -73,6 +73,7 @@
 		* pidgin_menu_position_func_helper
 		* pidgin_blist_get_name_markup, returns the buddy list markup
 		  text for a given buddy.
+		* pidgin_themes_remove_smiley_theme
 
 		Changed:
 		* pidgin_append_menu_action returns the menuitem added to the menu.
@@ -88,10 +89,24 @@
 		Added:
 		* finch_retrieve_user_info
 
-		Changed:
-		* gnt_tree_get_rows() now returns a GList* instead of a const
-		  GList*, as const is not very useful with GLists.  The
-		  returned value still must not be modified or freed.
+		libgnt:
+			Added:
+			* GntWS for workspaces
+			* gnt_tree_set_column_title
+			* GntSlider widget
+			* "completion" signal for GntEntry
+			* "terminal-refresh" signal for GntWM, with a corresponding entry
+			  in GntWMClass
+			* New flags for GntTextView to decide whether to word-wrap or show
+			  scrollbars (GntTextViewFlag) which can be set by
+			  gnt_text_view_set_flag
+			* gnt_style_get_from_name
+			* gnt_window_present
+
+			Changed:
+			* gnt_tree_get_rows() now returns a GList* instead of a const
+			  GList*, as const is not very useful with GLists.  The
+			  returned value still must not be modified or freed.
 
 version 2.0.2 (6/14/2007):
 	Pidgin:
--- a/doc/finch.1.in	Fri Jun 29 13:52:07 2007 +0000
+++ b/doc/finch.1.in	Sun Jul 01 08:41:01 2007 +0000
@@ -110,14 +110,26 @@
 Bring up the menu (if there is one) for a window. Note that currently only the
 buddylist has a menu.
 .TP
-.B Alt \+ Shift \+ .
+.B Alt \+ /
+Show a list of available key-bindings for the current widget in focus.
+.TP
+.B Alt \+ \>
 Switch to the next workspace
 .TP
-.B Alt \+ Shift \+ ,
+.B Alt \+ \<
 Switch to the previous workspace
 .TP
+.B Alt \+ t
+Tag (or untag) the current window
+.TP
+.B Alt \+ T
+Attached all the tag windows to the current workspace
+.TP
 .B Alt \+ s
 Show the workspace list
+.TP
+.B F9
+Create a new workspace and switch to it
 
 .SH FILES
 \fI~/.gntrc\fR: configuration file for gnt applications.
@@ -135,7 +147,9 @@
 .br
 # To use some custom window-manager
 .br
-wm = /usr/local/lib/purple/s.so
+wm = /usr/local/lib/gnt/s.so
+.br
+# There's also a custom window manager called irssi.so
 .br
 # Remember window-positions based on the titles (on by default)
 .br
@@ -308,6 +322,8 @@
 .br
 pagedown = page-down
 .br
+backspace = move-parent
+.br
 # Following is the default binding for the context-menu
 .br
 menu = context-menu
@@ -377,6 +393,16 @@
 .br
 a-l = refresh-screen
 .br
+a-s = workspace-list
+.br
+a-t = window-tag
+.br
+a-T = place-tagged
+.br
+a-C = toggle-clipboard
+.br
+a-/ = help-for-widget
+.br
 # The following action is still incomplete, and doesn't have a default binding
 .br
 # switch-window-n
@@ -387,8 +413,18 @@
 [GntS::binding]
 .br
 a-b = toggle-buddylist
+
+# For the irssi window manager
 .br
-a-C = toggle-clipboard
+[Irssi::binding]
+.br
+a-L = move-right
+.br
+a-H = move-left
+.br
+a-J = move-down
+.br
+a-K = move-up
 
 .SH Conversation Commands
 There are a few helpful commands in addition to the regular commands. You can
--- a/doc/notify-signals.dox	Fri Jun 29 13:52:07 2007 +0000
+++ b/doc/notify-signals.dox	Sun Jul 01 08:41:01 2007 +0000
@@ -1,4 +1,4 @@
-/** @page conversation-signals Notification Signals
+/** @page notify-signals Notification Signals
 
  @signals
   @signal displaying-userinfo
--- a/finch/finch.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/finch.c	Sun Jul 01 08:41:01 2007 +0000
@@ -381,6 +381,7 @@
 {
 	signal(SIGPIPE, SIG_IGN);
 
+	g_set_prgname("Finch");
 	g_set_application_name(_("Finch"));
 
 	/* Initialize the libpurple stuff */
--- a/finch/gntconv.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/gntconv.c	Sun Jul 01 08:41:01 2007 +0000
@@ -140,13 +140,6 @@
 					break;
 			}
 			g_free(error);
-#if 0
-			gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv),
-					_("Commands are not supported yet. Message was NOT sent."),
-					GNT_TEXT_FLAG_DIM | GNT_TEXT_FLAG_UNDERLINE);
-			gnt_text_view_next_line(GNT_TEXT_VIEW(ggconv->tv));
-			gnt_text_view_scroll(GNT_TEXT_VIEW(ggconv->tv), 0);
-#endif
 		}
 		else
 		{
@@ -451,6 +444,13 @@
 }
 
 static void
+completion_cb(GntEntry *entry, const char *start, const char *end)
+{
+	if (start == entry->start)
+		gnt_widget_key_pressed(GNT_WIDGET(entry), ": ");
+}
+
+static void
 finch_create_conversation(PurpleConversation *conv)
 {
 	FinchConv *ggc = conv->ui_data;
@@ -542,6 +542,7 @@
 
 	gnt_text_view_attach_scroll_widget(GNT_TEXT_VIEW(ggc->tv), ggc->entry);
 	g_signal_connect_after(G_OBJECT(ggc->entry), "key_pressed", G_CALLBACK(entry_key_pressed), ggc);
+	g_signal_connect(G_OBJECT(ggc->entry), "completion", G_CALLBACK(completion_cb), NULL);
 	g_signal_connect(G_OBJECT(ggc->window), "destroy", G_CALLBACK(closing_window), ggc);
 
 	gnt_widget_set_position(ggc->window, purple_prefs_get_int(PREF_ROOT "/position/x"),
--- a/finch/libgnt/gntentry.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/libgnt/gntentry.c	Sun Jul 01 08:41:01 2007 +0000
@@ -25,6 +25,7 @@
 
 #include "gntbox.h"
 #include "gntentry.h"
+#include "gntmarshal.h"
 #include "gntstyle.h"
 #include "gnttree.h"
 #include "gntutils.h"
@@ -32,6 +33,7 @@
 enum
 {
 	SIG_TEXT_CHANGED,
+	SIG_COMPLETION,
 	SIGS,
 };
 static guint signals[SIGS] = { 0 };
@@ -69,9 +71,12 @@
 complete_suggest(GntEntry *entry, const char *text)
 {
 	gboolean changed = FALSE;
+	int offstart = 0, offend = 0;
+
 	if (entry->word) {
 		char *s = get_beginning_of_word(entry);
 		const char *iter = text;
+		offstart = g_utf8_pointer_to_offset(entry->start, s);
 		while (*iter && toupper(*s) == toupper(*iter)) {
 			if (*s != *iter)
 				changed = TRUE;
@@ -81,10 +86,17 @@
 			gnt_entry_key_pressed(GNT_WIDGET(entry), iter);
 			changed = TRUE;
 		}
+		offend = g_utf8_pointer_to_offset(entry->start, entry->cursor);
 	} else {
+		offstart = 0;
 		gnt_entry_set_text_internal(entry, text);
 		changed = TRUE;
+		offend = g_utf8_strlen(text, -1);
 	}
+
+	if (changed)
+		g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0,
+				entry->start + offstart, entry->start + offend);
 	return changed;
 }
 
@@ -687,6 +699,14 @@
 					 g_cclosure_marshal_VOID__VOID,
 					 G_TYPE_NONE, 0);
 
+	signals[SIG_COMPLETION] =
+		g_signal_new("completion",
+					 G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST,
+					 0, NULL, NULL,
+					 gnt_closure_marshal_VOID__POINTER_POINTER,
+					 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
+
 	gnt_bindable_class_register_action(bindable, "cursor-home", move_start,
 				GNT_KEY_CTRL_A, NULL);
 	gnt_bindable_register_binding(bindable, "cursor-home", GNT_KEY_HOME, NULL);
--- a/finch/libgnt/gntstyle.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Sun Jul 01 08:41:01 2007 +0000
@@ -269,7 +269,8 @@
 {
 	GError *error = NULL;
 	gsize nkeys;
-	char **keys = g_key_file_get_keys(kfile, "general", &nkeys, &error);
+	const char *prgname = g_get_prgname();
+	char **keys = NULL;
 	int i;
 	struct
 	{
@@ -282,6 +283,14 @@
 	              {"remember_position", GNT_STYLE_REMPOS},
 	              {NULL, 0}};
 
+	if (prgname && *prgname)
+		keys = g_key_file_get_keys(kfile, prgname, &nkeys, NULL);
+
+	if (keys == NULL) {
+		prgname = "general";
+		keys = g_key_file_get_keys(kfile, prgname, &nkeys, &error);
+	}
+
 	if (error)
 	{
 		g_printerr("GntStyle: %s\n", error->message);
@@ -292,7 +301,7 @@
 		for (i = 0; styles[i].style; i++)
 		{
 			str_styles[styles[i].en] =
-					g_key_file_get_string(kfile, "general", styles[i].style, NULL);
+					g_key_file_get_string(kfile, prgname, styles[i].style, NULL);
 		}
 	}
 	g_strfreev(keys);
--- a/finch/libgnt/gntwm.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/libgnt/gntwm.c	Sun Jul 01 08:41:01 2007 +0000
@@ -61,6 +61,7 @@
 	SIG_GIVE_FOCUS,
 	SIG_KEY_PRESS,
 	SIG_MOUSE_CLICK,
+	SIG_TERMINAL_REFRESH,
 	SIGS
 };
 
@@ -1031,6 +1032,8 @@
 	endwin();
 
 	g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, NULL);
+	refresh();
+	g_signal_emit(wm, signals[SIG_TERMINAL_REFRESH], 0);
 	update_screen(wm);
 	gnt_ws_draw_taskbar(wm->cws, TRUE);
 	curs_set(0);   /* endwin resets the cursor to normal */
@@ -1238,6 +1241,15 @@
 					 gnt_closure_marshal_BOOLEAN__INT_INT_INT_POINTER,
 					 G_TYPE_BOOLEAN, 4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER);
 
+	signals[SIG_TERMINAL_REFRESH] = 
+		g_signal_new("terminal-refresh",
+					 G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST,
+					 G_STRUCT_OFFSET(GntWMClass, terminal_refresh),
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "window-next", window_next,
 				"\033" "n", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "window-prev", window_prev,
--- a/finch/libgnt/gntwm.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/libgnt/gntwm.h	Sun Jul 01 08:41:01 2007 +0000
@@ -169,10 +169,15 @@
 	 */
 	/*GList *(*window_list)();*/
 
+	/* This is invoked whenever the terminal window is resized, or the
+	 * screen session is attached to a new terminal. (ie, from the
+	 * SIGWINCH callback)
+	 */
+	void (*terminal_refresh)(GntWM *wm);
+
 	void (*res1)(void);
 	void (*res2)(void);
 	void (*res3)(void);
-	void (*res4)(void);
 };
 
 G_BEGIN_DECLS
--- a/finch/libgnt/wms/irssi.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/libgnt/wms/irssi.c	Sun Jul 01 08:41:01 2007 +0000
@@ -248,6 +248,12 @@
 }
 
 static void
+irssi_terminal_refresh(GntWM *wm)
+{
+	draw_line_separators((Irssi*)wm);
+}
+
+static void
 irssi_class_init(IrssiClass *klass)
 {
 	GntWMClass *pclass = GNT_WM_CLASS(klass);
@@ -258,6 +264,7 @@
 	pclass->window_resized = irssi_window_resized;
 	pclass->close_window = irssi_close_window;
 	pclass->window_update = irssi_update_window;
+	pclass->terminal_refresh = irssi_terminal_refresh;
 
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "move-up", move_direction,
 			"\033" "K", GINT_TO_POINTER('k'), NULL);
--- a/finch/plugins/pietray.py	Fri Jun 29 13:52:07 2007 +0000
+++ b/finch/plugins/pietray.py	Sun Jul 01 08:41:01 2007 +0000
@@ -78,9 +78,27 @@
 def toggle_pref(item, pref):
 	purple.PurplePrefsSetBool(pref, item.get_active())
 
+def quit_finch(item, null):
+	# XXX: Ask first
+	purple.PurpleCoreQuit()
+	gtk.main_quit()
+
+def close_docklet(item, null):
+	gtk.main_quit()
+
 def popup_menu(icon, button, tm, none):
 	menu = gtk.Menu()
 
+	#item = gtk.ImageMenuItem(gtk.STOCK_QUIT)
+	#item.connect("activate", quit_finch, None)
+	#menu.append(item)
+
+	item = gtk.ImageMenuItem(gtk.STOCK_CLOSE)
+	item.connect("activate", close_docklet, None)
+	menu.append(item)
+
+	menu.append(gtk.MenuItem())
+
 	item = gtk.CheckMenuItem("Blink for unread IM")
 	item.set_active(purple.PurplePrefsGetBool("/plugins/dbus/docklet/blink/im"))
 	item.connect("activate", toggle_pref, "/plugins/dbus/docklet/blink/im")
@@ -176,8 +194,14 @@
 t = gtk.StatusIcon()
 t.connect("popup-menu", popup_menu, None)
 
-init_prefs()
-detect_unread_conversations()
+try:
+	init_prefs()
+	detect_unread_conversations()
+	gtk.main ()
+except:
+	dialog = gtk.Dialog("pietray: Error", None, gtk.DIALOG_NO_SEPARATOR | gtk.DIALOG_MODAL, ("Close", gtk.RESPONSE_CLOSE))
+	dialog.set_resizable(False)
+	dialog.vbox.pack_start(gtk.Label("There was some error. Perhaps a purple client is not running."), False, False, 0)
+	dialog.show_all()
+	dialog.run()
 
-gtk.main ()
-
--- a/libpurple/blist.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/blist.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,8 @@
  * 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
+ *
+ * @see @ref blist-signals
  */
 #ifndef _PURPLE_BLIST_H_
 #define _PURPLE_BLIST_H_
--- a/libpurple/cipher.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/cipher.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,8 @@
  * 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
+ *
+ * @see @ref cipher-signals
  */
 #ifndef PURPLE_CIPHER_H
 #define PURPLE_CIPHER_H
--- a/libpurple/core.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/core.h	Sun Jul 01 08:41:01 2007 +0000
@@ -20,6 +20,8 @@
  * 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
+ *
+ * @see @ref core-signals
  */
 #ifndef _PURPLE_CORE_H_
 #define _PURPLE_CORE_H_
--- a/libpurple/dbus-server.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/dbus-server.h	Sun Jul 01 08:41:01 2007 +0000
@@ -22,6 +22,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
+ * @see @ref dbus-server-signals
  */
 
 #ifndef _PURPLE_DBUS_SERVER_H_
--- a/libpurple/dnssrv.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/dnssrv.c	Sun Jul 01 08:41:01 2007 +0000
@@ -50,11 +50,11 @@
 	u_char buf[1024];
 } queryans;
 #else
-static DNS_STATUS WINAPI (*MyDnsQuery_UTF8) (
+static DNS_STATUS (WINAPI *MyDnsQuery_UTF8) (
 	PCSTR lpstrName, WORD wType, DWORD fOptions,
 	PIP4_ARRAY aipServers, PDNS_RECORD* ppQueryResultsSet,
 	PVOID* pReserved) = NULL;
-static void WINAPI (*MyDnsRecordListFree) (PDNS_RECORD pRecordList,
+static void (WINAPI *MyDnsRecordListFree) (PDNS_RECORD pRecordList,
 	DNS_FREE_TYPE FreeType) = NULL;
 #endif
 
--- a/libpurple/imgstore.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/imgstore.c	Sun Jul 01 08:41:01 2007 +0000
@@ -55,8 +55,8 @@
 {
 	PurpleStoredImage *img;
 
-	g_return_val_if_fail(data != NULL, 0);
-	g_return_val_if_fail(size > 0, 0);
+	g_return_val_if_fail(data != NULL, NULL);
+	g_return_val_if_fail(size > 0, NULL);
 
 	img = g_new(PurpleStoredImage, 1);
 	PURPLE_DBUS_REGISTER_POINTER(img, PurpleStoredImage);
@@ -73,11 +73,13 @@
 purple_imgstore_add_with_id(gpointer data, size_t size, const char *filename)
 {
 	PurpleStoredImage *img = purple_imgstore_add(data, size, filename);
-	img->id = ++nextid;
+	if (img) {
+		img->id = ++nextid;
 
-	g_hash_table_insert(imgstore, &(img->id), img);
+		g_hash_table_insert(imgstore, &(img->id), img);
+	}
 
-	return img->id;
+	return (img ? img->id : 0);
 }
 
 PurpleStoredImage *purple_imgstore_find_by_id(int id) {
--- a/libpurple/imgstore.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/imgstore.h	Sun Jul 01 08:41:01 2007 +0000
@@ -22,6 +22,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
+ * @see @ref imgstore-signals
  */
 #ifndef _PURPLE_IMGSTORE_H_
 #define _PURPLE_IMGSTORE_H_
--- a/libpurple/internal.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/internal.h	Sun Jul 01 08:41:01 2007 +0000
@@ -111,11 +111,16 @@
 # include <unistd.h>
 #endif
 
-#ifndef MAXPATHLEN
-# ifdef PATH_MAX
-#  define MAXPATHLEN PATH_MAX
-# else
-#  define MAXPATHLEN 1024
+/* MAXPATHLEN should only be used with readlink() on glib < 2.4.0.  For
+ * anything else, use g_file_read_link() or other dynamic functions.  This is
+ * important because Hurd has no hard limits on path length. */
+#if !GLIB_CHECK_VERSION(2,4,0)
+# ifndef MAXPATHLEN
+#  ifdef PATH_MAX
+#   define MAXPATHLEN PATH_MAX
+#  else
+#   define MAXPATHLEN 1024
+#  endif
 # endif
 #endif
 
--- a/libpurple/log.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/log.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,8 @@
  * 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
+ *
+ * @see @ref log-signals
  */
 #ifndef _PURPLE_LOG_H_
 #define _PURPLE_LOG_H_
--- a/libpurple/network.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/network.c	Sun Jul 01 08:41:01 2007 +0000
@@ -517,7 +517,7 @@
 	WSAQUERYSET qs;
 	time_t last_trigger = time(NULL);
 
-	int WSAAPI (*MyWSANSPIoctl) (
+	int (WSAAPI *MyWSANSPIoctl) (
 		HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer,
 		DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,
 		LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL;
--- a/libpurple/notify.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/notify.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,8 @@
  * 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
+ *
+ * @see @ref notify-signals
  */
 #ifndef _PURPLE_NOTIFY_H_
 #define _PURPLE_NOTIFY_H_
--- a/libpurple/plugin.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/plugin.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,10 @@
  * 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
+ *
+ * @see @ref plugin-signals
+ * @see @ref plugin-ids
+ * @see @ref plugin-i18n
  */
 #ifndef _PURPLE_PLUGIN_H_
 #define _PURPLE_PLUGIN_H_
--- a/libpurple/plugins/filectl.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/plugins/filectl.c	Sun Jul 01 08:41:01 2007 +0000
@@ -56,7 +56,7 @@
 		purple_debug_misc("filectl", "read: %s\n", buffer);
 		command = getarg(buffer, 0, 0);
 
-		if (!strncasecmp(command, "login", 6)) {
+		if (!g_ascii_strncasecmp(command, "login", 6)) {
 			PurpleAccount *account;
 
 			arg1 = getarg(buffer, 1, 0);
@@ -69,7 +69,7 @@
 			free(arg1);
 			free(arg2);
 
-		} else if (!strncasecmp(command, "logout", 7)) {
+		} else if (!g_ascii_strncasecmp(command, "logout", 7)) {
 			PurpleAccount *account;
 
 			arg1 = getarg(buffer, 1, 1);
@@ -88,7 +88,7 @@
 
 /* purple_find_conversation() is gone in 2.0.0. */
 #if 0
-		} else if (!strncasecmp(command, "send", 4)) {
+		} else if (!g_ascii_strncasecmp(command, "send", 4)) {
 			PurpleConversation *conv;
 
 			arg1 = getarg(buffer, 1, 0);
@@ -107,21 +107,21 @@
 			free(arg2);
 #endif
 
-		} else if (!strncasecmp(command, "away", 4)) {
+		} else if (!g_ascii_strncasecmp(command, "away", 4)) {
 			arg1 = getarg(buffer, 1, 1);
 			/* serv_set_away_all(arg1); */
 			free(arg1);
 
-		} else if (!strncasecmp(command, "hide", 4)) {
+		} else if (!g_ascii_strncasecmp(command, "hide", 4)) {
 			purple_blist_set_visible(FALSE);
 
-		} else if (!strncasecmp(command, "unhide", 6)) {
+		} else if (!g_ascii_strncasecmp(command, "unhide", 6)) {
 			purple_blist_set_visible(TRUE);
 
-		} else if (!strncasecmp(command, "back", 4)) {
+		} else if (!g_ascii_strncasecmp(command, "back", 4)) {
 			/* do_im_back(); */
 
-		} else if (!strncasecmp(command, "quit", 4)) {
+		} else if (!g_ascii_strncasecmp(command, "quit", 4)) {
 			purple_core_quit();
 
 		}
--- a/libpurple/plugins/log_reader.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/plugins/log_reader.c	Sun Jul 01 08:41:01 2007 +0000
@@ -1,13 +1,5 @@
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
 #include <stdio.h>
 
-#ifndef PURPLE_PLUGINS
-# define PURPLE_PLUGINS
-#endif
-
 #include "internal.h"
 
 #include "debug.h"
@@ -106,8 +98,8 @@
 				if (sscanf(date, "%u|%u|%u",
 						&tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) {
 
-					purple_debug(PURPLE_DEBUG_ERROR, "Adium log parse",
-							"Filename timestamp parsing error\n");
+					purple_debug_error("Adium log parse",
+					                   "Filename timestamp parsing error\n");
 				} else {
 					char *filename = g_build_filename(path, file, NULL);
 					FILE *handle = g_fopen(filename, "rb");
@@ -141,8 +133,8 @@
 					if (sscanf(contents2, "%u.%u.%u",
 							&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
 
-						purple_debug(PURPLE_DEBUG_ERROR, "Adium log parse",
-								"Contents timestamp parsing error\n");
+						purple_debug_error("Adium log parse",
+						                   "Contents timestamp parsing error\n");
 						g_free(contents);
 						g_free(filename);
 						continue;
@@ -161,7 +153,7 @@
 					log->logger = adium_logger;
 					log->logger_data = data;
 
-					list = g_list_append(list, log);
+					list = g_list_prepend(list, log);
 				}
 			} else if (purple_str_has_suffix(file, ".adiumLog")) {
 				struct tm tm;
@@ -171,8 +163,8 @@
 				if (sscanf(date, "%u|%u|%u",
 						&tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) {
 
-					purple_debug(PURPLE_DEBUG_ERROR, "Adium log parse",
-							"Filename timestamp parsing error\n");
+					purple_debug_error("Adium log parse",
+					                   "Filename timestamp parsing error\n");
 				} else {
 					char *filename = g_build_filename(path, file, NULL);
 					FILE *handle = g_fopen(filename, "rb");
@@ -201,8 +193,8 @@
 					if (sscanf(contents2, "%u.%u.%u",
 							&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
 
-						purple_debug(PURPLE_DEBUG_ERROR, "Adium log parse",
-								"Contents timestamp parsing error\n");
+						purple_debug_error("Adium log parse",
+						                   "Contents timestamp parsing error\n");
 						g_free(contents);
 						g_free(filename);
 						continue;
@@ -222,7 +214,7 @@
 					log->logger = adium_logger;
 					log->logger_data = data;
 
-					list = g_list_append(list, log);
+					list = g_list_prepend(list, log);
 				}
 			}
 		}
@@ -242,17 +234,19 @@
 	gchar *read = NULL;
 	gsize length;
 
+	/* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE
+	 * XXX: TODO: for HTML logs. */
+	*flags = 0;
+
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
 	data = log->logger_data;
 
 	g_return_val_if_fail(data->path != NULL, g_strdup(""));
 
-	purple_debug(PURPLE_DEBUG_INFO, "Adium log read",
-				"Reading %s\n", data->path);
+	purple_debug_info("Adium log read", "Reading %s\n", data->path);
 	if (!g_file_get_contents(data->path, &read, &length, &error)) {
-		purple_debug(PURPLE_DEBUG_ERROR, "Adium log read",
-				"Error reading log\n");
+		purple_debug_error("Adium log read", "Error reading log\n");
 		if (error)
 			g_error_free(error);
 		return g_strdup("");
@@ -320,6 +314,7 @@
 	data = log->logger_data;
 
 	g_free(data->path);
+	g_free(data);
 }
 
 
@@ -481,7 +476,7 @@
 	if (!(datetime && *datetime))
 	{
 		purple_debug_error("MSN log timestamp parse",
-		                 "Attribute missing: %s\n", "DateTime");
+		                   "Attribute missing: %s\n", "DateTime");
 		return (time_t)0;
 	}
 
@@ -502,7 +497,7 @@
 	if (!(date && *date))
 	{
 		purple_debug_error("MSN log timestamp parse",
-		                 "Attribute missing: %s\n", "Date");
+		                   "Attribute missing: %s\n", "Date");
 		*tm_out = &tm2;
 		return stamp;
 	}
@@ -511,7 +506,7 @@
 	if (!(time && *time))
 	{
 		purple_debug_error("MSN log timestamp parse",
-		                 "Attribute missing: %s\n", "Time");
+		                   "Attribute missing: %s\n", "Time");
 		*tm_out = &tm2;
 		return stamp;
 	}
@@ -519,7 +514,7 @@
 	if (sscanf(date, "%u/%u/%u", &month, &day, &year) != 3)
 	{
 		purple_debug_error("MSN log timestamp parse",
-		                 "%s parsing error\n", "Date");
+		                   "%s parsing error\n", "Date");
 		*tm_out = &tm2;
 		return stamp;
 	}
@@ -536,7 +531,7 @@
 	if (sscanf(time, "%u:%u:%u %c", &hour, &min, &sec, &am_pm) != 4)
 	{
 		purple_debug_error("MSN log timestamp parse",
-		                 "%s parsing error\n", "Time");
+		                   "%s parsing error\n", "Time");
 		*tm_out = &tm2;
 		return stamp;
 	}
@@ -803,12 +798,10 @@
 		logfile = NULL; /* No sense saving the obvious buddy@domain.com. */
 	}
 
-	purple_debug(PURPLE_DEBUG_INFO, "MSN log read",
-				"Reading %s\n", path);
+	purple_debug_info("MSN log read", "Reading %s\n", path);
 	if (!g_file_get_contents(path, &contents, &length, &error)) {
 		g_free(path);
-		purple_debug(PURPLE_DEBUG_ERROR, "MSN log read",
-				"Error reading log\n");
+		purple_debug_error("MSN log read", "Error reading log\n");
 		if (error)
 			g_error_free(error);
 		return list;
@@ -837,8 +830,8 @@
 
 		session_id = xmlnode_get_attrib(message, "SessionID");
 		if (!session_id) {
-			purple_debug(PURPLE_DEBUG_ERROR, "MSN log parse",
-					"Error parsing message: %s\n", "SessionID missing");
+			purple_debug_error("MSN log parse",
+			                   "Error parsing message: %s\n", "SessionID missing");
 			continue;
 		}
 
@@ -864,7 +857,7 @@
 			log->logger = msn_logger;
 			log->logger_data = data;
 
-			list = g_list_append(list, log);
+			list = g_list_prepend(list, log);
 		}
 		old_session_id = session_id;
 	}
@@ -872,7 +865,7 @@
 	if (data)
 		data->last_log = TRUE;
 
-	return list;
+	return g_list_reverse(list);
 }
 
 static char * msn_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
@@ -881,6 +874,7 @@
 	GString *text = NULL;
 	xmlnode *message;
 
+	*flags = PURPLE_LOG_READ_NO_NEWLINE;
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
 	data = log->logger_data;
@@ -898,8 +892,8 @@
 
 	if (!data->root || !data->message || !data->session_id) {
 		/* Something isn't allocated correctly. */
-		purple_debug(PURPLE_DEBUG_ERROR, "MSN log parse",
-				"Error parsing message: %s\n", "Internal variables inconsistent");
+		purple_debug_error("MSN log parse",
+		                   "Error parsing message: %s\n", "Internal variables inconsistent");
 		data->text = text;
 
 		return text->str;
@@ -926,8 +920,8 @@
 
 		/* If this triggers, something is wrong with the XML. */
 		if (!new_session_id) {
-			purple_debug(PURPLE_DEBUG_ERROR, "MSN log parse",
-					"Error parsing message: %s\n", "New SessionID missing");
+			purple_debug_error("MSN log parse",
+			                   "Error parsing message: %s\n", "New SessionID missing");
 			break;
 		}
 
@@ -1133,10 +1127,10 @@
 			text = g_string_append(text, style);
 			text = g_string_append(text, "\">");
 			text = g_string_append(text, tmp);
-			text = g_string_append(text, "</span>\n");
+			text = g_string_append(text, "</span><br>");
 		} else {
 			text = g_string_append(text, tmp);
-			text = g_string_append(text, "\n");
+			text = g_string_append(text, "<br>");
 		}
 		g_free(tmp);
 	}
@@ -1176,6 +1170,8 @@
 
 	if (data->text)
 		g_string_free(data->text, FALSE);
+
+	g_free(data);
 }
 
 
@@ -1238,8 +1234,7 @@
 	path = g_build_filename(
 		logdir, prpl_name, filename, NULL);
 
-	purple_debug(PURPLE_DEBUG_INFO, "Trillian log list",
-				"Reading %s\n", path);
+	purple_debug_info("Trillian log list", "Reading %s\n", path);
 	/* FIXME: There's really no need to read the entire file at once.
 	 * See src/log.c:old_logger_list for a better approach.
 	 */
@@ -1252,8 +1247,7 @@
 
 		path = g_build_filename(
 			logdir, prpl_name, "Query", filename, NULL);
-		purple_debug(PURPLE_DEBUG_INFO, "Trillian log list",
-					"Reading %s\n", path);
+		purple_debug_info("Trillian log list", "Reading %s\n", path);
 		if (!g_file_get_contents(path, &contents, &length, &error)) {
 			if (error)
 				g_error_free(error);
@@ -1283,8 +1277,8 @@
 						/* This log had no data, so we remove it. */
 						GList *last = g_list_last(list);
 
-						purple_debug(PURPLE_DEBUG_INFO, "Trillian log list",
-							"Empty log. Offset %i\n", data->offset);
+						purple_debug_info("Trillian log list",
+						                  "Empty log. Offset %i\n", data->offset);
 
 						trillian_logger_finalize((PurpleLog *)last->data);
 						list = g_list_delete_link(list, last);
@@ -1295,7 +1289,7 @@
 				/* The conditional is to make sure we're not reading off
 				 * the end of the string.  We don't want strlen(), as that'd
 				 * have to count the whole string needlessly.
-				 * 
+				 *
 				 * The odd check here is because a Session Start at the
 				 * beginning of the file can be overwritten with a UTF-8
 				 * byte order mark.  Yes, it's weird.
@@ -1348,9 +1342,8 @@
 							&tm.tm_min, &tm.tm_sec,
 							&tm.tm_year) != 5) {
 
-						purple_debug(PURPLE_DEBUG_ERROR,
-							"Trillian log timestamp parse",
-							"Session Start parsing error\n");
+						purple_debug_error("Trillian log timestamp parse",
+						                   "Session Start parsing error\n");
 					} else {
 						PurpleLog *log;
 
@@ -1405,7 +1398,7 @@
 						log->logger = trillian_logger;
 						log->logger_data = data;
 
-						list = g_list_append(list, log);
+						list = g_list_prepend(list, log);
 					}
 				}
 			}
@@ -1420,7 +1413,7 @@
 
 	g_free(prpl_name);
 
-	return list;
+	return g_list_reverse(list);
 }
 
 static char * trillian_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
@@ -1434,6 +1427,7 @@
 	char *c;
 	const char *line;
 
+	*flags = PURPLE_LOG_READ_NO_NEWLINE;
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
 	data = log->logger_data;
@@ -1442,8 +1436,7 @@
 	g_return_val_if_fail(data->length > 0, g_strdup(""));
 	g_return_val_if_fail(data->their_nickname != NULL, g_strdup(""));
 
-	purple_debug(PURPLE_DEBUG_INFO, "Trillian log read",
-				"Reading %s\n", data->path);
+	purple_debug_info("Trillian log read", "Reading %s\n", data->path);
 
 	read = g_malloc(data->length + 2);
 
@@ -1491,7 +1484,7 @@
 		 * ">
 		 * Then, replace the next " " (or add this if the end-of-line is reached) with:
 		 * </a>
-		 * 
+		 *
 		 * As implemented, this isn't perfect, but it should cover common cases.
 		 */
 		while (line && (link = strstr(line, "(Link: ")))
@@ -1689,10 +1682,14 @@
 		if (footer)
 			g_string_append(formatted, footer);
 
-		g_string_append_c(formatted, '\n');
+		g_string_append(formatted, "<br>");
 	}
 
 	g_free(read);
+
+	/* XXX: TODO: What can we do about removing \r characters?
+	 * XXX: TODO: and will that allow us to avoid this
+	 * XXX: TODO: g_strchomp(), or is that unrelated? */
 	/* XXX: TODO: Avoid this g_strchomp() */
 	return g_strchomp(g_string_free(formatted, FALSE));
 }
@@ -1728,9 +1725,369 @@
 
 	g_free(data->path);
 	g_free(data->their_nickname);
-
+	g_free(data);
 }
 
+/*****************************************************************************
+ * QIP Logger                                                           *
+ *****************************************************************************/
+
+/* The QIP logger doesn't write logs, only reads them.  This is to include
+ * QIP logs in the log viewer transparently.
+ */
+#define QIP_LOG_DELIMITER "--------------------------------------"
+#define QIP_LOG_IN_MESSAGE (QIP_LOG_DELIMITER "<-")
+#define QIP_LOG_OUT_MESSAGE (QIP_LOG_DELIMITER ">-")
+#define QIP_LOG_IN_MESSAGE_ESC (QIP_LOG_DELIMITER "&lt;-")
+#define QIP_LOG_OUT_MESSAGE_ESC (QIP_LOG_DELIMITER "&gt;-")
+#define QIP_LOG_TIMEOUT (60*60)
+
+static PurpleLogLogger *qip_logger;
+
+struct qip_logger_data {
+
+	char *path; /* FIXME: Change this to use PurpleStringref like log.c:old_logger_list  */
+	int offset;
+	int length;
+};
+
+static GList *qip_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
+{
+	GList *list = NULL;
+	const char *logdir;
+	PurplePlugin *plugin;
+	PurplePluginProtocolInfo *prpl_info;
+	char *username;
+	char *filename;
+	char *path;
+	char *contents;
+	struct qip_logger_data *data = NULL;
+	struct tm prev_tm;
+	struct tm tm;
+	gboolean prev_tm_init = FALSE;
+	gboolean main_cycle = TRUE;
+	char *c;
+	char *start_log;
+	char *new_line;
+	int offset = 0;
+	GError *error;
+
+	g_return_val_if_fail(sn != NULL, list);
+	g_return_val_if_fail(account != NULL, list);
+
+	/* QIP only supports ICQ. */
+	if (strcmp(account->protocol_id, "prpl-icq"))
+		return list;
+
+	logdir = purple_prefs_get_string("/plugins/core/log_reader/qip/log_directory");
+
+	/* By clearing the log directory path, this logger can be (effectively) disabled. */
+	if (!*logdir)
+		return list;
+
+	plugin = purple_find_prpl(purple_account_get_protocol_id(account));
+	if (!plugin)
+		return NULL;
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
+	if (!prpl_info->list_icon)
+		return NULL;
+
+	username = g_strdup(purple_normalize(account, account->username));
+	filename = g_strdup_printf("%s.txt", purple_normalize(account, sn));
+	path = g_build_filename(logdir, username, "History", filename, NULL);
+	g_free(username);
+	g_free(filename);
+
+	purple_debug_info("QIP logger", "Reading %s\n", path);
+
+	error = NULL;
+	if (!g_file_get_contents(path, &contents, NULL, &error)) {
+		purple_debug_error("QIP logger",
+		                   "Couldn't read file %s: %s \n", path, error->message);
+		g_error_free(error);
+		g_free(path);
+		return list;
+	}
+
+	c = contents;
+	start_log = contents;
+	while (main_cycle) {
+
+		gboolean add_new_log = FALSE;
+
+		if (*c) {
+			if (purple_str_has_prefix(c, QIP_LOG_IN_MESSAGE) ||
+				purple_str_has_prefix(c, QIP_LOG_OUT_MESSAGE)) {
+
+				char *tmp;
+				
+				new_line = c;
+
+				/* find EOL */
+				c = strstr(c, "\n");
+				c++;
+
+				/* Find the last '(' character. */
+				if ((tmp = strstr(c, "\n")) != NULL) {
+					while (*tmp && *tmp != '(') --tmp;
+					c = tmp;
+				} else {
+					while (*c)
+						c++;
+					c--;
+					c = g_strrstr(c, "(");
+				}
+
+				if (c != NULL) {
+					const char *timestamp = ++c;
+
+					/*  Parse the time, day, month and year  */
+					if (sscanf(timestamp, "%u:%u:%u %u/%u/%u",
+						&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
+						&tm.tm_mday, &tm.tm_mon, &tm.tm_year) != 6) {
+
+						purple_debug_error("QIP logger list",
+							"Parsing timestamp error\n");
+					} else {
+						tm.tm_mon -= 1;
+						tm.tm_year -= 1900;
+
+						/* Let the C library deal with
+						 * daylight savings time. */
+						tm.tm_isdst = -1;
+
+						if (!prev_tm_init) {
+							prev_tm = tm;
+							prev_tm_init = TRUE;
+						} else {
+							add_new_log = difftime(mktime(&tm), mktime(&prev_tm)) > QIP_LOG_TIMEOUT;
+						}
+					}
+				}
+			}
+		} else {
+			add_new_log = TRUE;
+			main_cycle = FALSE;
+			new_line = c;
+		}
+
+		/* adding  log */
+		if (add_new_log && prev_tm_init) {
+			PurpleLog *log;
+
+			/* filling data */
+			data = g_new0(struct qip_logger_data, 1);
+			data->path = g_strdup(path);
+			data->length = new_line - start_log;
+			data->offset = offset;
+			offset += data->length;
+			purple_debug_info("QIP logger list",
+				"Creating log: path = (%s); length = (%d); offset = (%d)\n", 
+				data->path, data->length, data->offset);
+
+			/* XXX: Look into this later... Should we pass in a struct tm? */
+			log = purple_log_new(PURPLE_LOG_IM, sn, account,
+				NULL, mktime(&prev_tm), NULL);
+
+			log->logger = qip_logger;
+			log->logger_data = data;
+
+			list = g_list_prepend(list, log);
+
+			prev_tm = tm;
+			start_log = new_line;
+		}
+
+		if (*c) {
+			/* find EOF */
+			c = strstr(c, "\n");
+			c++;
+		}
+	}
+
+	g_free(contents);
+	g_free(path);
+	return g_list_reverse(list);
+}
+
+static char *qip_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
+{
+	struct qip_logger_data *data;
+	PurpleBuddy *buddy;
+	GString *formatted;
+	char *c;
+	const char *line;
+	gchar *contents;
+	char *selected;
+	GError *error;
+	char *utf8_string;
+	FILE *file;
+
+
+	g_return_val_if_fail(log != NULL, g_strdup(""));
+
+	data = log->logger_data;
+
+	g_return_val_if_fail(data->path != NULL, g_strdup(""));
+	g_return_val_if_fail(data->length > 0, g_strdup(""));
+
+	error = NULL;
+	
+	contents = g_malloc(data->length + 2);
+
+	file = g_fopen(data->path, "rb");
+	g_return_val_if_fail(file != NULL, g_strdup(""));
+	
+	fseek(file, data->offset, SEEK_SET);
+	fread(contents, data->length, 1, file);
+	fclose(file);
+
+	contents[data->length] = '\n';
+	contents[data->length + 1] = '\0';
+
+	/* Convert file contents from Cp1251 to UTF-8 codeset */
+	error = NULL;
+	if (!(utf8_string = g_convert(contents, -1, "UTF-8", "Cp1251", NULL, NULL, &error))) {
+		purple_debug_error("QIP logger",
+			"Couldn't convert file %s to UTF-8: %s\n", data->path, error->message);
+		g_error_free(error);
+		g_free(contents);
+		return g_strdup("");
+	}
+
+	g_free(contents);
+	contents = g_markup_escape_text(utf8_string, -1);
+	g_free(utf8_string);
+
+	buddy = purple_find_buddy(log->account, log->name);
+
+	/* Apply formatting... */
+	formatted = g_string_sized_new(data->length + 2);
+	c = contents;
+	line = contents;
+
+	while (*c) {
+		gboolean is_in_message = FALSE;
+
+		if (purple_str_has_prefix(line, QIP_LOG_IN_MESSAGE_ESC) ||
+		    purple_str_has_prefix(line, QIP_LOG_OUT_MESSAGE_ESC)) {
+
+			char *tmp;
+			const char *buddy_name;
+
+			is_in_message = purple_str_has_prefix(line, QIP_LOG_IN_MESSAGE_ESC);
+
+			/* find EOL */
+			c = strstr(c, "\n");
+
+			/* XXX: Do we need buddy_name when we have buddy->alias? */
+			buddy_name = ++c;
+
+			/* Find the last '(' character. */
+			if ((tmp = strstr(c, "\n")) != NULL) {
+				while (*tmp && *tmp != '(') --tmp;
+				c = tmp;
+			} else {
+				while (*c)
+					c++;
+				c--;
+				c = g_strrstr(c, "(");
+			}
+
+			if (c != NULL) {
+				const char *timestamp = c;
+				int hour;
+				int min;
+				int sec;
+
+				timestamp++;
+
+				/*  Parse the time, day, month and year */
+				if (sscanf(timestamp, "%u:%u:%u",
+				           &hour, &min, &sec) != 3) {
+					purple_debug_error("QIP logger read",
+					                   "Parsing timestamp error\n");
+				} else {
+					g_string_append(formatted, "<font size=\"2\">");
+					/* TODO: Figure out if we can do anything more locale-independent. */
+					g_string_append_printf(formatted,
+						"(%u:%02u:%02u) %cM ", hour % 12,
+						min, sec, (hour >= 12) ? 'P': 'A');
+					g_string_append(formatted, "</font> ");
+
+					if (is_in_message) {
+						if (buddy_name != NULL && buddy->alias) {
+							g_string_append_printf(formatted,
+								"<span style=\"color: #A82F2F;\">"
+								"<b>%s</b></span>: ", buddy->alias);
+						}
+					} else {
+						const char *acct_name;
+						acct_name = purple_account_get_alias(log->account);
+						if (!acct_name)
+							acct_name = purple_account_get_username(log->account);
+
+						g_string_append_printf(formatted,
+							"<span style=\"color: #16569E;\">"
+							"<b>%s</b></span>: ", acct_name);
+					}
+
+					/* find EOF */
+					c = strstr(c, "\n");
+					line = ++c;
+				}
+			}
+		} else {
+			if ((c = strstr(c, "\n")))
+				*c = '\0';
+
+			if (line[0] != '\n' && line[0] != '\r') {
+
+				g_string_append(formatted, line);
+				g_string_append(formatted, "<br>");
+			}
+			line = ++c;
+		}
+	}
+	g_free(contents);
+
+	/* XXX: TODO: Avoid this g_strchomp() */
+	return g_strchomp(g_string_free(formatted, FALSE));
+}
+
+static int qip_logger_size (PurpleLog *log)
+{
+	struct qip_logger_data *data;
+	char *text;
+	size_t size;
+
+	g_return_val_if_fail(log != NULL, 0);
+
+	data = log->logger_data;
+
+	if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
+		return data ? data->length : 0;
+	}
+
+	text = qip_logger_read(log, NULL);
+	size = strlen(text);
+	g_free(text);
+
+	return size;
+}
+
+static void qip_logger_finalize(PurpleLog *log)
+{
+	struct qip_logger_data *data;
+
+	g_return_if_fail(log != NULL);
+
+	data = log->logger_data;
+
+	g_free(data->path);
+	g_free(data);
+}
 
 /*****************************************************************************
  * Plugin Code                                                               *
@@ -1761,15 +2118,11 @@
 
 	/* Calculate default Adium log directory. */
 #ifdef _WIN32
-		path = "";
+	purple_prefs_add_string("/plugins/core/log_reader/adium/log_directory", "");
 #else
-		path = g_build_filename(purple_home_dir(), "Library", "Application Support",
-			"Adium 2.0", "Users", "Default", "Logs", NULL);
-#endif
-
+	path = g_build_filename(purple_home_dir(), "Library", "Application Support",
+	                        "Adium 2.0", "Users", "Default", "Logs", NULL);
 	purple_prefs_add_string("/plugins/core/log_reader/adium/log_directory", path);
-
-#ifndef _WIN32
 	g_free(path);
 #endif
 
@@ -1779,15 +2132,11 @@
 
 	/* Calculate default Fire log directory. */
 #ifdef _WIN32
-		path = "";
+	purple_prefs_add_string("/plugins/core/log_reader/fire/log_directory", "");
 #else
-		path = g_build_filename(purple_home_dir(), "Library", "Application Support",
-			"Fire", "Sessions", NULL);
-#endif
-
+	path = g_build_filename(purple_home_dir(), "Library", "Application Support",
+	                        "Fire", "Sessions", NULL);
 	purple_prefs_add_string("/plugins/core/log_reader/fire/log_directory", path);
-
-#ifndef _WIN32
 	g_free(path);
 #endif
 
@@ -1799,21 +2148,15 @@
 #ifdef _WIN32
 	folder = wpurple_get_special_folder(CSIDL_PERSONAL);
 	if (folder) {
-#endif
-	path = g_build_filename(
-#ifdef _WIN32
-		folder,
+		path = g_build_filename(folder, "My Chat Logs", NULL);
+		g_free(folder);
+	} else
+		path = g_strdup("");
 #else
-		PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
-		g_get_user_name(), "My Documents",
+	path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT,
+	                        "Documents and Settings", g_get_user_name(),
+	                        "My Documents", "My Chat Logs", NULL);
 #endif
-		"My Chat Logs", NULL);
-#ifdef _WIN32
-	g_free(folder);
-	} else /* !folder */
-		path = g_strdup("");
-#endif
-
 	purple_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path);
 	g_free(path);
 
@@ -1825,21 +2168,15 @@
 #ifdef _WIN32
 	folder = wpurple_get_special_folder(CSIDL_PERSONAL);
 	if (folder) {
-#endif
-	path = g_build_filename(
-#ifdef _WIN32
-		folder,
+		path = g_build_filename(folder, "My Received Files", NULL);
+		g_free(folder);
+	} else
+		path = g_strdup("");
 #else
-		PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
-		g_get_user_name(), "My Documents",
+	path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT,
+	                        "Documents and Settings", g_get_user_name(),
+	                        "My Documents", "My Received Files", NULL);
 #endif
-		"My Received Files", NULL);
-#ifdef _WIN32
-	g_free(folder);
-	} else /* !folder */
-		path = g_strdup("");
-#endif
-
 	purple_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path);
 	g_free(path);
 
@@ -1882,7 +2219,7 @@
 		char *folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES);
 		if (folder) {
 			path = g_build_filename(folder, "Trillian",
-					"users", "default", "talk.ini", NULL);
+			                        "users", "default", "talk.ini", NULL);
 			g_free(folder);
 		}
 	}
@@ -1894,25 +2231,25 @@
 #if 0 && GLIB_CHECK_VERSION(2,6,0) /* FIXME: Not tested yet. */
 		GKeyFile *key_file;
 
-		purple_debug(PURPLE_DEBUG_INFO, "Trillian talk.ini read",
-				"Reading %s\n", path);
+		purple_debug_info("Trillian talk.ini read", "Reading %s\n", path);
+
+		error = NULL;
 		if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, GError &error)) {
-			purple_debug(PURPLE_DEBUG_ERROR, "Trillian talk.ini read",
-					"Error reading talk.ini\n");
+			purple_debug_error("Trillian talk.ini read",
+			                   "Error reading talk.ini\n");
 			if (error)
 				g_error_free(error);
 		} else {
 			char *logdir = g_key_file_get_string(key_file, "Logging", "Directory", &error);
 			if (error) {
-				purple_debug(PURPLE_DEBUG_ERROR, "Trillian talk.ini read",
-						"Error reading Directory value from Logging section\n");
+				purple_debug_error("Trillian talk.ini read",
+				                   "Error reading Directory value from Logging section\n");
 				g_error_free(error);
 			}
 
 			if (logdir) {
 				g_strchomp(logdir);
-				purple_prefs_add_string(
-					"/plugins/core/log_reader/trillian/log_directory", logdir);
+				purple_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", logdir);
 				found = TRUE;
 			}
 
@@ -1922,11 +2259,11 @@
 		gsize length;
 		gchar *contents = NULL;
 
-		purple_debug(PURPLE_DEBUG_INFO, "Trillian talk.ini read",
+		purple_debug_info("Trillian talk.ini read",
 					"Reading %s\n", path);
 		if (!g_file_get_contents(path, &contents, &length, &error)) {
-			purple_debug(PURPLE_DEBUG_ERROR, "Trillian talk.ini read",
-					"Error reading talk.ini\n");
+			purple_debug_error("Trillian talk.ini read",
+			                   "Error reading talk.ini\n");
 			if (error)
 				g_error_free(error);
 		} else {
@@ -1957,32 +2294,43 @@
 	} /* path */
 
 	if (!found) {
-#endif /* defined(_WIN32) */
+		folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES);
+		if (folder) {
+			path = g_build_filename(folder, "Trillian", "users",
+			                        "default", "logs", NULL);
+			g_free(folder);
+		} else
+			path = g_strdup("");
+	}
+#else /* !defined(_WIN32) */
+	/* TODO: At some point, this could attempt to parse talk.ini
+	 * TODO: from the default Trillian install directory on the
+	 * TODO: Windows mount point. */
 
 	/* Calculate default Trillian log directory. */
+	path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT,
+	                        "Program Files", "Trillian", "users",
+	                        "default", "logs", NULL);
+#endif
+
+
+	/* Add QIP log directory preference. */
+	purple_prefs_add_none("/plugins/core/log_reader/qip");
+
+	/* Calculate default QIP log directory. */
 #ifdef _WIN32
 	folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES);
 	if (folder) {
-#endif
-	path = g_build_filename(
-#ifdef _WIN32
-		folder,
+		path = g_build_filename(folder, "QIP", "Users", NULL);
+		g_free(folder);
+	} else
+		path = g_strdup("");
 #else
-		PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Program Files",
+	path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT,
+	                        "Program Files", "QIP", "Users", NULL);
 #endif
-		"Trillian", "users", "default", "logs", NULL);
-#ifdef _WIN32
-	g_free(folder);
-	} else /* !folder */
-		path = g_strdup("");
-#endif
-
-	purple_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", path);
+	purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path);
 	g_free(path);
-
-#ifdef _WIN32
-	} /* !found */
-#endif
 }
 
 static gboolean
@@ -2026,11 +2374,24 @@
 												messenger_plus_logger_read,
 												messenger_plus_logger_size);
 	purple_log_logger_add(messenger_plus_logger);
+
 #endif
 
 	/* The names of IM clients are marked for translation at the request of
 	   translators who wanted to transliterate them.  Many translators
 	   choose to leave them alone.  Choose what's best for your language. */
+	qip_logger = purple_log_logger_new("qip", _("QIP"), 6,
+											NULL,
+											NULL,
+											qip_logger_finalize,
+											qip_logger_list,
+											qip_logger_read,
+											qip_logger_size);
+	purple_log_logger_add(qip_logger);
+
+	/* The names of IM clients are marked for translation at the request of
+	   translators who wanted to transliterate them.  Many translators
+	   choose to leave them alone.  Choose what's best for your language. */
 	msn_logger = purple_log_logger_new("msn", _("MSN Messenger"), 6,
 									 NULL,
 									 NULL,
@@ -2067,6 +2428,7 @@
 #endif
 	purple_log_logger_remove(msn_logger);
 	purple_log_logger_remove(trillian_logger);
+	purple_log_logger_remove(qip_logger);
 
 	return TRUE;
 }
@@ -2116,6 +2478,10 @@
 #endif
 
 	ppref = purple_plugin_pref_new_with_name_and_label(
+		"/plugins/core/log_reader/qip/log_directory", _("QIP"));
+	purple_plugin_pref_frame_add(frame, ppref);
+
+	ppref = purple_plugin_pref_new_with_name_and_label(
 		"/plugins/core/log_reader/msn/log_directory", _("MSN Messenger"));
 	purple_plugin_pref_frame_add(frame, ppref);
 
--- a/libpurple/protocols/gg/lib/http.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/gg/lib/http.c	Sun Jul 01 08:41:01 2007 +0000
@@ -349,7 +349,7 @@
 			gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header);
 
 			while (line) {
-				if (!strncasecmp(line, "Content-length: ", 16)) {
+				if (!g_ascii_strncasecmp(line, "Content-length: ", 16)) {
 					h->body_size = atoi(line + 16);
 				}
 				line = strchr(line, '\n');
--- a/libpurple/protocols/gg/lib/pubdir50.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/gg/lib/pubdir50.c	Sun Jul 01 08:41:01 2007 +0000
@@ -348,7 +348,7 @@
 
 		/* je¶li dostali¶my namier na nastêpne wyniki, to znaczy ¿e
 		 * mamy koniec wyników i nie jest to kolejna osoba. */
-		if (!strcasecmp(field, "nextstart")) {
+		if (!g_ascii_strcasecmp(field, "nextstart")) {
 			res->next = atoi(value);
 			num--;
 		} else {
@@ -391,7 +391,7 @@
 	}
 
 	for (i = 0; i < res->entries_count; i++) {
-		if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) {
+		if (res->entries[i].num == num && !g_ascii_strcasecmp(res->entries[i].field, field)) {
 			value = res->entries[i].value;
 			break;
 		}
--- a/libpurple/protocols/msn/msn-utils.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/msn/msn-utils.c	Sun Jul 01 08:41:01 2007 +0000
@@ -296,7 +296,7 @@
 							char *attr_dir;
 							attributes = g_strndup(c, attr_len);
 							attr_dir = purple_markup_get_css_property(attributes, "direction");
-							if (attr_dir && (!strncasecmp(attr_dir, "RTL", 3)))
+							if (attr_dir && (!g_ascii_strncasecmp(attr_dir, "RTL", 3)))
 								direction = '1';
 							g_free(attr_dir);
 							g_free(attributes);
--- a/libpurple/protocols/oscar/odc.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/oscar/odc.c	Sun Jul 01 08:41:01 2007 +0000
@@ -314,7 +314,7 @@
 			tmp += size;
 
 			/* Skip past the closing </data> tag */
-			if (strncasecmp(tmp, "</data>", 7))
+			if (g_ascii_strncasecmp(tmp, "</data>", 7))
 			{
 				g_free(embedded_data);
 				break;
--- a/libpurple/protocols/silc/buddy.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/silc/buddy.c	Sun Jul 01 08:41:01 2007 +0000
@@ -1252,7 +1252,7 @@
 			SilcClientEntry entry;
 			silc_dlist_start(clients);
 			while ((entry = silc_dlist_get(clients))) {
-				if (!strncasecmp(b->name, entry->nickname,
+				if (!g_ascii_strncasecmp(b->name, entry->nickname,
 						 strlen(b->name))) {
 					client_entry = entry;
 					break;
--- a/libpurple/protocols/silc/util.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/silc/util.c	Sun Jul 01 08:41:01 2007 +0000
@@ -623,15 +623,15 @@
 	ct = strrchr(filename, '.');
 	if (!ct)
 		return NULL;
-	else if (!strcasecmp(".png", ct))
+	else if (!g_ascii_strcasecmp(".png", ct))
 		return strdup("image/png");
-	else if (!strcasecmp(".jpg", ct))
+	else if (!g_ascii_strcasecmp(".jpg", ct))
 		return strdup("image/jpeg");
-	else if (!strcasecmp(".jpeg", ct))
+	else if (!g_ascii_strcasecmp(".jpeg", ct))
 		return strdup("image/jpeg");
-	else if (!strcasecmp(".gif", ct))
+	else if (!g_ascii_strcasecmp(".gif", ct))
 		return strdup("image/gif");
-	else if (!strcasecmp(".tiff", ct))
+	else if (!g_ascii_strcasecmp(".tiff", ct))
 		return strdup("image/tiff");
 
 	return NULL;
--- a/libpurple/protocols/silc10/buddy.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/silc10/buddy.c	Sun Jul 01 08:41:01 2007 +0000
@@ -1246,7 +1246,7 @@
 			   buddy nickname. */
 			int i;
 			for (i = 0; i < clients_count; i++) {
-				if (!strncasecmp(b->name, clients[i]->nickname,
+				if (!g_ascii_strncasecmp(b->name, clients[i]->nickname,
 						 strlen(b->name))) {
 					clients[0] = clients[i];
 					break;
--- a/libpurple/protocols/simple/simple.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/simple/simple.c	Sun Jul 01 08:41:01 2007 +0000
@@ -337,7 +337,7 @@
 		return;
 	}
 
-	if(!g_strncasecmp(hdr, "NTLM", 4)) {
+	if(!g_ascii_strncasecmp(hdr, "NTLM", 4)) {
 		purple_debug_info("simple", "found NTLM\n");
 		auth->type = 2;
 		parts = g_strsplit(hdr+5, "\", ", 0);
--- a/libpurple/protocols/yahoo/yahoo_picture.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Sun Jul 01 08:41:01 2007 +0000
@@ -110,7 +110,7 @@
 	}
 
 	/* Yahoo IM 6 spits out 0.png as the URL if the buddy icon is not set */
-	if (who && got_icon_info && url && !strncasecmp(url, "http://", 7)) {
+	if (who && got_icon_info && url && !g_ascii_strncasecmp(url, "http://", 7)) {
 		/* TODO: make this work p2p, try p2p before the url */
 		PurpleUtilFetchUrlData *url_data;
 		struct yahoo_fetch_picture_data *data;
--- a/libpurple/protocols/yahoo/yahoochat.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Sun Jul 01 08:41:01 2007 +0000
@@ -785,44 +785,6 @@
 	g_free(eroom);
 }
 
-/* borrowed from gtkconv.c */
-static gboolean
-meify(char *message, size_t len)
-{
-	/*
-	 * Read /me-ify: If the message (post-HTML) starts with /me,
-	 * remove the "/me " part of it (including that space) and return TRUE.
-	 */
-	char *c;
-	gboolean inside_html = 0;
-
-	/* Umm.. this would be very bad if this happens. */
-	g_return_val_if_fail(message != NULL, FALSE);
-
-	if (len == -1)
-		len = strlen(message);
-
-	for (c = message; *c != '\0'; c++, len--) {
-		if (inside_html) {
-			if (*c == '>')
-				inside_html = FALSE;
-		}
-		else {
-			if (*c == '<')
-				inside_html = TRUE;
-			else
-				break;
-		}
-	}
-
-	if (*c != '\0' && !g_ascii_strncasecmp(c, "/me ", 4)) {
-		memmove(c, c + 4, len - 3);
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
 static int yahoo_chat_send(PurpleConnection *gc, const char *dn, const char *room, const char *what, PurpleMessageFlags flags)
 {
 	struct yahoo_data *yd = gc->proto_data;
@@ -839,7 +801,7 @@
 
 	msg1 = g_strdup(what);
 
-	if (meify(msg1, -1))
+	if (purple_message_meify(msg1, -1))
 		me = 1;
 
 	msg2 = yahoo_html_to_codes(msg1);
--- a/libpurple/protocols/zephyr/ZVariables.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/protocols/zephyr/ZVariables.c	Sun Jul 01 08:41:01 2007 +0000
@@ -186,7 +186,7 @@
 #define max(a,b) ((a > b) ? (a) : (b))
 #endif
 
-    if (strncasecmp(bfr, var, max(strlen(var),cp - bfr)))
+    if (g_strncasecmp(bfr, var, max(strlen(var), cp - bfr)))
 	return(0);			/* var is not the var in
 					   bfr ==> no match */
 
--- a/libpurple/savedstatuses.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/savedstatuses.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,8 @@
  * 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
+ *
+ * @see @ref savedstatus-signals
  */
 #ifndef _PURPLE_SAVEDSTATUSES_H_
 #define _PURPLE_SAVEDSTATUSES_H_
--- a/libpurple/sound.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/sound.h	Sun Jul 01 08:41:01 2007 +0000
@@ -21,6 +21,8 @@
  * 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
+ *
+ * @see @ref sound-signals
  */
 #ifndef _PURPLE_SOUND_H_
 #define _PURPLE_SOUND_H_
--- a/libpurple/tests/check_libpurple.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/tests/check_libpurple.c	Sun Jul 01 08:41:01 2007 +0000
@@ -20,9 +20,17 @@
 
 static PurpleEventLoopUiOps eventloop_ui_ops = {
 	g_timeout_add,
-	(guint (*)(guint))g_source_remove,
+	g_source_remove,
 	purple_check_input_add,
-	(guint (*)(guint))g_source_remove,
+	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/libpurple/util.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/util.c	Sun Jul 01 08:41:01 2007 +0000
@@ -1732,7 +1732,7 @@
 			if (cdata_close_tag)
 			{
 				/* Note: Don't even assume any other tag is a tag in CDATA */
-				if (strncasecmp(str2 + i, cdata_close_tag,
+				if (g_ascii_strncasecmp(str2 + i, cdata_close_tag,
 						strlen(cdata_close_tag)) == 0)
 				{
 					i += strlen(cdata_close_tag) - 1;
@@ -1740,12 +1740,12 @@
 				}
 				continue;
 			}
-			else if (strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p)
+			else if (g_ascii_strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p)
 			{
 				str2[j++] = '\t';
 				visible = TRUE;
 			}
-			else if (strncasecmp(str2 + i, "</td>", 5) == 0)
+			else if (g_ascii_strncasecmp(str2 + i, "</td>", 5) == 0)
 			{
 				closing_td_p = TRUE;
 				visible = FALSE;
@@ -1773,7 +1773,7 @@
 
 				/* If we've got an <a> tag with an href, save the address
 				 * to print later. */
-				if (strncasecmp(str2 + i, "<a", 2) == 0 &&
+				if (g_ascii_strncasecmp(str2 + i, "<a", 2) == 0 &&
 				    g_ascii_isspace(str2[i+2]))
 				{
 					int st; /* start of href, inclusive [ */
@@ -1782,7 +1782,7 @@
 					/* Find start of href */
 					for (st = i + 3; st < k; st++)
 					{
-						if (strncasecmp(str2+st, "href=", 5) == 0)
+						if (g_ascii_strncasecmp(str2+st, "href=", 5) == 0)
 						{
 							st += 5;
 							if (str2[st] == '"' || str2[st] == '\'')
@@ -1814,7 +1814,7 @@
 
 				/* Replace </a> with an ascii representation of the
 				 * address the link was pointing to. */
-				else if (href != NULL && strncasecmp(str2 + i, "</a>", 4) == 0)
+				else if (href != NULL && g_ascii_strncasecmp(str2 + i, "</a>", 4) == 0)
 				{
 
 					size_t hrlen = strlen(href);
@@ -1836,29 +1836,29 @@
 				}
 
 				/* Check for tags which should be mapped to newline */
-				else if (strncasecmp(str2 + i, "<p>", 3) == 0
-				 || strncasecmp(str2 + i, "<tr", 3) == 0
-				 || strncasecmp(str2 + i, "<br", 3) == 0
-				 || strncasecmp(str2 + i, "<hr", 3) == 0
-				 || strncasecmp(str2 + i, "<li", 3) == 0
-				 || strncasecmp(str2 + i, "<div", 4) == 0
-				 || strncasecmp(str2 + i, "</table>", 8) == 0)
+				else if (g_ascii_strncasecmp(str2 + i, "<p>", 3) == 0
+				 || g_ascii_strncasecmp(str2 + i, "<tr", 3) == 0
+				 || g_ascii_strncasecmp(str2 + i, "<br", 3) == 0
+				 || g_ascii_strncasecmp(str2 + i, "<hr", 3) == 0
+				 || g_ascii_strncasecmp(str2 + i, "<li", 3) == 0
+				 || g_ascii_strncasecmp(str2 + i, "<div", 4) == 0
+				 || g_ascii_strncasecmp(str2 + i, "</table>", 8) == 0)
 				{
 					str2[j++] = '\n';
 				}
 				/* Check for tags which begin CDATA and need to be closed */
 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */
-				else if (strncasecmp(str2 + i, "<option", 7) == 0)
+				else if (g_ascii_strncasecmp(str2 + i, "<option", 7) == 0)
 				{
 					/* FIXME: We should not do this if the OPTION is SELECT'd */
 					cdata_close_tag = "</option>";
 				}
 #endif
-				else if (strncasecmp(str2 + i, "<script", 7) == 0)
+				else if (g_ascii_strncasecmp(str2 + i, "<script", 7) == 0)
 				{
 					cdata_close_tag = "</script>";
 				}
-				else if (strncasecmp(str2 + i, "<style", 6) == 0)
+				else if (g_ascii_strncasecmp(str2 + i, "<style", 6) == 0)
 				{
 					cdata_close_tag = "</style>";
 				}
@@ -3081,7 +3081,7 @@
 	i = 0; /* position in the source string */
 	j = 0; /* number of occurrences of "delimiter" */
 	while (string[i] != '\0') {
-		if (!strncasecmp(&string[i], delimiter, length_del)) {
+		if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
 			i += length_del;
 			j += length_rep;
 		} else {
@@ -3095,7 +3095,7 @@
 	i = 0; /* position in the source string */
 	j = 0; /* position in the destination string */
 	while (string[i] != '\0') {
-		if (!strncasecmp(&string[i], delimiter, length_del)) {
+		if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
 			strncpy(&ret[j], replacement, length_rep);
 			i += length_del;
 			j += length_rep;
--- a/libpurple/util.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/util.h	Sun Jul 01 08:41:01 2007 +0000
@@ -1105,12 +1105,13 @@
 void purple_print_utf8_to_console(FILE *filestream, char *message);
 
 /**
- * Checks for messages starting with "/me "
+ * Checks for messages starting (post-HTML) with "/me ", including the space.
  *
  * @param message The message to check
  * @param len     The message length, or -1
  *
- * @return TRUE if it starts with /me, and it has been removed, otherwise FALSE
+ * @return TRUE if it starts with "/me ", and it has been removed, otherwise
+ *         FALSE
  */
 gboolean purple_message_meify(char *message, size_t len);
 
--- a/libpurple/win32/libc_interface.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/win32/libc_interface.c	Sun Jul 01 08:41:01 2007 +0000
@@ -413,8 +413,8 @@
 		/* newname exists */
 		if(g_stat(newname, &newstat) == 0) {
 			/* oldname is a dir */
-			if(_S_ISDIR(oldstat.st_mode)) {
-				if(!_S_ISDIR(newstat.st_mode)) {
+			if(S_ISDIR(oldstat.st_mode)) {
+				if(!S_ISDIR(newstat.st_mode)) {
 					return g_rename(oldname, newname);
 				}
 				/* newname is a dir */
@@ -430,7 +430,7 @@
 			/* oldname is not a dir */
 			else {
 				/* newname is a dir */
-				if(_S_ISDIR(newstat.st_mode)) {
+				if(S_ISDIR(newstat.st_mode)) {
 					errno = EISDIR;
 					return -1;
 				}
--- a/libpurple/win32/libc_interface.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/libpurple/win32/libc_interface.h	Sun Jul 01 08:41:01 2007 +0000
@@ -29,6 +29,16 @@
 #include "libc_internal.h"
 #include <glib.h>
 
+#ifdef _MSC_VER
+#define S_IRUSR S_IREAD
+#define S_IWUSR S_IWRITE
+#define S_IXUSR S_IEXEC
+
+#define S_ISDIR(m)	 (((m)&S_IFDIR)==S_IFDIR)
+
+#define F_OK 0
+#endif
+
 /* sys/socket.h */
 #define socket( namespace, style, protocol ) \
 wpurple_socket( namespace, style, protocol )
@@ -36,11 +46,11 @@
 #define connect( socket, addr, length ) \
 wpurple_connect( socket, addr, length )
 
-#define getsockopt( args... ) \
-wpurple_getsockopt( args )
+#define getsockopt( socket, level, optname, optval, optlenptr ) \
+wpurple_getsockopt( socket, level, optname, optval, optlenptr )
 
-#define setsockopt( args... ) \
-wpurple_setsockopt( args )
+#define setsockopt( socket, level, optname, optval, optlen ) \
+wpurple_setsockopt( socket, level, optname, optval, optlen )
 
 #define getsockname( socket, addr, lenptr ) \
 wpurple_getsockname( socket, addr, lenptr )
--- a/pidgin/gtkblist.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkblist.c	Sun Jul 01 08:41:01 2007 +0000
@@ -2873,7 +2873,11 @@
 	{ N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
 	{ N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
 	{ N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
+#if GTK_CHECK_VERSION(2,6,0)	
+	{ N_("/Help/_About"), NULL, pidgin_dialogs_about, 0,  "<StockItem>", GTK_STOCK_ABOUT },
+#else
 	{ N_("/Help/_About"), NULL, pidgin_dialogs_about, 0,  "<Item>", NULL },
+#endif
 };
 
 /*********************************************************
--- a/pidgin/gtkconv.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkconv.c	Sun Jul 01 08:41:01 2007 +0000
@@ -2410,9 +2410,6 @@
 		return FALSE;
 	}
 
-	gtkconv->auto_resize = TRUE;
-	g_idle_add(reset_auto_resize_cb, gtkconv);
-
 	gdk_pixbuf_animation_iter_advance(gtkconv->u.im->iter, NULL);
 	buf = gdk_pixbuf_animation_iter_get_pixbuf(gtkconv->u.im->iter);
 
@@ -4285,7 +4282,7 @@
 	if (sr.height < height + PIDGIN_HIG_BOX_SPACE) {
 		gtkconv->auto_resize = TRUE;
 		gtkconv->entry_growing = TRUE;
-	        gtk_widget_set_size_request(gtkconv->lower_hbox, -1, height + PIDGIN_HIG_BOX_SPACE);
+	        gtk_widget_set_size_request(gtkconv->entry, -1, height);
 	        g_idle_add(reset_auto_resize_cb, gtkconv);
 	}
 }
--- a/pidgin/gtkimhtml.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkimhtml.c	Sun Jul 01 08:41:01 2007 +0000
@@ -501,6 +501,8 @@
 	GSList *tags = NULL, *templist = NULL;
 	GdkColor *norm, *pre;
 	GtkTextTag *tag = NULL, *oldprelit_tag;
+	GtkTextChildAnchor* anchor;
+	gboolean hand = TRUE;
 
 	oldprelit_tag = GTK_IMHTML(imhtml)->prelit_tag;
 
@@ -557,10 +559,17 @@
 			g_source_remove(GTK_IMHTML(imhtml)->tip_timer);
 		GTK_IMHTML(imhtml)->tip_timer = 0;
 	}
+
+	/* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
+	anchor = gtk_text_iter_get_child_anchor(&iter);
+	if (anchor) {
+		tip = g_object_get_data(G_OBJECT(anchor), "gtkimhtml_plaintext");
+		hand = FALSE;
+	}
 //yaz here bomb explodes
 #if 1
 	if (tip){
-		if (!GTK_IMHTML(imhtml)->editable)
+		if (!GTK_IMHTML(imhtml)->editable && hand)
 			gdk_window_set_cursor(win, GTK_IMHTML(imhtml)->hand_cursor);
 		GTK_IMHTML(imhtml)->tip_timer = g_timeout_add (TOOLTIP_TIMEOUT,
 							       gtk_imhtml_tip, imhtml);
@@ -2161,7 +2170,7 @@
 	gint i;
 
 	for(i=0; i<accepted_protocols_size; i++){
-		if( strncasecmp(text, accepted_protocols[i], strlen(accepted_protocols[i])) == 0  ){
+		if( g_ascii_strncasecmp(text, accepted_protocols[i], strlen(accepted_protocols[i])) == 0  ){
 			return strlen(accepted_protocols[i]);
 		}
 	}
@@ -2734,7 +2743,7 @@
 						/* NEW_BIT (NEW_TEXT_BIT); */
 
 						/* Bi-Directional text support */
-						if (direction && (!strncasecmp(direction, "RTL", 3))) {
+						if (direction && (!g_ascii_strncasecmp(direction, "RTL", 3))) {
 							rtl_direction = TRUE;
 							/* insert RLE character to set direction */
 							ws[wpos++]  = 0xE2;
@@ -2746,7 +2755,7 @@
 						}
 						g_free(direction);
 
-						if (alignment && (!strncasecmp(alignment, "RIGHT", 5))) {
+						if (alignment && (!g_ascii_strncasecmp(alignment, "RIGHT", 5))) {
 							align_right = TRUE;
 							align_line = gtk_text_iter_get_line(iter);
 						}
@@ -4629,6 +4638,7 @@
 			GtkWidget *img = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_MENU);
 			gtk_container_add(GTK_CONTAINER(ebox), img);
 			gtk_widget_show(img);
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", g_strdup(unescaped), g_free);
 			gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor);
 		}
 	} else {
--- a/pidgin/gtkimhtmltoolbar.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Sun Jul 01 08:41:01 2007 +0000
@@ -37,6 +37,8 @@
 #include "gtkthemes.h"
 #include "gtkutils.h"
 
+#include <gdk/gdkkeysyms.h>
+
 static GtkHBoxClass *parent_class = NULL;
 
 static void toggle_button_set_active_block(GtkToggleButton *button,
@@ -629,7 +631,6 @@
 	return ls;
 }
 
-
 static gboolean
 smiley_is_unique(GSList *list, GtkIMHtmlSmiley *smiley)
 {
@@ -642,6 +643,18 @@
 	return TRUE;
 }
 
+static gboolean
+smiley_dialog_input_cb(GtkWidget *dialog, GdkEvent *event, GtkIMHtmlToolbar *toolbar)
+{
+	if ((event->type == GDK_KEY_PRESS && event->key.keyval == GDK_Escape) ||
+	    (event->type == GDK_BUTTON_PRESS && event->button.button == 1))
+	{
+		close_smiley_dialog(NULL, NULL, toolbar);
+		return TRUE;
+	}
+
+	return FALSE;
+}
 
 static void
 insert_smiley_cb(GtkWidget *smiley, GtkIMHtmlToolbar *toolbar)
@@ -720,11 +733,16 @@
 			free(it_tmp);
 		}
 		gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, TRUE, 0);
+
+		gtk_widget_add_events(dialog, GDK_KEY_PRESS_MASK);
 	}
 	else {
 		smiley_table = gtk_label_new(_("This theme has no available smileys."));
+		gtk_widget_add_events(dialog, GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK);
+		g_signal_connect(G_OBJECT(dialog), "button-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
 	}
 
+	g_signal_connect(G_OBJECT(dialog), "key-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
 	gtk_container_add(GTK_CONTAINER(dialog), smiley_table);
 
 	gtk_widget_show(smiley_table);
--- a/pidgin/gtkprefs.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkprefs.c	Sun Jul 01 08:41:01 2007 +0000
@@ -60,6 +60,7 @@
 
 static GtkWidget *sound_entry = NULL;
 static GtkListStore *smiley_theme_store = NULL;
+static GtkTreeSelection *smiley_theme_sel = NULL;
 static GtkWidget *prefs_proxy_frame = NULL;
 
 static GtkWidget *prefs = NULL;
@@ -366,9 +367,12 @@
 	GValue val;
 	GtkTreePath *path, *oldpath;
 	struct smiley_theme *new_theme, *old_theme;
+	GtkWidget *remove_button = g_object_get_data(G_OBJECT(sel), "remove_button");
 
-	if (!gtk_tree_selection_get_selected(sel, &model, &iter))
+	if (!gtk_tree_selection_get_selected(sel, &model, &iter)) {
+		gtk_widget_set_sensitive(remove_button, FALSE);
 		return;
+	}
 
 	old_theme = current_smiley_theme;
 	val.g_type = 0;
@@ -376,6 +380,9 @@
 	path = gtk_tree_model_get_path(model, &iter);
 	themename = g_value_get_string(&val);
 	purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", themename);
+
+	gtk_widget_set_sensitive(remove_button, (strcmp(themename, "none") &&
+	                                         strcmp(themename, _("Default"))));
 	g_value_unset (&val);
 
 	/* current_smiley_theme is set in callback for the above pref change */
@@ -514,8 +521,13 @@
 	g_free(destdir);
 
 	theme_rowref = theme_refresh_theme_list();
-	if (theme_rowref != NULL)
+	if (theme_rowref != NULL) {
+		GtkTreePath *tp = gtk_tree_row_reference_get_path(theme_rowref);
+
+		if (tp)
+			gtk_tree_selection_select_path(smiley_theme_sel, tp);
 		gtk_tree_row_reference_free(theme_rowref);
+	}
 }
 
 static void
@@ -619,9 +631,55 @@
 	return ret;
 }
 
+static void
+request_theme_file_name_cb(gpointer data, char *theme_file_name)
+{
+	theme_install_theme(theme_file_name, NULL) ;
+}
+
+static void
+add_theme_button_clicked_cb(GtkWidget *widget, gpointer null)
+{
+	purple_request_file(NULL, "Install Theme", NULL, FALSE, (GCallback)request_theme_file_name_cb, NULL, NULL, NULL, NULL, NULL) ;
+}
+
+static void
+remove_theme_button_clicked_cb(GtkWidget *button, GtkTreeView *tv)
+{
+	char *theme_name = NULL, *theme_file = NULL;
+	GtkTreeModel *tm;
+	GtkTreeIter itr;
+	GtkTreeRowReference *trr = NULL;
+
+	if ((tm = gtk_tree_view_get_model(tv)) == NULL)
+		return;
+	if (!gtk_tree_selection_get_selected(smiley_theme_sel, NULL, &itr))
+		return;
+	gtk_tree_model_get(tm, &itr, 2, &theme_file, 3, &theme_name, -1);
+
+	if (theme_file && theme_name && strcmp(theme_name, "none"))
+		pidgin_themes_remove_smiley_theme(theme_file);
+
+	if ((trr = theme_refresh_theme_list()) != NULL) {
+		GtkTreePath *tp = gtk_tree_row_reference_get_path(trr);
+
+		if (tp) {
+			gtk_tree_selection_select_path(smiley_theme_sel, tp);
+			gtk_tree_path_free(tp);
+		}
+		gtk_tree_row_reference_free(trr);
+	}
+
+	g_free(theme_file);
+	g_free(theme_name);
+}
+
 static GtkWidget *
 theme_page()
 {
+	GtkWidget *add_button, *remove_button;
+	GtkWidget *hbox_buttons;
+	GtkWidget *alignment;
 	GtkWidget *ret;
 	GtkWidget *sw;
 	GtkWidget *view;
@@ -661,7 +719,7 @@
 	g_signal_connect(G_OBJECT(view), "drag_data_received", G_CALLBACK(theme_dnd_recv), smiley_theme_store);
 
 	rend = gtk_cell_renderer_pixbuf_new();
-	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	smiley_theme_sel = sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
 
 	/* Custom sort so "none" theme is at top of list */
 	gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(smiley_theme_store),
@@ -687,6 +745,25 @@
 
 	g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(smiley_sel), NULL);
 
+	alignment = gtk_alignment_new(1.0, 0.5, 0.0, 1.0);
+	gtk_widget_show(alignment);
+	gtk_box_pack_start(GTK_BOX(ret), alignment, FALSE, TRUE, 0);
+
+	hbox_buttons = gtk_hbox_new(TRUE, PIDGIN_HIG_CAT_SPACE);
+	gtk_widget_show(hbox_buttons);
+	gtk_container_add(GTK_CONTAINER(alignment), hbox_buttons);
+
+	add_button = gtk_button_new_from_stock(GTK_STOCK_ADD);
+	gtk_widget_show(add_button);
+	gtk_box_pack_start(GTK_BOX(hbox_buttons), add_button, FALSE, TRUE, 0);
+	g_signal_connect(G_OBJECT(add_button), "clicked", (GCallback)add_theme_button_clicked_cb, view);
+
+	remove_button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
+	gtk_widget_show(remove_button);
+	gtk_box_pack_start(GTK_BOX(hbox_buttons), remove_button, FALSE, TRUE, 0);
+	g_signal_connect(G_OBJECT(remove_button), "clicked", (GCallback)remove_theme_button_clicked_cb, view);
+	g_object_set_data(G_OBJECT(sel), "remove_button", remove_button);
+
 	if (rowref) {
 		GtkTreePath *path = gtk_tree_row_reference_get_path(rowref);
 		gtk_tree_row_reference_free(rowref);
@@ -978,7 +1055,7 @@
 									GTK_IMHTML_BACKCOLOR |
 									GTK_IMHTML_BACKGROUND);
 
-	gtk_imhtml_append_text(GTK_IMHTML(imhtml), _("This is how your outgoing message text will appear when you use protocols that support formatting. :)"), 0);
+	gtk_imhtml_append_text(GTK_IMHTML(imhtml), _("This is how your outgoing message text will appear when you use protocols that support formatting."), 0);
 
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 
--- a/pidgin/gtkthemes.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkthemes.c	Sun Jul 01 08:41:01 2007 +0000
@@ -36,6 +36,8 @@
 GSList *smiley_themes = NULL;
 struct smiley_theme *current_smiley_theme;
 
+static void pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme);
+
 gboolean pidgin_themes_smileys_disabled()
 {
 	if (!current_smiley_theme)
@@ -44,6 +46,79 @@
 	return strcmp(current_smiley_theme->name, "none") == 0;
 }
 
+static void
+pidgin_themes_destroy_smiley_theme(struct smiley_theme *theme)
+{
+	pidgin_themes_destroy_smiley_theme_smileys(theme);
+
+	g_free(theme->name);
+	g_free(theme->desc);
+	g_free(theme->author);
+	g_free(theme->icon);
+	g_free(theme->path);
+	g_free(theme);
+}
+
+static void pidgin_themes_remove_theme_dir(const char *theme_dir_name)
+{
+	GString *str = NULL;
+	const char *file_name = NULL;
+	GDir *theme_dir = NULL;
+
+	if ((theme_dir = g_dir_open(theme_dir_name, 0, NULL)) != NULL) {
+		if ((str = g_string_new(theme_dir_name)) != NULL) {
+			while ((file_name = g_dir_read_name(theme_dir)) != NULL) {
+				g_string_printf(str, "%s%s%s", theme_dir_name, G_DIR_SEPARATOR_S, file_name);
+				g_unlink(str->str);
+			}
+			g_string_free(str, TRUE);
+		}
+		g_dir_close(theme_dir);
+		g_rmdir(theme_dir_name);
+	}
+}
+
+void pidgin_themes_remove_smiley_theme(const char *file)
+{
+	char *theme_dir = NULL, *last_slash = NULL;
+	g_return_if_fail(NULL != file);
+	
+	if (!g_file_test(file, G_FILE_TEST_EXISTS)) return;
+	if ((theme_dir = g_strdup(file)) == NULL) return ;
+
+	if ((last_slash = g_strrstr(theme_dir, G_DIR_SEPARATOR_S)) != NULL) {
+		GSList *iter = NULL;
+		struct smiley_theme *theme = NULL, *new_theme = NULL;
+
+		*last_slash = 0;
+
+		/* Delete files on disk */
+		pidgin_themes_remove_theme_dir(theme_dir);
+
+		/* Find theme in themes list and remove it */
+		for (iter = smiley_themes ; iter ; iter = iter->next) {
+			theme = ((struct smiley_theme *)(iter->data));
+			if (!strcmp(theme->path, file))
+				break ;
+		}
+		if (iter) {
+			if (theme == current_smiley_theme) {
+				new_theme = ((struct smiley_theme *)(NULL == iter->next ? (smiley_themes == iter ? NULL : smiley_themes->data) : iter->next->data));
+				if (new_theme)
+					purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", new_theme->name);
+				else
+					current_smiley_theme = NULL;
+			}
+			smiley_themes = g_slist_delete_link(smiley_themes, iter);
+
+			/* Destroy theme structure */
+			pidgin_themes_destroy_smiley_theme(theme);
+		}
+	}
+
+	g_free(theme_dir);
+}
+
 void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
 {
 	struct smiley_list *list;
@@ -64,7 +139,7 @@
 }
 
 static void
-pidgin_themes_destroy_smiley_theme(struct smiley_theme *theme)
+pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme)
 {
 	GHashTable *already_freed;
 	struct smiley_list *wer;
@@ -92,6 +167,32 @@
 	g_hash_table_destroy(already_freed);
 }
 
+static void
+pidgin_smiley_themes_remove_non_existing()
+{
+	static struct smiley_theme *theme = NULL;
+	GSList *iter = NULL;
+
+	if (!smiley_themes) return ;
+
+	for (iter = smiley_themes ; iter ; iter = iter->next) {
+		theme = ((struct smiley_theme *)(iter->data));
+		if (!g_file_test(theme->path, G_FILE_TEST_EXISTS)) {
+			if (theme == current_smiley_theme)
+				current_smiley_theme = ((struct smiley_theme *)(NULL == iter->next ? NULL : iter->next->data));
+			pidgin_themes_destroy_smiley_theme(theme);
+			iter->data = NULL;
+		}
+	}
+	/* Remove all elements whose data is NULL */
+	smiley_themes = g_slist_remove_all(smiley_themes, NULL);
+
+	if (!current_smiley_theme && smiley_themes) {
+		struct smiley_theme *smile = g_slist_last(smiley_themes)->data;
+		pidgin_themes_load_smiley_theme(smile->path, TRUE);
+	}
+}
+
 void pidgin_themes_load_smiley_theme(const char *file, gboolean load)
 {
 	FILE *f = g_fopen(file, "r");
@@ -221,14 +322,6 @@
 		purple_debug_error("gtkthemes", "Invalid file format, not loading smiley theme from '%s'\n", file);
 
 		pidgin_themes_destroy_smiley_theme(theme);
-
-		g_free(theme->name);
-		g_free(theme->desc);
-		g_free(theme->author);
-		g_free(theme->icon);
-		g_free(theme->path);
-		g_free(theme);
-
 		return;
 	}
 
@@ -240,7 +333,7 @@
 		GList *cnv;
 
 		if (current_smiley_theme)
-			pidgin_themes_destroy_smiley_theme(current_smiley_theme);
+			pidgin_themes_destroy_smiley_theme_smileys(current_smiley_theme);
 		current_smiley_theme = theme;
 
 		for (cnv = purple_get_conversations(); cnv != NULL; cnv = cnv->next) {
@@ -258,10 +351,12 @@
 {
 	GDir *dir;
 	const gchar *file;
-	gchar *path;
+	gchar *path, *test_path;
 	int l;
+	char* probedirs[3];
 
-	char* probedirs[3];
+	pidgin_smiley_themes_remove_non_existing();
+
 	probedirs[0] = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", NULL);
 	probedirs[1] = g_build_filename(purple_user_dir(), "smileys", NULL);
 	probedirs[2] = 0;
@@ -269,14 +364,18 @@
 		dir = g_dir_open(probedirs[l], 0, NULL);
 		if (dir) {
 			while ((file = g_dir_read_name(dir))) {
-				path = g_build_filename(probedirs[l], file, "theme", NULL);
+				test_path = g_build_filename(probedirs[l], file, NULL);
+				if (g_file_test(test_path, G_FILE_TEST_IS_DIR)) {
+					path = g_build_filename(probedirs[l], file, "theme", NULL);
 
-				/* Here we check to see that the theme has proper syntax.
-				 * We set the second argument to FALSE so that it doesn't load
-				 * the theme yet.
-				 */
-				pidgin_themes_load_smiley_theme(path, FALSE);
-				g_free(path);
+					/* Here we check to see that the theme has proper syntax.
+					 * We set the second argument to FALSE so that it doesn't load
+					 * the theme yet.
+					 */
+					pidgin_themes_load_smiley_theme(path, FALSE);
+					g_free(path);
+				}
+				g_free(test_path);
 			}
 			g_dir_close(dir);
 		} else if (l == 1) {
@@ -284,6 +383,11 @@
 		}
 		g_free(probedirs[l]);
 	}
+
+	if (!current_smiley_theme && smiley_themes) {
+		struct smiley_theme *smile = smiley_themes->data;
+		pidgin_themes_load_smiley_theme(smile->path, TRUE);
+	}
 }
 
 GSList *pidgin_themes_get_proto_smileys(const char *id) {
@@ -333,5 +437,4 @@
 		struct smiley_theme *smile = smiley_themes->data;
 		pidgin_themes_load_smiley_theme(smile->path, TRUE);
 	}
-
 }
--- a/pidgin/gtkthemes.h	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkthemes.h	Sun Jul 01 08:41:01 2007 +0000
@@ -49,5 +49,6 @@
 void pidgin_themes_smiley_themeize(GtkWidget *);
 void pidgin_themes_smiley_theme_probe(void);
 void pidgin_themes_load_smiley_theme(const char *file, gboolean load);
+void pidgin_themes_remove_smiley_theme(const char *file);
 GSList *pidgin_themes_get_proto_smileys(const char *id);
 #endif /* _PIDGINDIALOGS_H_ */
--- a/pidgin/gtkutils.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/gtkutils.c	Sun Jul 01 08:41:01 2007 +0000
@@ -176,7 +176,7 @@
 
 	sw = gtk_scrolled_window_new(NULL, NULL);
 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
-								   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+								   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
 	gtk_widget_show(sw);
 
--- a/pidgin/plugins/spellchk.c	Fri Jun 29 13:52:07 2007 +0000
+++ b/pidgin/plugins/spellchk.c	Sun Jul 01 08:41:01 2007 +0000
@@ -45,9 +45,6 @@
 
 #include <stdio.h>
 #include <string.h>
-#ifndef _WIN32
-#include <strings.h>
-#endif
 #include <sys/types.h>
 #include <sys/stat.h>
 
@@ -1787,19 +1784,19 @@
 
 	while (buf_get_line(ibuf, &buf, &pnt, size)) {
 		if (*buf != '#') {
-			if (!strncasecmp(buf, "BAD ", 4))
+			if (!g_ascii_strncasecmp(buf, "BAD ", 4))
 			{
 				strncpy(bad, buf + 4, 81);
 			}
-			else if(!strncasecmp(buf, "CASE ", 5))
+			else if(!g_ascii_strncasecmp(buf, "CASE ", 5))
 			{
 				case_sensitive = *(buf+5) == '0' ? FALSE : TRUE;
 			}
-			else if(!strncasecmp(buf, "COMPLETE ", 9))
+			else if(!g_ascii_strncasecmp(buf, "COMPLETE ", 9))
 			{
 				complete = *(buf+9) == '0' ? FALSE : TRUE;
 			}
-			else if (!strncasecmp(buf, "GOOD ", 5))
+			else if (!g_ascii_strncasecmp(buf, "GOOD ", 5))
 			{
 				strncpy(good, buf + 5, 255);