changeset 9554:8b2451878e26

[gaim-migrate @ 10387] " This patch adds chat user status icons (voice / halfop / op / founder) to chats There's a screenshot here, showing ops, voices and ignored ops and voices http://nosnilmot.com/gaim/chatusers.png This required some changes in how the core stores the list of users in chats to be able to store the status too, which are detailed below. I also fixed up some memory leaks as I came across them (string values returned by gtk_tree_model_get() not being g_free()'d) and a minor bug in signals-test.c Conversation API: Changed: gaim_conv_chat_add_user() (added flags parameter) gaim_conv_chat_add_users() now (added GList of flags parameter) gaim_conv_chat_get_users() now returns a GList of GaimChatBuddy's gaim_conv_chat_set_users() now expects a GList of GaimChatBuddy's Added: gaim_conv_chat_set_user_flags() gaim_conv_chat_get_user_flags() gaim_conv_chat_find_user() gaim_conv_chat_cb_new() gaim_conv_chat_cb_find() gaim_conv_chat_cb_destroy() gaim_conv_chat_cb_get_name() Conversation UI ops: added: chat_update_user() Signals: Changed: chat-buddy-joining & chat-buddy-joined now include the user's flags Added: chat-buddy-flags for when user's flags change Added: gaim_marshal_VOID__POINTER_POINTER_POINTER_UINT_UINT (required for the new chat-buddy-flags signal) Protocol Plugins: All updated to work with above changes (obviously) User flags support added to IRC, Jabber and SILC New Files: pixmaps/status/default/ voice.svg halfop.svg op.svg founder.svg " --Stu Tomlinson committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Sat, 17 Jul 2004 18:11:12 +0000
parents 8a64666476e6
children a79e03ef63f6
files ChangeLog doc/conversation-signals.dox pixmaps/status/default/Makefile.am pixmaps/status/default/founder.svg pixmaps/status/default/halfop.svg pixmaps/status/default/ignored.svg pixmaps/status/default/op.svg pixmaps/status/default/voice.svg plugins/ChangeLog.API plugins/signals-test.c src/conversation.c src/conversation.h src/gtkconv.c src/gtkconv.h src/protocols/irc/msgs.c src/protocols/jabber/chat.c src/protocols/jabber/presence.c src/protocols/msn/msn.c src/protocols/msn/switchboard.c src/protocols/napster/napster.c src/protocols/novell/novell.c src/protocols/oscar/oscar.c src/protocols/silc/chat.c src/protocols/silc/ops.c src/protocols/toc/toc.c src/protocols/yahoo/yahoochat.c src/protocols/yahoo/ycht.c src/protocols/zephyr/zephyr.c src/signals.c src/signals.h
diffstat 30 files changed, 881 insertions(+), 373 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Jul 17 17:08:24 2004 +0000
+++ b/ChangeLog	Sat Jul 17 18:11:12 2004 +0000
@@ -4,6 +4,7 @@
 	New Features:
 	* The autorecon plugin will somewhat remember state information(Yosef
 	  Radchenko)
+	* Visual display of ops/voice/halfops/so on in Chats (Stu Tomlinson)
 
 	Bug Fixes:
 	* Long buddy lists with irc should cause flooding disconnects less
--- a/doc/conversation-signals.dox	Sat Jul 17 17:08:24 2004 +0000
+++ b/doc/conversation-signals.dox	Sat Jul 17 18:11:12 2004 +0000
@@ -331,22 +331,41 @@
 
  @signaldef chat-buddy-joining
   @signalproto
-void (*chat_buddy_joining)(GaimConversation *conv, const char *name);
+void (*chat_buddy_joining)(GaimConversation *conv, const char *name,
+                           GaimConvChatBuddyFlags flags);
   @endsignalproto
   @signaldesc
    Emitted when a buddy is joining a chat, before the list of
    users in the chat updates to include the new user.
   @param conv The chat conversation.
   @param name The name of the user that is joining the conversation.
+  @param flags The flags of the user that is joining the conversation.
  @endsignaldef
 
  @signaldef chat-buddy-joined
   @signalproto
-void (*chat_buddy_joined)(GaimConversation *conv, const char *name);
+void (*chat_buddy_joined)(GaimConversation *conv, const char *name,
+                          GaimConvChatBuddyFlags flags);
   @endsignalproto
   @signaldesc
    Emitted when a buddy joined a chat, after the users list is updated.
   @param conv The chat conversation.
+  @param name The name of the user that has joined the conversation.
+  @param flags The flags of the user that has joined the conversation.
+ @endsignaldef
+
+ @signaldef chat-buddy-flags
+  @signalproto
+void (*chat_buddy_flags)(GaimConversation *conv, const char *name,
+                         GaimConvChatBuddyFlags oldflags,
+                         GaimConvChatBuddyFlags newflags);
+  @endsignalproto
+  @signaldesc
+   Emitted when a user in a chat changes flags.
+  @param conv The chat conversation.
+  @param name The name of the user.
+  @param oldflags The old flags.
+  @param newflags The new flags.
  @endsignaldef
 
  @signaldef chat-buddy-leaving
--- a/pixmaps/status/default/Makefile.am	Sat Jul 17 17:08:24 2004 +0000
+++ b/pixmaps/status/default/Makefile.am	Sat Jul 17 18:11:12 2004 +0000
@@ -7,9 +7,11 @@
 	dnd.png \
 	extendedaway.png \
 	female.png \
+	founder.svg \
 	freeforchat.png \
 	gadu-gadu.png \
 	game.png \
+	halfop.svg \
 	hiptop.png \
 	icq.png \
 	ignored.svg \
@@ -26,10 +28,12 @@
 	novell.png \
 	occupied.png \
 	offline.png \
+	op.svg \
 	rendezvous.png \
 	secure.png \
 	silc.png \
 	trepia.png \
+	voice.svg \
 	wireless.png \
 	yahoo.png \
 	zephyr.png
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/status/default/founder.svg	Sat Jul 17 18:11:12 2004 +0000
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+   id="svg656"
+   sodipodi:version="0.34"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   width="32pt"
+   height="32pt"
+   xml:space="preserve"
+   sodipodi:docbase="/home/build"
+   sodipodi:docname="/home/build/founder.svg"><defs
+   id="defs658"><linearGradient
+   id="linearGradient665"><stop
+   style="stop-color:#e50000;stop-opacity:1;"
+   offset="0"
+   id="stop666" /><stop
+   style="stop-color:#fffd00;stop-opacity:1;"
+   offset="1"
+   id="stop667" /></linearGradient><linearGradient
+   id="linearGradient660"><stop
+   style="stop-color:#ffff2f;stop-opacity:1;"
+   offset="0"
+   id="stop661" /><stop
+   style="stop-color:#e50000;stop-opacity:1;"
+   offset="1"
+   id="stop662" /></linearGradient><linearGradient
+   xlink:href="#linearGradient665"
+   id="linearGradient663"
+   x1="8.26311464e-8"
+   y1="-1.55637082e-7"
+   x2="1.00000024"
+   y2="-1.55637082e-7"
+   spreadMethod="pad"
+   gradientUnits="objectBoundingBox" /><radialGradient
+   xlink:href="#linearGradient665"
+   id="radialGradient664"
+   cx="0.5"
+   cy="0.5"
+   r="0.5"
+   fx="0.5"
+   fy="0.5" /><radialGradient
+   xlink:href="#linearGradient665"
+   id="radialGradient668"
+   cx="0.42774564"
+   cy="0.47096771"
+   r="0.55007958"
+   fx="0.42774564"
+   fy="0.47096771"
+   spreadMethod="pad" /></defs><sodipodi:namedview
+   id="base"
+   showgrid="false"
+   snaptogrid="false"
+   snaptoguides="false" /><path
+   sodipodi:type="spiral"
+   style="font-size:12;fill:url(#radialGradient664);fill-rule:evenodd;stroke:#000000;stroke-width:1.9759;fill-opacity:1;stroke-dasharray:none;stroke-linejoin:bevel;stroke-linecap:round;stroke-opacity:1;"
+   id="path659"
+   sodipodi:cx="4.48084641"
+   sodipodi:cy="5.90660334"
+   sodipodi:expansion="1"
+   sodipodi:revolution="3"
+   sodipodi:radius="17.6416454"
+   sodipodi:argument="-18.0953979"
+   sodipodi:t0="0"
+   d="M 4.480846 5.906603 C 4.935488 6.582345 4.122899 7.106161 3.47428 6.97811 C 2.10434 6.707657 1.812772 4.978034 2.337832 3.893471 C 3.267143 1.97389 5.798381 1.652043 7.500546 2.692082 C 9.982851 4.208796 10.33764 7.631665 8.766874 9.932869 C 6.682602 12.98637 2.341894 13.37245 -0.551986 11.26414 C -4.181049 8.620212 -4.597599 3.350751 -1.948195 -0.132795 C 1.251495 -4.339882 7.45529 -4.786495 11.52681 -1.593945 C 16.31342 2.159319 16.78989 9.300695 13.0529 13.95914 C 8.747414 19.32625 0.666401 19.83246 -4.578251 15.55017 C -10.52654 10.69335 -11.06243 1.671308 -6.234223 -4.159061 C -0.826688 -10.68901 9.137353 -11.25453 15.55308 -5.879973 C 22.66503 0.077829 23.26017 10.9846 17.33893 17.9854 "
+   transform="matrix(-1.195682,0,0,1.195682,26.97744,14.72193)" /></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/status/default/halfop.svg	Sat Jul 17 18:11:12 2004 +0000
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.0"
+   x="0"
+   y="0"
+   width="40"
+   height="40"
+   id="svg644"
+   xml:space="preserve"><defs
+   id="defs646"><linearGradient
+   id="linearGradient651"><stop
+   style="stop-color:#000000;stop-opacity:1;"
+   offset="0"
+   id="stop652" /><stop
+   style="stop-color:#ffffff;stop-opacity:1;"
+   offset="1"
+   id="stop653" /></linearGradient><radialGradient
+   cx="0.5"
+   cy="0.5"
+   r="0.5"
+   fx="0.5"
+   fy="0.5"
+   id="radialGradient654"
+   xlink:href="#linearGradient651" /><linearGradient
+   x1="-6.36533315e-9"
+   y1="-1.07861e-8"
+   x2="1.00000012"
+   y2="-1.07861e-8"
+   id="linearGradient655"
+   xlink:href="#linearGradient651"
+   gradientUnits="objectBoundingBox"
+   spreadMethod="pad" /></defs><path
+   d="M 20.28064 30.48507 L 8.797826 36.6693 L 10.87388 23.79337 L 1.443943 14.7836 L 14.33122 12.77916 L 19.98601 1.026587 L 20.28064 30.48507 z "
+   transform="translate(2.384186e-6,1.696012)"
+   style="font-size:12;fill:#01ff00;stroke:#000000;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;"
+   id="path710" /></svg>
--- a/pixmaps/status/default/ignored.svg	Sat Jul 17 17:08:24 2004 +0000
+++ b/pixmaps/status/default/ignored.svg	Sat Jul 17 18:11:12 2004 +0000
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
 <svg
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -12,6 +13,9 @@
    id="svg602"
    xml:space="preserve"><defs
    id="defs604" /><path
-   d="M 37.36908 20.13459 C 37.36908 29.5592 29.7289 37.19937 20.30434 37.19937 C 10.8797 37.19937 3.239555 29.5592 3.239555 20.13459 C 3.239555 10.70999 10.8797 3.069842 20.30434 3.069842 C 29.7289 3.069842 37.36908 10.70999 37.36908 20.13459 z M 7.462905 32.42286 L 33.06009 6.825752 "
+   d="M 37.36908 20.13459 C 37.36908 29.5592 29.7289 37.19937 20.30434 37.19937 C 10.8797 37.19937 3.239555 29.5592 3.239555 20.13459 C 3.239555 10.70999 10.8797 3.069842 20.30434 3.069842 C 29.7289 3.069842 37.36908 10.70999 37.36908 20.13459 z "
    style="font-size:12;fill:none;stroke:#000000;stroke-width:3.83957;"
-   id="path631" /></svg>
+   id="path635" /><path
+   d="M 7.462905 32.42286 L 33.06009 6.825752 "
+   style="font-size:12;fill:none;stroke:#000000;stroke-width:7.5;"
+   id="path636" /></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/status/default/op.svg	Sat Jul 17 18:11:12 2004 +0000
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.0"
+   x="0"
+   y="0"
+   width="40"
+   height="40"
+   id="svg644"
+   xml:space="preserve"><defs
+   id="defs646"><linearGradient
+   id="linearGradient651"><stop
+   style="stop-color:#000000;stop-opacity:1;"
+   offset="0"
+   id="stop652" /><stop
+   style="stop-color:#ffffff;stop-opacity:1;"
+   offset="1"
+   id="stop653" /></linearGradient><radialGradient
+   cx="0.5"
+   cy="0.5"
+   r="0.5"
+   fx="0.5"
+   fy="0.5"
+   id="radialGradient654"
+   xlink:href="#linearGradient651" /><linearGradient
+   x1="-6.36533315e-9"
+   y1="-1.07861e-8"
+   x2="1.00000012"
+   y2="-1.07861e-8"
+   id="linearGradient655"
+   xlink:href="#linearGradient651"
+   gradientUnits="objectBoundingBox"
+   spreadMethod="pad" /></defs><polygon
+   points="31.88484,36.4384 20.28064,30.48507 8.797826,36.6693 10.87388,23.79337 1.443943,14.7836 14.33122,12.77916 19.98601,1.026588 25.87473,12.66371 38.79951,14.40999 29.55167,23.60656 31.88484,36.4384 "
+   transform="translate(2.384186e-6,1.696012)"
+   style="font-size:12;fill:#01ff00;stroke:#000000;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;"
+   id="polygon650" /></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/status/default/voice.svg	Sat Jul 17 18:11:12 2004 +0000
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.0"
+   x="0"
+   y="0"
+   width="40"
+   height="40"
+   id="svg669"
+   xml:space="preserve"><defs
+   id="defs671" /><path
+   d="M 7.301596 3.746032 L 7.301596 36.00001 L 37.39683 19.87303 L 7.301596 3.746032 z "
+   style="font-size:12;fill:#ffc600;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linejoin:round;"
+   id="path673" /></svg>
--- a/plugins/ChangeLog.API	Sat Jul 17 17:08:24 2004 +0000
+++ b/plugins/ChangeLog.API	Sat Jul 17 18:11:12 2004 +0000
@@ -1,5 +1,28 @@
 Gaim: The Pimpin' Penguin IM Client that's good for the soul!
 
+version 0.81cvs
+	Conversation API:
+	* Changed: gaim_conv_chat_add_user() (added flags parameter)
+	gaim_conv_chat_add_users() now (added GList of flags parameter)
+	gaim_conv_chat_get_users() now returns a GList of GaimChatBuddy's
+	gaim_conv_chat_set_users() now expects a GList of GaimChatBuddy's
+	* Added: gaim_conv_chat_set_user_flags()
+	gaim_conv_chat_get_user_flags()
+	gaim_conv_chat_find_user()
+	gaim_conv_chat_cb_new()
+	gaim_conv_chat_cb_find()
+	gaim_conv_chat_cb_destroy()
+	gaim_conv_chat_cb_get_name()
+
+	Conversation UI ops:
+	* Added: chat_update_user()
+
+	Signals:
+	* Changed: chat-buddy-joining & chat-buddy-joined now include the user's flags
+	* Added: chat-buddy-flags for when user's flags change
+	gaim_marshal_VOID__POINTER_POINTER_POINTER_UINT_UINT (required for the new
+	chat-buddy-flags signal)
+
 version 0.80 (07/15/2004):
 	Gaim API:
 	* Removed: PRPL numbers : gaim_account_set_protocol(),
@@ -10,7 +33,7 @@
 	* Added: can_receive_file & send_file to the GaimPluginProtocolInfo struct
 
 	Signals:
-	* Changed "chat-invited" to also include the components hash table so 
+	* Changed "chat-invited" to also include the components hash table so
 	  plugins can use serv_join_chat when the signal is emitted.
 	* Added "chat-topic-changed" signal plugins know when a topic is changed.
 
--- a/plugins/signals-test.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/plugins/signals-test.c	Sat Jul 17 18:11:12 2004 +0000
@@ -345,17 +345,27 @@
 }
 
 static void
-chat_buddy_joining_cb(GaimConversation *conv, const char *user, void *data)
+chat_buddy_joining_cb(GaimConversation *conv, const char *user,
+					  GaimConvChatBuddyFlags flags, void *data)
 {
-	gaim_debug_misc("signals test", "chat-buddy-joining (%s, %s)\n",
-					gaim_conversation_get_name(conv), user);
+	gaim_debug_misc("signals test", "chat-buddy-joining (%s, %s, %d)\n",
+					gaim_conversation_get_name(conv), user, flags);
 }
 
 static void
-chat_buddy_joined_cb(GaimConversation *conv, const char *user, void *data)
+chat_buddy_joined_cb(GaimConversation *conv, const char *user,
+					 GaimConvChatBuddyFlags flags, void *data)
 {
-	gaim_debug_misc("signals test", "chat-buddy-joined (%s, %s)\n",
-					gaim_conversation_get_name(conv), user);
+	gaim_debug_misc("signals test", "chat-buddy-joined (%s, %s, %d)\n",
+					gaim_conversation_get_name(conv), user, flags);
+}
+
+static void
+chat_buddy_flags_cb(GaimConversation *conv, const char *user,
+					GaimConvChatBuddyFlags oldflags, GaimConvChatBuddyFlags newflags, void *data)
+{
+	gaim_debug_misc("signals test", "chat-buddy-flags (%s, %s, %d, %d)\n",
+					gaim_conversation_get_name(conv), user, oldflags, newflags);
 }
 
 static void
@@ -376,10 +386,10 @@
 
 static void
 chat_inviting_user_cb(GaimConversation *conv, const char *name,
-					  const char *reason, void *data)
+					  char **reason, void *data)
 {
 	gaim_debug_misc("signals test", "chat-inviting-user (%s, %s, %s)\n",
-					gaim_conversation_get_name(conv), name, reason);
+					gaim_conversation_get_name(conv), name, *reason);
 }
 
 static void
@@ -529,6 +539,8 @@
 						plugin, GAIM_CALLBACK(chat_buddy_joining_cb), NULL);
 	gaim_signal_connect(conv_handle, "chat-buddy-joined",
 						plugin, GAIM_CALLBACK(chat_buddy_joined_cb), NULL);
+	gaim_signal_connect(conv_handle, "chat-buddy-flags",
+						plugin, GAIM_CALLBACK(chat_buddy_flags_cb), NULL);
 	gaim_signal_connect(conv_handle, "chat-buddy-leaving",
 						plugin, GAIM_CALLBACK(chat_buddy_leaving_cb), NULL);
 	gaim_signal_connect(conv_handle, "chat-buddy-left",
--- a/src/conversation.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/conversation.c	Sat Jul 17 18:11:12 2004 +0000
@@ -30,10 +30,6 @@
 #include "signals.h"
 #include "util.h"
 
-/* XXX CORE/UI, waiting for away splittage */
-#include "gtkinternal.h"
-#include "ui.h"
-
 typedef struct
 {
 	char *id;
@@ -56,34 +52,6 @@
 static void ensure_default_funcs(void);
 static void conv_placement_last_created_win(GaimConversation *conv);
 
-static gint
-insertname_compare(gconstpointer one, gconstpointer two)
-{
-	const char *a = (const char *)one;
-	const char *b = (const char *)two;
-
-	if (*a == '@') {
-		if (*b != '@') return -1;
-
-		return g_ascii_strcasecmp(a + 1, b + 1);
-
-	} else if (*a == '%') {
-		if (*b != '%') return -1;
-
-		return g_ascii_strcasecmp(a + 1, b + 1);
-
-	} else if (*a == '+') {
-		if (*b == '@') return  1;
-		if (*b != '+') return -1;
-
-		return g_ascii_strcasecmp(a + 1, b + 1);
-
-	} else if (*b == '@' || *b == '%' || *b == '+')
-		return 1;
-
-	return g_ascii_strcasecmp(a, b);
-}
-
 static gboolean
 find_nick(const char *nick, const char *message)
 {
@@ -1927,10 +1895,12 @@
 }
 
 void
-gaim_conv_chat_add_user(GaimConvChat *chat, const char *user, const char *extra_msg)
+gaim_conv_chat_add_user(GaimConvChat *chat, const char *user, const char *extra_msg,
+						GaimConvChatBuddyFlags flags)
 {
 	GaimConversation *conv;
 	GaimConversationUiOps *ops;
+	GaimConvChatBuddy *cb;
 	char tmp[BUF_LONG];
 
 	g_return_if_fail(chat != NULL);
@@ -1942,9 +1912,10 @@
 	gaim_signal_emit(gaim_conversations_get_handle(),
 					 "chat-buddy-joining", conv, user);
 
+	cb = gaim_conv_chat_cb_new(user, flags);
+
 	gaim_conv_chat_set_users(chat,
-		g_list_insert_sorted(gaim_conv_chat_get_users(chat), g_strdup(user),
-							 insertname_compare));
+		g_list_append(gaim_conv_chat_get_users(chat), cb));
 
 	if (ops != NULL && ops->chat_add_user != NULL)
 		ops->chat_add_user(conv, user);
@@ -1963,11 +1934,12 @@
 }
 
 void
-gaim_conv_chat_add_users(GaimConvChat *chat, GList *users)
+gaim_conv_chat_add_users(GaimConvChat *chat, GList *users, GList *flags)
 {
 	GaimConversation *conv;
 	GaimConversationUiOps *ops;
-	GList *l;
+	GaimConvChatBuddy *cb;
+	GList *ul, *fl;
 
 	g_return_if_fail(chat  != NULL);
 	g_return_if_fail(users != NULL);
@@ -1975,19 +1947,23 @@
 	conv = gaim_conv_chat_get_conversation(chat);
 	ops  = gaim_conversation_get_ui_ops(conv);
 
-	for (l = users; l != NULL; l = l->next) {
-		const char *user = (const char *)l->data;
+	ul = users;
+	fl = flags;
+	while ((ul != NULL) && (fl != NULL)) {
+		const char *user = (const char *)ul->data;
+		GaimConvChatBuddyFlags f = GPOINTER_TO_INT(fl->data);
 
 		gaim_signal_emit(gaim_conversations_get_handle(),
-						 "chat-buddy-joining", conv, user);
-
+						 "chat-buddy-joining", conv, user, f);
+
+		cb = gaim_conv_chat_cb_new(user, f);
 		gaim_conv_chat_set_users(chat,
-				g_list_insert_sorted(gaim_conv_chat_get_users(chat),
-									 g_strdup((char *)l->data),
-									 insertname_compare));
+				g_list_append(gaim_conv_chat_get_users(chat), cb));
 
 		gaim_signal_emit(gaim_conversations_get_handle(),
-						 "chat-buddy-joined", conv, user);
+						 "chat-buddy-joined", conv, user, f);
+		ul = ul->next;
+		fl = fl->next;
 	}
 
 	if (ops != NULL && ops->chat_add_users != NULL)
@@ -2000,8 +1976,9 @@
 {
 	GaimConversation *conv;
 	GaimConversationUiOps *ops;
+	GaimConvChatBuddy *cb;
+	GaimConvChatBuddyFlags flags;
 	char tmp[BUF_LONG];
-	GList *names;
 	gboolean its_me = FALSE;
 
 	g_return_if_fail(chat != NULL);
@@ -2011,22 +1988,20 @@
 	conv = gaim_conv_chat_get_conversation(chat);
 	ops  = gaim_conversation_get_ui_ops(conv);
 
+	flags = gaim_conv_chat_user_get_flags(chat, old_user);
+	cb = gaim_conv_chat_cb_new(new_user, flags);
 	gaim_conv_chat_set_users(chat,
-		g_list_insert_sorted(gaim_conv_chat_get_users(chat), g_strdup(new_user),
-							 insertname_compare));
+		g_list_append(gaim_conv_chat_get_users(chat), cb));
 
 	if (ops != NULL && ops->chat_rename_user != NULL)
 		ops->chat_rename_user(conv, old_user, new_user);
 
-	for (names = gaim_conv_chat_get_users(chat);
-		 names != NULL;
-		 names = names->next) {
-
-		if (!gaim_utf8_strcasecmp((char *)names->data, old_user)) {
-			gaim_conv_chat_set_users(chat,
-					g_list_remove(gaim_conv_chat_get_users(chat), names->data));
-			break;
-		}
+	cb = gaim_conv_chat_cb_find(chat, old_user);
+
+	if (cb) {
+		gaim_conv_chat_set_users(chat,
+				g_list_remove(gaim_conv_chat_get_users(chat), cb));
+		gaim_conv_chat_cb_destroy(cb);
 	}
 
 	if (gaim_conv_chat_is_user_ignored(chat, old_user)) {
@@ -2059,8 +2034,8 @@
 {
 	GaimConversation *conv;
 	GaimConversationUiOps *ops;
+	GaimConvChatBuddy *cb;
 	char tmp[BUF_LONG];
-	GList *names;
 
 	g_return_if_fail(chat != NULL);
 	g_return_if_fail(user != NULL);
@@ -2074,15 +2049,12 @@
 	if (ops != NULL && ops->chat_remove_user != NULL)
 		ops->chat_remove_user(conv, user);
 
-	for (names = gaim_conv_chat_get_users(chat);
-		 names != NULL;
-		 names = names->next) {
-
-		if (!gaim_utf8_strcasecmp((char *)names->data, user)) {
-			gaim_conv_chat_set_users(chat,
-					g_list_remove(gaim_conv_chat_get_users(chat), names->data));
-			break;
-		}
+	cb = gaim_conv_chat_cb_find(chat, user);
+
+	if (cb) {
+		gaim_conv_chat_set_users(chat,
+				g_list_remove(gaim_conv_chat_get_users(chat), cb));
+		gaim_conv_chat_cb_destroy(cb);
 	}
 
 	/* NOTE: Don't remove them from ignored in case they re-enter. */
@@ -2104,8 +2076,9 @@
 {
 	GaimConversation *conv;
 	GaimConversationUiOps *ops;
+	GaimConvChatBuddy *cb;
 	char tmp[BUF_LONG];
-	GList *names, *l;
+	GList *l;
 
 	g_return_if_fail(chat  != NULL);
 	g_return_if_fail(users != NULL);
@@ -2126,17 +2099,12 @@
 	for (l = users; l != NULL; l = l->next) {
 		const char *user = (const char *)l->data;
 
-		for (names = gaim_conv_chat_get_users(chat);
-			 names != NULL;
-			 names = names->next) {
-
-			if (!gaim_utf8_strcasecmp((char *)names->data, user))
-			{
-				gaim_conv_chat_set_users(chat,
-					g_list_remove(gaim_conv_chat_get_users(chat), names->data));
-
-				break;
-			}
+		cb = gaim_conv_chat_cb_find(chat, user);
+
+		if (cb) {
+			gaim_conv_chat_set_users(chat,
+					g_list_remove(gaim_conv_chat_get_users(chat), cb));
+			gaim_conv_chat_cb_destroy(cb);
 		}
 
 		gaim_signal_emit(gaim_conversations_get_handle(), "chat-buddy-left",
@@ -2176,8 +2144,8 @@
 {
 	GaimConversation *conv;
 	GaimConversationUiOps *ops;
-	GList *users;
-	GList *l, *l_next;
+	GList *users, *names = NULL;
+	GList *l;
 
 	g_return_if_fail(chat != NULL);
 
@@ -2185,27 +2153,90 @@
 	ops   = gaim_conversation_get_ui_ops(conv);
 	users = gaim_conv_chat_get_users(chat);
 
-	if (ops != NULL && ops->chat_remove_users != NULL)
-		ops->chat_remove_users(conv, users);
-
-	for (l = users; l != NULL; l = l_next)
+	if (ops != NULL && ops->chat_remove_users != NULL) {
+		for (l = users; l; l = l->next) {
+			GaimConvChatBuddy *cb = l->data;
+			names = g_list_append(names, cb->name);
+		}
+		ops->chat_remove_users(conv, names);
+		g_list_free(names);
+	}
+
+	for (l = users; l; l = l->next)
 	{
-		char *user = (char *)l->data;
-
-		l_next = l->next;
+		GaimConvChatBuddy *cb = l->data;
 
 		gaim_signal_emit(gaim_conversations_get_handle(),
-						 "chat-buddy-leaving", conv, user, NULL);
+						 "chat-buddy-leaving", conv, cb->name, NULL);
 		gaim_signal_emit(gaim_conversations_get_handle(),
-						 "chat-buddy-left", conv, user, NULL);
-
-		g_free(user);
+						 "chat-buddy-left", conv, cb->name, NULL);
+
+		gaim_conv_chat_cb_destroy(cb);
 	}
 
 	g_list_free(users);
 	gaim_conv_chat_set_users(chat, NULL);
 }
 
+
+gboolean
+gaim_conv_chat_find_user(GaimConvChat *chat, const char *user)
+{
+	g_return_val_if_fail(chat != NULL, FALSE);
+	g_return_val_if_fail(user != NULL, FALSE);
+
+	return (gaim_conv_chat_cb_find(chat, user) != NULL);
+}
+
+void
+gaim_conv_chat_user_set_flags(GaimConvChat *chat, const char *user,
+							  GaimConvChatBuddyFlags flags)
+{
+	GaimConversation *conv;
+	GaimConversationUiOps *ops;
+	GaimConvChatBuddy *cb;
+	GaimConvChatBuddyFlags oldflags;
+
+	g_return_if_fail(chat != NULL);
+	g_return_if_fail(user != NULL);
+
+	cb = gaim_conv_chat_cb_find(chat, user);
+
+	if (!cb)
+		return;
+
+	if (flags == cb->flags)
+		return;
+
+	oldflags = cb->flags;
+	cb->flags = flags;
+
+	conv = gaim_conv_chat_get_conversation(chat);
+	ops = gaim_conversation_get_ui_ops(conv);
+
+	if (ops != NULL && ops->chat_update_user != NULL)
+			ops->chat_update_user(conv, user);
+
+	gaim_signal_emit(gaim_conversations_get_handle(),
+					 "chat-buddy-flags", conv, user, oldflags, flags);
+}
+
+GaimConvChatBuddyFlags
+gaim_conv_chat_user_get_flags(GaimConvChat *chat, const char *user)
+{
+	GaimConvChatBuddy *cb;
+
+	g_return_val_if_fail(chat != NULL, 0);
+	g_return_val_if_fail(user != NULL, 0);
+
+	cb = gaim_conv_chat_cb_find(chat, user);
+
+	if (!cb)
+		return GAIM_CBFLAGS_NONE;
+
+	return cb->flags;
+}
+
 void gaim_conv_chat_set_nick(GaimConvChat *chat, const char *nick) {
 	g_return_if_fail(chat != NULL);
 
@@ -2254,6 +2285,58 @@
 	return chat->left;
 }
 
+GaimConvChatBuddy *
+gaim_conv_chat_cb_new(const char *name, GaimConvChatBuddyFlags flags)
+{
+	GaimConvChatBuddy *cb;
+
+	g_return_val_if_fail(name != NULL, NULL);
+
+	cb = g_new0(GaimConvChatBuddy, 1);
+	cb->name = g_strdup(name);
+	cb->flags = flags;
+
+	return cb;
+}
+
+GaimConvChatBuddy *
+gaim_conv_chat_cb_find(GaimConvChat *chat, const char *name)
+{
+	GList *l;
+	GaimConvChatBuddy *cb = NULL;
+
+	g_return_val_if_fail(chat != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	for (l = gaim_conv_chat_get_users(chat); l; l = l->next) {
+		cb = l->data;
+		if (!gaim_utf8_strcasecmp(cb->name, name))
+			return cb;
+	}
+
+	return NULL;
+}
+
+void
+gaim_conv_chat_cb_destroy(GaimConvChatBuddy *cb)
+{
+	g_return_if_fail(cb != NULL);
+
+	if (cb->name)
+		g_free(cb->name);
+	cb->name = NULL;
+	cb->flags = 0;
+	g_free(cb);
+}
+
+const char *
+gaim_conv_chat_cb_get_name(GaimConvChatBuddy *cb)
+{
+	g_return_val_if_fail(cb != NULL, NULL);
+
+	return cb->name;
+}
+
 /**************************************************************************
  * Conversation placement functions
  **************************************************************************/
@@ -2798,16 +2881,26 @@
 										GAIM_SUBTYPE_CONVERSATION));
 
 	gaim_signal_register(handle, "chat-buddy-joining",
-						 gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+						 gaim_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_CONVERSATION),
+						 gaim_value_new(GAIM_TYPE_STRING),
+						 gaim_value_new(GAIM_TYPE_UINT));
+
+	gaim_signal_register(handle, "chat-buddy-joined",
+						 gaim_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
 						 gaim_value_new(GAIM_TYPE_SUBTYPE,
 										GAIM_SUBTYPE_CONVERSATION),
-						 gaim_value_new(GAIM_TYPE_STRING));
-
-	gaim_signal_register(handle, "chat-buddy-joined",
-						 gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+						 gaim_value_new(GAIM_TYPE_STRING),
+						 gaim_value_new(GAIM_TYPE_UINT));
+
+	gaim_signal_register(handle, "chat-buddy-flags",
+						 gaim_marshal_VOID__POINTER_POINTER_UINT_UINT, NULL, 4,
 						 gaim_value_new(GAIM_TYPE_SUBTYPE,
 										GAIM_SUBTYPE_CONVERSATION),
-						 gaim_value_new(GAIM_TYPE_STRING));
+						 gaim_value_new(GAIM_TYPE_STRING),
+						 gaim_value_new(GAIM_TYPE_UINT),
+						 gaim_value_new(GAIM_TYPE_UINT));
 
 	gaim_signal_register(handle, "chat-buddy-leaving",
 						 gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
--- a/src/conversation.h	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/conversation.h	Sat Jul 17 18:11:12 2004 +0000
@@ -37,6 +37,7 @@
 typedef struct _GaimConversation      GaimConversation;
 typedef struct _GaimConvIm            GaimConvIm;
 typedef struct _GaimConvChat          GaimConvChat;
+typedef struct _GaimConvChatBuddy     GaimConvChatBuddy;
 
 /**
  * A type of conversation.
@@ -118,6 +119,18 @@
 	GAIM_MESSAGE_ERROR     = 0x0200  /**< Error message.           */
 } GaimMessageFlags;
 
+/**
+ * Flags applicable to users in Chats.
+ */
+typedef enum
+{
+	GAIM_CBFLAGS_NONE          = 0x0000, /**< No flags                     */
+	GAIM_CBFLAGS_VOICE         = 0x0001, /**< Voiced user or "Participant" */
+	GAIM_CBFLAGS_HALFOP        = 0x0002, /**< Half-op                      */
+	GAIM_CBFLAGS_OP            = 0x0004, /**< Channel Op or Moderator      */
+	GAIM_CBFLAGS_FOUNDER       = 0x0008  /**< Channel Founder              */
+} GaimConvChatBuddyFlags;
+
 #include "account.h"
 #include "log.h"
 #include "buddyicon.h"
@@ -175,6 +188,7 @@
 	                         const char *old_name, const char *new_name);
 	void (*chat_remove_user)(GaimConversation *conv, const char *user);
 	void (*chat_remove_users)(GaimConversation *conv, GList *users);
+	void (*chat_update_user)(GaimConversation *conv, const char *user);
 
 	void (*update_progress)(GaimConversation *conv, float percent);
 
@@ -231,6 +245,15 @@
 };
 
 /**
+ * Data for "Chat Buddies"
+ */
+struct _GaimConvChatBuddy
+{
+	char *name;                      /**< The name                      */
+	GaimConvChatBuddyFlags flags;    /**< Flags (ops, voice etc.)       */
+};
+
+/**
  * A core representation of a conversation between two or more people.
  *
  * The conversation can be an IM or a chat. Each conversation is kept
@@ -1150,9 +1173,10 @@
  * @param chat      The chat.
  * @param user      The user to add.
  * @param extra_msg An extra message to display with the join message.
+ * @param flags     The users flags
  */
 void gaim_conv_chat_add_user(GaimConvChat *chat, const char *user,
-							 const char *extra_msg);
+							 const char *extra_msg, GaimConvChatBuddyFlags flags);
 
 /**
  * Adds a list of users to a chat.
@@ -1162,8 +1186,9 @@
  *
  * @param chat      The chat.
  * @param users     The list of users to add.
+ * @param flags     The list of flags for each user.
  */
-void gaim_conv_chat_add_users(GaimConvChat *chat, GList *users);
+void gaim_conv_chat_add_users(GaimConvChat *chat, GList *users, GList *flags);
 
 /**
  * Renames a user in a chat.
@@ -1198,6 +1223,37 @@
 								 const char *reason);
 
 /**
+ * Finds a user in a chat
+ *
+ * @param chat   The chat.
+ * @param user   The user to look for.
+ *
+ * @return TRUE if the user is in the chat, FALSE if not
+ */
+gboolean gaim_conv_chat_find_user(GaimConvChat *chat, const char *user);
+
+/**
+ * Set a users flags in a chat
+ *
+ * @param chat   The chat.
+ * @param user   The user to update.
+ * @param flags  The new flags.
+ */
+void gaim_conv_chat_user_set_flags(GaimConvChat *chat, const char *user,
+								   GaimConvChatBuddyFlags flags);
+
+/**
+ * Get the flags for a user in a chat
+ *
+ * @param chat   The chat.
+ * @param user   The user to find the flags for
+ *
+ * @return The flags for the user
+ */
+GaimConvChatBuddyFlags gaim_conv_chat_user_get_flags(GaimConvChat *chat,
+													 const char *user);
+
+/**
  * Clears all users from a chat.
  *
  * @param chat The chat.
@@ -1249,6 +1305,41 @@
  */
 gboolean gaim_conv_chat_has_left(GaimConvChat *chat);
 
+/**
+ * Creates a new chat buddy
+ *
+ * @param name The name.
+ * @param flags The flags.
+ *
+ * @return The new chat buddy
+ */
+GaimConvChatBuddy *gaim_conv_chat_cb_new(const char *name,
+										GaimConvChatBuddyFlags flags);
+
+/**
+ * Find a chat buddy in a chat
+ *
+ * @param chat The chat.
+ * @param name The name of the chat buddy to find.
+ */
+GaimConvChatBuddy *gaim_conv_chat_cb_find(GaimConvChat *chat, const char *name);
+
+/**
+ * Get the name of a chat buddy
+ *
+ * @param cb    The chat buddy.
+ *
+ * @return The name of the chat buddy.
+ */
+const char *gaim_conv_chat_cb_get_name(GaimConvChatBuddy *cb);
+
+/**
+ * Destroys a chat buddy
+ *
+ * @param cb The chat buddy to destroy
+ */
+void gaim_conv_chat_cb_destroy(GaimConvChatBuddy *cb);
+
 /*@}*/
 
 /**************************************************************************/
--- a/src/gtkconv.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/gtkconv.c	Sat Jul 17 18:11:12 2004 +0000
@@ -126,7 +126,7 @@
 static void got_typing_keypress(GaimConversation *conv, gboolean first);
 static GList *generate_invite_user_names(GaimConnection *gc);
 static void add_chat_buddy_common(GaimConversation *conv,
-								  const char *name, int pos);
+								  const char *name);
 static void tab_complete(GaimConversation *conv);
 static void update_typing_icon(GaimConversation *conv);
 static gboolean update_send_as_selection(GaimConvWindow *win);
@@ -591,7 +591,7 @@
 		GtkTreeIter iter;
 		GtkTreeModel *model;
 		GtkTreeSelection *sel;
-		const char *name;
+		char *name;
 
 		gtkchat = gtkconv->u.chat;
 
@@ -604,6 +604,7 @@
 			return;
 
 		chat_do_info(conv, name);
+		g_free(name);
 	}
 }
 
@@ -1196,7 +1197,7 @@
 	GtkTreeIter iter;
 	GtkTreeModel *model;
 	GtkTreeSelection *sel;
-	const char *name;
+	char *name;
 
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
 	gtkchat = gtkconv->u.chat;
@@ -1210,6 +1211,7 @@
 		return;
 
 	chat_do_im(conv, name);
+	g_free(name);
 }
 
 static void
@@ -1222,7 +1224,6 @@
 	GtkTreeModel *model;
 	GtkTreeSelection *sel;
 	const char *name;
-	int pos;
 
 	chat    = GAIM_CONV_CHAT(conv);
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
@@ -1238,14 +1239,12 @@
 	else
 		return;
 
-	pos = g_list_index(gaim_conv_chat_get_users(chat), name);
-
 	if (gaim_conv_chat_is_user_ignored(chat, name))
 		gaim_conv_chat_unignore(chat, name);
 	else
 		gaim_conv_chat_ignore(chat, name);
 
-	add_chat_buddy_common(conv, name, pos);
+	add_chat_buddy_common(conv, name);
 }
 
 static void
@@ -1480,6 +1479,7 @@
 
 	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
 		chat_do_im(conv, who);
+		g_free(who);
 	} else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
 		GtkWidget *menu = create_chat_menu (conv, who, prpl_info, gc);
 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
@@ -3248,23 +3248,41 @@
 }
 
 static GdkPixbuf *
-get_chat_user_status_icon(GaimConvChat *chat, const char *name)
+get_chat_buddy_status_icon(GaimConvChat *chat, const char *name, GaimConvChatBuddyFlags flags)
 {
-	GdkPixbuf *pixbuf, *scale;
+	GdkPixbuf *pixbuf, *scale, *scale2;
 	char *filename;
-
-	/* Eventually this should compose the pixbuf based on user status (op, voice, etc.)
-	 * and ignored status and any other fancy things we want to show. For now though,
-	 * it's just ignored users that have the privilege of an icon */
-
-	if(gaim_conv_chat_is_user_ignored(chat, name)) {
-		filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "ignored.svg", NULL);
+	char *image = NULL;
+
+	if (flags & GAIM_CBFLAGS_FOUNDER) {
+		image = g_strdup("founder.svg");
+	} else if (flags & GAIM_CBFLAGS_OP) {
+		image = g_strdup("op.svg");
+	} else if (flags & GAIM_CBFLAGS_HALFOP) {
+		image = g_strdup("halfop.svg");
+	} else if (flags & GAIM_CBFLAGS_VOICE) {
+		image = g_strdup("voice.svg");
+	} else if ((!flags) && gaim_conv_chat_is_user_ignored(chat, name)) {
+		image = g_strdup("ignored.svg");
+	}
+	if (image) {
+		filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", image, NULL);
+		g_free(image);
 		pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
 		g_free(filename);
 		if (!pixbuf)
 			return NULL;
 		scale = gdk_pixbuf_scale_simple(pixbuf, 15, 15, GDK_INTERP_BILINEAR);
 		g_object_unref(pixbuf);
+		if (flags && gaim_conv_chat_is_user_ignored(chat, name)) {
+			filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "ignored.svg", NULL);
+			pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+			g_free(filename);
+			scale2 = gdk_pixbuf_scale_simple(pixbuf, 15, 15, GDK_INTERP_BILINEAR);
+			g_object_unref(pixbuf);
+			gdk_pixbuf_composite(scale2, scale, 0, 0, 15, 15, 0, 0, 1, 1, GDK_INTERP_BILINEAR, 192);
+			g_object_unref(scale2);
+		}
 		return scale;
 	}
 
@@ -3272,11 +3290,12 @@
 }
 
 static void
-add_chat_buddy_common(GaimConversation *conv, const char *name, int pos)
+add_chat_buddy_common(GaimConversation *conv, const char *name)
 {
 	GaimGtkConversation *gtkconv;
 	GaimGtkChatPane *gtkchat;
 	GaimConvChat *chat;
+	GaimConvChatBuddyFlags flags;
 	GtkTreeIter iter;
 	GtkListStore *ls;
 	GdkPixbuf *pixbuf;
@@ -3287,13 +3306,16 @@
 
 	ls = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)));
 
-	pixbuf = get_chat_user_status_icon(chat, name);
+	flags = gaim_conv_chat_user_get_flags(chat, name);
+	pixbuf = get_chat_buddy_status_icon(chat, name, flags);
 
 	gtk_list_store_append(ls, &iter);
 	gtk_list_store_set(ls, &iter, CHAT_USERS_ICON_COLUMN, pixbuf,
-					   CHAT_USERS_NAME_COLUMN, name, -1);
+					   CHAT_USERS_NAME_COLUMN, name, CHAT_USERS_FLAGS_COLUMN, flags, -1);
 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls), CHAT_USERS_NAME_COLUMN,
 										 GTK_SORT_ASCENDING);
+	if (pixbuf)
+		g_object_unref(pixbuf);
 }
 
 static void
@@ -3356,9 +3378,9 @@
 		 nicks != NULL;
 		 nicks = nicks->next) {
 
-		char *nick = nicks->data;
-
-		strncpy(nick_partial, nick, strlen(entered));
+		GaimConvChatBuddy *cb = nicks->data;
+
+		strncpy(nick_partial, cb->name, strlen(entered));
 		nick_partial[strlen(entered)] = '\0';
 		if(gaim_utf8_strcasecmp(nick_partial, entered))
 			continue;
@@ -3370,11 +3392,11 @@
 			 * this will only get called once, since from now
 			 * on most_matched is >= 0
 			 */
-			most_matched = strlen(nick);
-			partial = g_strdup(nick);
+			most_matched = strlen(cb->name);
+			partial = g_strdup(cb->name);
 		}
 		else if (most_matched) {
-			char *tmp = g_strdup(nick);
+			char *tmp = g_strdup(cb->name);
 
 			while (gaim_utf8_strcasecmp(tmp, partial)) {
 				partial[most_matched] = '\0';
@@ -3387,7 +3409,7 @@
 			g_free(tmp);
 		}
 
-		matches = g_list_append(matches, nick);
+		matches = g_list_append(matches, cb->name);
 	}
 
 	g_free(nick_partial);
@@ -3662,7 +3684,7 @@
 						 _("Block the user"), NULL);
 	gtk_box_pack_start(GTK_BOX(parent), gtkim->block, FALSE, FALSE, 0);
 
-	/* Block button */
+	/* Send File button */
 	gtkim->send_file = gaim_gtk_change_text(_("Send File"), gtkim->send_file,
 										GAIM_STOCK_FILE_TRANSFER, GAIM_CONV_IM);
 	gtk_tooltips_set_tip(gtkconv->tooltips, gtkim->send_file,
@@ -3852,6 +3874,32 @@
 			new_topic);
 }
 
+static gint
+sort_chat_users(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata)
+{
+	GaimConvChatBuddyFlags f1 = 0, f2 = 0;
+	char *user1 = NULL, *user2 = NULL;
+	gint ret = 0;
+
+	gtk_tree_model_get(model, a, CHAT_USERS_NAME_COLUMN, &user1, CHAT_USERS_FLAGS_COLUMN, &f1, -1);
+	gtk_tree_model_get(model, b, CHAT_USERS_NAME_COLUMN, &user2, CHAT_USERS_FLAGS_COLUMN, &f2, -1);
+
+	if (user1 == NULL || user2 == NULL) {
+		if (!(user1 == NULL && user2 == NULL))
+			ret = (user1 == NULL) ? -1: 1;
+	} else if (f1 != f2) {
+		/* sort more important users first */
+		ret = (f1 > f2) ? -1 : 1;
+	} else {
+		ret = g_utf8_collate(user1, user2);
+	}
+
+	g_free(user1);
+	g_free(user2);
+
+	return ret;
+}
+
 static GtkWidget *
 setup_chat_pane(GaimConversation *conv)
 {
@@ -3969,7 +4017,10 @@
 	gtk_box_pack_start(GTK_BOX(lbox), sw, TRUE, TRUE, 0);
 	gtk_widget_show(sw);
 
-	ls = gtk_list_store_new(CHAT_USERS_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
+	ls = gtk_list_store_new(CHAT_USERS_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
+							G_TYPE_INT);
+	gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls), CHAT_USERS_NAME_COLUMN,
+									sort_chat_users, NULL, NULL);
 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls), CHAT_USERS_NAME_COLUMN,
 										 GTK_SORT_ASCENDING);
 
@@ -3979,6 +4030,8 @@
 
 	col = gtk_tree_view_column_new_with_attributes(NULL, rend,
 												   "pixbuf", CHAT_USERS_ICON_COLUMN, NULL);
+	gtk_tree_view_column_set_clickable(GTK_TREE_VIEW_COLUMN(col), TRUE);
+
 	gtk_tree_view_append_column(GTK_TREE_VIEW(list), col);
 
 	g_signal_connect(G_OBJECT(list), "button_press_event",
@@ -5016,11 +5069,11 @@
 				   mdate, message);
 		else
 			g_snprintf(buf, BUF_LONG, "<FONT COLOR=\"#ff0000\"><B>%s</B></FONT>", message);
-		
+
 		g_snprintf(buf2, sizeof(buf2),
 			   "<FONT %s><FONT SIZE=\"2\"><!--(%s) --></FONT><B>%s</B></FONT>",
 			   sml_attrib, mdate, message);
-		
+
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, 0);
 
 		/* Add the message to a conversations scrollback buffer */
@@ -5157,7 +5210,6 @@
 	GaimGtkChatPane *gtkchat;
 	char tmp[BUF_LONG];
 	int num_users;
-	int pos;
 
 	chat    = GAIM_CONV_CHAT(conv);
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
@@ -5175,9 +5227,7 @@
 	if (gtkconv->make_sound)
 		gaim_sound_play_event(GAIM_SOUND_CHAT_JOIN);
 
-	pos = g_list_index(gaim_conv_chat_get_users(chat), user);
-
-	add_chat_buddy_common(conv, user, pos);
+	add_chat_buddy_common(conv, user);
 }
 
 static void
@@ -5189,7 +5239,6 @@
 	GList *l;
 	char tmp[BUF_LONG];
 	int num_users;
-	int pos;
 
 	chat    = GAIM_CONV_CHAT(conv);
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
@@ -5205,9 +5254,7 @@
 	gtk_label_set_text(GTK_LABEL(gtkchat->count), tmp);
 
 	for (l = users; l != NULL; l = l->next) {
-		pos = g_list_index(gaim_conv_chat_get_users(chat), (char *)l->data);
-
-		add_chat_buddy_common(conv, (char *)l->data, pos);
+		add_chat_buddy_common(conv, (char *)l->data);
 	}
 }
 
@@ -5220,51 +5267,37 @@
 	GaimGtkChatPane *gtkchat;
 	GtkTreeIter iter;
 	GtkTreeModel *model;
-	GList *names;
-	int pos;
 	int f = 1;
 
 	chat    = GAIM_CONV_CHAT(conv);
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
 	gtkchat = gtkconv->u.chat;
 
-	for (names = gaim_conv_chat_get_users(chat);
-		 names != NULL;
-		 names = names->next) {
-
-		char *u = (char *)names->data;
-
-		if (!gaim_utf8_strcasecmp(u, old_name)) {
-			model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
-
-			if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
-				break;
-
-			while (f != 0) {
-				char *val;
-
-				gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
-
-				if (!gaim_utf8_strcasecmp(old_name, val)) {
-					gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
-					break;
-				}
-
-				f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
-
-				g_free(val);
-			}
-
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
+
+	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
+		return;
+
+	while (f != 0) {
+		char *val;
+
+		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
+
+		if (!gaim_utf8_strcasecmp(old_name, val)) {
+			gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+			g_free(val);
 			break;
 		}
+
+		f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
+
+		g_free(val);
 	}
 
-	if (!names)
+	if (!gaim_conv_chat_find_user(chat, old_name))
 		return;
 
-	pos = g_list_index(gaim_conv_chat_get_users(chat), new_name);
-
-	add_chat_buddy_common(conv, new_name, pos);
+	add_chat_buddy_common(conv, new_name);
 }
 
 static void
@@ -5275,7 +5308,6 @@
 	GaimGtkChatPane *gtkchat;
 	GtkTreeIter iter;
 	GtkTreeModel *model;
-	GList *names;
 	char tmp[BUF_LONG];
 	int num_users;
 	int f = 1;
@@ -5286,41 +5318,33 @@
 
 	num_users = g_list_length(gaim_conv_chat_get_users(chat)) - 1;
 
-	for (names = gaim_conv_chat_get_users(chat);
-		 names != NULL;
-		 names = names->next) {
-
-		char *u = (char *)names->data;
-
-		if (!gaim_utf8_strcasecmp(u, user)) {
-			model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
-
-			if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
-				break;
-
-			while (f != 0) {
-				char *val;
-
-				gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
-
-				if (!gaim_utf8_strcasecmp(user, val))
-					gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
-
-				f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
-
-				g_free(val);
-			}
-
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
+
+	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
+		return;
+
+	while (f != 0) {
+		char *val;
+
+		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
+
+		if (!gaim_utf8_strcasecmp(user, val)) {
+			gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+			g_free(val);
 			break;
 		}
+
+		f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
+
+		g_free(val);
 	}
 
-	if (names == NULL)
+	if (!gaim_conv_chat_find_user(chat, user))
 		return;
 
 	g_snprintf(tmp, sizeof(tmp),
-			   ngettext("%d person in room", "%d people in room",
-						num_users), num_users);
+			ngettext("%d person in room", "%d people in room",
+				num_users), num_users);
 
 	gtk_label_set_text(GTK_LABEL(gtkchat->count), tmp);
 
@@ -5336,7 +5360,6 @@
 	GaimGtkChatPane *gtkchat;
 	GtkTreeIter iter;
 	GtkTreeModel *model;
-	GList *names = NULL;
 	GList *l;
 	char tmp[BUF_LONG];
 	int num_users;
@@ -5350,41 +5373,27 @@
 	            g_list_length(users);
 
 	for (l = users; l != NULL; l = l->next) {
-		for (names = gaim_conv_chat_get_users(chat);
-			 names != NULL;
-			 names = names->next) {
-
-			char *u = (char *)names->data;
-
-			if (!gaim_utf8_strcasecmp(u, (char *)l->data)) {
-				model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
-
-				if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model),
-												   &iter))
-					break;
-
-				do {
-					char *val;
-
-					gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
-									   CHAT_USERS_NAME_COLUMN, &val, -1);
-
-					if (!gaim_utf8_strcasecmp((char *)l->data, val))
-						gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
-
-					f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
-
-					g_free(val);
-				} while (f);
-
-				break;
-			}
-		}
+		model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
+
+		if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model),
+					&iter))
+			continue;
+
+		do {
+			char *val;
+
+			gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
+							   CHAT_USERS_NAME_COLUMN, &val, -1);
+
+			if (!gaim_utf8_strcasecmp((char *)l->data, val))
+				f = gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+			else
+				f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
+
+			g_free(val);
+		} while (f);
 	}
 
-	if (names == NULL)
-		return;
-
 	g_snprintf(tmp, sizeof(tmp),
 			   ngettext("%d person in room", "%d people in room",
 						num_users), num_users);
@@ -5392,6 +5401,47 @@
 	gtk_label_set_text(GTK_LABEL(gtkchat->count), tmp);
 }
 
+static void
+gaim_gtkconv_chat_update_user(GaimConversation *conv, const char *user)
+{
+	GaimConvChat *chat;
+	GaimGtkConversation *gtkconv;
+	GaimGtkChatPane *gtkchat;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	int f = 1;
+
+	chat    = GAIM_CONV_CHAT(conv);
+	gtkconv = GAIM_GTK_CONVERSATION(conv);
+	gtkchat = gtkconv->u.chat;
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
+
+	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
+		return;
+
+	while (f != 0) {
+		char *val;
+
+		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
+
+		if (!gaim_utf8_strcasecmp(user, val)) {
+			gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+			g_free(val);
+			break;
+		}
+
+		f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
+
+		g_free(val);
+	}
+
+	if (!gaim_conv_chat_find_user(chat, user))
+		return;
+
+	add_chat_buddy_common(conv, user);
+}
+
 static gboolean
 gaim_gtkconv_has_focus(GaimConversation *conv)
 {
@@ -5514,9 +5564,9 @@
 
 		topic = gaim_conv_chat_get_topic(chat);
 
-		gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text),topic);
+		gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), topic ? topic : "");
 		gtk_tooltips_set_tip(gtkconv->tooltips, gtkchat->topic_text,
-		                     topic, NULL);
+		                     topic ? topic : "", NULL);
 	}
 	else if (type == GAIM_CONV_ACCOUNT_ONLINE ||
 			 type == GAIM_CONV_ACCOUNT_OFFLINE)
@@ -5553,6 +5603,7 @@
 	gaim_gtkconv_chat_rename_user,   /* chat_rename_user     */
 	gaim_gtkconv_chat_remove_user,   /* chat_remove_user     */
 	gaim_gtkconv_chat_remove_users,  /* chat_remove_users    */
+	gaim_gtkconv_chat_update_user,   /* chat_update_user     */
 	NULL,                            /* update_progress      */
 	gaim_gtkconv_has_focus,          /* has_focus            */
 	gaim_gtkconv_updated             /* updated              */
--- a/src/gtkconv.h	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/gtkconv.h	Sat Jul 17 18:11:12 2004 +0000
@@ -31,6 +31,7 @@
 enum {
 	CHAT_USERS_ICON_COLUMN,
 	CHAT_USERS_NAME_COLUMN,
+	CHAT_USERS_FLAGS_COLUMN,
 	CHAT_USERS_COLUMNS
 };
 
--- a/src/protocols/irc/msgs.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/irc/msgs.c	Sat Jul 17 18:11:12 2004 +0000
@@ -56,10 +56,9 @@
 
 static void irc_chat_remove_buddy(GaimConversation *convo, char *data[2])
 {
-	GList *users = gaim_conv_chat_get_users(GAIM_CONV_CHAT(convo));
 	char *message = g_strdup_printf("quit: %s", data[1]);
 
-	if (g_list_find_custom(users, data[0], (GCompareFunc)(strcmp)))
+	if (gaim_conv_chat_find_user(GAIM_CONV_CHAT(convo), data[0]))
 		gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo), data[0], message);
 
 	g_free(message);
@@ -332,15 +331,26 @@
 			irc->nameconv = NULL;
 		} else {
 			GList *users = NULL;
+			GList *flags = NULL;
 
 			while (*cur) {
+				GaimConvChatBuddyFlags f = GAIM_CBFLAGS_NONE;
 				end = strchr(cur, ' ');
 				if (!end)
 					end = cur + strlen(cur);
-				if (*cur == '@' || *cur == '%' || *cur == '+')
+				if (*cur == '@') {
+					f = GAIM_CBFLAGS_OP;
 					cur++;
+				} else if (*cur == '%') {
+					f = GAIM_CBFLAGS_HALFOP;
+					cur++;
+				} else if(*cur == '+') {
+					f = GAIM_CBFLAGS_VOICE;
+					cur++;
+				}
 				tmp = g_strndup(cur, end - cur);
 				users = g_list_append(users, tmp);
+				flags = g_list_append(flags, GINT_TO_POINTER(f));
 				cur = end;
 				if (*cur)
 					cur++;
@@ -349,12 +359,13 @@
 			if (users != NULL) {
 				GList *l;
 
-				gaim_conv_chat_add_users(GAIM_CONV_CHAT(convo), users);
+				gaim_conv_chat_add_users(GAIM_CONV_CHAT(convo), users, flags);
 
 				for (l = users; l != NULL; l = l->next)
 					g_free(l->data);
 
 				g_list_free(users);
+				g_list_free(flags);
 			}
 		}
 		g_free(names);
@@ -574,7 +585,7 @@
 	}
 
 	userhost = irc_mask_userhost(from);
-	gaim_conv_chat_add_user(GAIM_CONV_CHAT(convo), nick, userhost);
+	gaim_conv_chat_add_user(GAIM_CONV_CHAT(convo), nick, userhost, GAIM_CBFLAGS_NONE);
 
 	if ((ib = g_hash_table_lookup(irc->buddies, nick)) != NULL) {
 		ib->flag = TRUE;
@@ -632,6 +643,45 @@
 		buf = g_strdup_printf(_("mode (%s %s) by %s"), args[1], args[2] ? args[2] : "", nick);
 		gaim_conv_chat_write(GAIM_CONV_CHAT(convo), args[0], buf, GAIM_MESSAGE_SYSTEM|GAIM_MESSAGE_NO_LOG, time(NULL));
 		g_free(buf);
+		if(args[2]) {
+			GaimConvChatBuddyFlags newflag, flags;
+			char *mcur, *cur, *end, *user;
+			gboolean add = FALSE;
+			mcur = args[1];
+			cur = args[2];
+			while (*cur && *mcur) {
+				if ((*mcur == '+') || (*mcur == '-')) {
+					add = (*mcur == '+') ? TRUE : FALSE;
+					mcur++;
+					continue;
+				}
+				end = strchr(cur, ' ');
+				if (!end)
+					end = cur + strlen(cur);
+				user = g_strndup(cur, end - cur);
+				flags = gaim_conv_chat_user_get_flags(GAIM_CONV_CHAT(convo), user);
+				newflag = GAIM_CBFLAGS_NONE;
+				if (*mcur == 'o')
+					newflag = GAIM_CBFLAGS_OP;
+				else if (*mcur =='h')
+					newflag = GAIM_CBFLAGS_HALFOP;
+				else if (*mcur == 'v')
+					newflag = GAIM_CBFLAGS_VOICE;
+				if (newflag) {
+					if (add)
+						flags |= newflag;
+					else
+						flags &= ~newflag;
+					gaim_conv_chat_user_set_flags(GAIM_CONV_CHAT(convo), user, flags);
+				}
+				g_free(user);
+				cur = end;
+				if (*cur)
+					cur++;
+				if (*mcur)
+					mcur++;
+			}
+		}
 	} else {					/* User		*/
 	}
 	g_free(nick);
@@ -655,18 +705,7 @@
 
 	while (chats) {
 		GaimConvChat *chat = GAIM_CONV_CHAT(chats->data);
-		GList *users = gaim_conv_chat_get_users(chat);
-
-		while (users) {
-			char *user = users->data;
-
-			if (!strcmp(nick, user)) {
-				gaim_conv_chat_rename_user(chat, user, args[0]);
-				users = gaim_conv_chat_get_users(chat);
-				break;
-			}
-			users = users->next;
-		}
+		gaim_conv_chat_rename_user(chat, nick, args[0]);
 		chats = chats->next;
 	}
 	g_free(nick);
--- a/src/protocols/jabber/chat.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/jabber/chat.c	Sat Jul 17 18:11:12 2004 +0000
@@ -261,15 +261,7 @@
 
 gboolean jabber_chat_find_buddy(GaimConversation *conv, const char *name)
 {
-	GList *m = gaim_conv_chat_get_users(GAIM_CONV_CHAT(conv));
-
-	while(m) {
-		if(!strcmp(m->data, name))
-			return TRUE;
-		m = m->next;
-	}
-
-	return FALSE;
+	return gaim_conv_chat_find_user(GAIM_CONV_CHAT(conv), name);
 }
 
 char *jabber_chat_buddy_real_name(GaimConnection *gc, int id, const char *who)
--- a/src/protocols/jabber/presence.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/jabber/presence.c	Sat Jul 17 18:11:12 2004 +0000
@@ -199,6 +199,7 @@
 	JabberChat *chat;
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr = NULL;
+	GaimConvChatBuddyFlags flags = GAIM_CBFLAGS_NONE;
 	GaimBuddy *b;
 	char *buddy_name;
 	int state = 0;
@@ -400,9 +401,17 @@
 
 			jabber_chat_track_handle(chat, jid->resource, real_jid, affiliation, role);
 
+			if (!strcmp(role, "moderator"))
+				flags = GAIM_CBFLAGS_OP;
+			else if (!strcmp(role, "participant"))
+				flags = GAIM_CBFLAGS_VOICE;
+
 			if(!jabber_chat_find_buddy(chat->conv, jid->resource))
 				gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat->conv), jid->resource,
-						real_jid);
+						real_jid, flags);
+			else
+				gaim_conv_chat_user_set_flags(GAIM_CONV_CHAT(chat->conv), jid->resource,
+						flags);
 		}
 		g_free(room_jid);
 	} else {
--- a/src/protocols/msn/msn.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/msn/msn.c	Sat Jul 17 18:11:12 2004 +0000
@@ -340,7 +340,7 @@
 	swboard->conv = serv_got_joined_chat(gc, swboard->chat_id, "MSN Chat");
 
 	gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv),
-							gaim_account_get_username(buddy->account), NULL);	
+							gaim_account_get_username(buddy->account), NULL, GAIM_CBFLAGS_NONE);
 }
 
 static void
--- a/src/protocols/msn/switchboard.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/msn/switchboard.c	Sat Jul 17 18:11:12 2004 +0000
@@ -68,7 +68,7 @@
 
 	if ((swboard->conv != NULL) && (gaim_conversation_get_type(swboard->conv) == GAIM_CONV_CHAT))
 	{
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv), user, NULL);
+		gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv), user, NULL, GAIM_CBFLAGS_NONE);
 	}
 	else if (swboard->current_users > 1 || swboard->total_users > 1)
 	{
@@ -99,14 +99,14 @@
 				 * tmp_user); */
 
 				gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv),
-										tmp_user, NULL);
+										tmp_user, NULL, GAIM_CBFLAGS_NONE);
 			}
 
 			/* gaim_debug_info("msn", "[chat] We add ourselves.\n"); */
 
 			gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv),
 									gaim_account_get_username(account),
-									NULL);
+									NULL, GAIM_CBFLAGS_NONE);
 
 			g_free(swboard->im_user);
 			swboard->im_user = NULL;
--- a/src/protocols/napster/napster.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/napster/napster.c	Sat Jul 17 18:11:12 2004 +0000
@@ -365,7 +365,7 @@
 	case 408: /* MSG_SERVER_CHANNEL_USER_LIST */
 		res = g_strsplit(buf, " ", 4);
 		c = nap_find_chat(gc, res[0]);
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(c), res[1], NULL);
+		gaim_conv_chat_add_user(GAIM_CONV_CHAT(c), res[1], NULL, GAIM_CBFLAGS_NONE);
 		g_strfreev(res);
 		break;
 
--- a/src/protocols/novell/novell.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/novell/novell.c	Sat Jul 17 18:11:12 2004 +0000
@@ -649,7 +649,7 @@
 				ur = nm_conference_get_participant(conference, i);
 				if (ur) {
 					name = nm_user_record_get_display_id(ur);
-					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL);
+					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL, GAIM_CBFLAGS_NONE);
 				}
 			}
 		}
@@ -1858,7 +1858,6 @@
 	NMUserRecord *ur = NULL;
 	const char *name;
 	const char *conf_name;
-	GList *list = NULL;
 
 	gc = gaim_account_get_connection(user->client_data);
 	if (gc == NULL)
@@ -1878,7 +1877,7 @@
 					nm_conference_set_data(conference, (gpointer) chat);
 
 					name = nm_user_record_get_display_id(ur);
-					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL);
+					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL, GAIM_CBFLAGS_NONE);
 
 				}
 			}
@@ -1888,9 +1887,8 @@
 			ur = nm_find_user_record(user, nm_event_get_source(event));
 			if (ur) {
 				name = nm_user_record_get_display_id(ur);
-				list = gaim_conv_chat_get_users(GAIM_CONV_CHAT(chat));
-				if (!g_list_find_custom(list, name, (GCompareFunc)nm_utf8_strcasecmp)) {
-					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL);
+				if (!gaim_conv_chat_find_user(GAIM_CONV_CHAT(chat), name)) {
+					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL, GAIM_CBFLAGS_NONE);
 				}
 			}
 		}
--- a/src/protocols/oscar/oscar.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/oscar/oscar.c	Sat Jul 17 18:11:12 2004 +0000
@@ -4189,7 +4189,7 @@
 		return 1;
 
 	for (i = 0; i < count; i++)
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(c->conv), info[i].sn, NULL);
+		gaim_conv_chat_add_user(GAIM_CONV_CHAT(c->conv), info[i].sn, NULL, GAIM_CBFLAGS_NONE);
 
 	return 1;
 }
--- a/src/protocols/silc/chat.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/silc/chat.c	Sat Jul 17 18:11:12 2004 +0000
@@ -941,7 +941,7 @@
 	SilcUInt32 retry = SILC_PTR_TO_32(channel->context);
 	SilcHashTableList htl;
 	SilcChannelUser chu;
-	GList *users = NULL;
+	GList *users = NULL, *flags = NULL;
 	char tmp[256];
 
 	if (!clients && retry < 1) {
@@ -963,25 +963,17 @@
 	/* Add all users to channel */
 	silc_hash_table_list(channel->user_list, &htl);
 	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		GaimConvChatBuddyFlags f = GAIM_CBFLAGS_NONE;
 		if (!chu->client->nickname)
 			continue;
 		chu->context = SILC_32_TO_PTR(sg->channel_ids);
 
-#if 0   /* XXX don't append mode char to nick because Gaim doesn't
-	   give a way to change it afterwards when mode changes. */
-		tmp2 = silc_client_chumode_char(chu->mode);
-		if (tmp2)
-			g_snprintf(tmp, sizeof(tmp), "%s%s", tmp2,
-				   chu->client->nickname);
-		else
-			g_snprintf(tmp, sizeof(tmp), "%s",
-				   chu->client->nickname);
-		silc_free(tmp2);
-
-		users = g_list_append(users, g_strdup(tmp));
-#else
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
+			f |= GAIM_CBFLAGS_FOUNDER;
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANOP)
+			f |= GAIM_CBFLAGS_OP;
 		users = g_list_append(users, g_strdup(chu->client->nickname));
-#endif
+		flags = g_list_append(flags, GINT_TO_POINTER(f));
 
 		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
 			if (chu->client == conn->local_entry)
@@ -1000,8 +992,9 @@
 	}
 	silc_hash_table_list_reset(&htl);
 
-	gaim_conv_chat_add_users(GAIM_CONV_CHAT(convo), users);
+	gaim_conv_chat_add_users(GAIM_CONV_CHAT(convo), users, flags);
 	g_list_free(users);
+	g_list_free(flags);
 
 	/* Set topic */
 	if (channel->topic)
--- a/src/protocols/silc/ops.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/silc/ops.c	Sat Jul 17 18:11:12 2004 +0000
@@ -272,7 +272,7 @@
 		g_snprintf(buf, sizeof(buf), "%s@%s",
 			   client_entry->username, client_entry->hostname);
 		gaim_conv_chat_add_user(GAIM_CONV_CHAT(convo),
-					g_strdup(client_entry->nickname), buf);
+					g_strdup(client_entry->nickname), buf, GAIM_CBFLAGS_NONE);
 
 		break;
 
@@ -422,39 +422,47 @@
 		break;
 
 	case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
-		idtype = va_arg(va, int);
-		entry = va_arg(va, void *);
-		mode = va_arg(va, SilcUInt32);
-		client_entry2 = va_arg(va, SilcClientEntry);
-		channel = va_arg(va, SilcChannelEntry);
+		{
+			GaimConvChatBuddyFlags flags = GAIM_CBFLAGS_NONE;
+			idtype = va_arg(va, int);
+			entry = va_arg(va, void *);
+			mode = va_arg(va, SilcUInt32);
+			client_entry2 = va_arg(va, SilcClientEntry);
+			channel = va_arg(va, SilcChannelEntry);
 
-		convo = gaim_find_conversation_with_account(channel->channel_name,
-							    sg->account);
-		if (!convo)
-			break;
+			convo = gaim_find_conversation_with_account(channel->channel_name,
+					sg->account);
+			if (!convo)
+				break;
 
-		if (idtype == SILC_ID_CLIENT)
-			name = ((SilcClientEntry)entry)->nickname;
-		else if (idtype == SILC_ID_SERVER)
-			name = ((SilcServerEntry)entry)->server_name;
-		else
-			name = ((SilcChannelEntry)entry)->channel_name;
-		if (!name)
-			break;
+			if (idtype == SILC_ID_CLIENT)
+				name = ((SilcClientEntry)entry)->nickname;
+			else if (idtype == SILC_ID_SERVER)
+				name = ((SilcServerEntry)entry)->server_name;
+			else
+				name = ((SilcChannelEntry)entry)->channel_name;
+			if (!name)
+				break;
 
-		if (mode) {
-			silcgaim_get_chumode_string(mode, buf2, sizeof(buf2));
-			g_snprintf(buf, sizeof(buf),
-				   _("<I>%s</I> set <I>%s's</I> modes to: %s"), name,
-				   client_entry2->nickname, buf2);
-		} else {
-			g_snprintf(buf, sizeof(buf),
-				   _("<I>%s</I> removed all <I>%s's</I> modes"), name,
-				   client_entry2->nickname);
+			if (mode) {
+				silcgaim_get_chumode_string(mode, buf2, sizeof(buf2));
+				g_snprintf(buf, sizeof(buf),
+						_("<I>%s</I> set <I>%s's</I> modes to: %s"), name,
+						client_entry2->nickname, buf2);
+				if (mode & SILC_CHANNEL_UMODE_CHANFO)
+					flags |= GAIM_CBFLAGS_FOUNDER;
+				if (mode & SILC_CHANNEL_UMODE_CHANOP)
+					flags |= GAIM_CBFLAGS_OP;
+			} else {
+				g_snprintf(buf, sizeof(buf),
+						_("<I>%s</I> removed all <I>%s's</I> modes"), name,
+						client_entry2->nickname);
+			}
+			gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name,
+					buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+			gaim_conv_chat_user_set_flags(GAIM_CONV_CHAT(convo), client_entry2->nickname, flags);
+			break;
 		}
-		gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name,
-				     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
-		break;
 
 	case SILC_NOTIFY_TYPE_MOTD:
 		tmp = va_arg(va, char *);
--- a/src/protocols/toc/toc.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/toc/toc.c	Sat Jul 17 18:11:12 2004 +0000
@@ -830,7 +830,7 @@
 
 		if (in && (*in == 'T'))
 			while ((buddy = strtok(NULL, ":")) != NULL)
-				gaim_conv_chat_add_user(chat, buddy, NULL);
+				gaim_conv_chat_add_user(chat, buddy, NULL, GAIM_CBFLAGS_NONE);
 		else
 			while ((buddy = strtok(NULL, ":")) != NULL)
 				gaim_conv_chat_remove_user(chat, buddy, NULL);
--- a/src/protocols/yahoo/yahoochat.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/yahoo/yahoochat.c	Sat Jul 17 18:11:12 2004 +0000
@@ -69,36 +69,24 @@
 	yahoo_packet_free(pkt);
 }
 
-static gint _mystrcmpwrapper(gconstpointer a, gconstpointer b)
-{
-	return strcmp(a, b);
-}
-
 /* this is slow, and different from the gaim_* version in that it (hopefully) won't add a user twice */
 void yahoo_chat_add_users(GaimConvChat *chat, GList *newusers)
 {
-	GList *users, *i, *j;
-
-	users = gaim_conv_chat_get_users(chat);
+	GList *i;
 
 	for (i = newusers; i; i = i->next) {
-		j = g_list_find_custom(users, i->data, _mystrcmpwrapper);
-		if (j)
+		if (gaim_conv_chat_find_user(chat, i->data))
 			continue;
-		gaim_conv_chat_add_user(chat, i->data, NULL);
+		gaim_conv_chat_add_user(chat, i->data, NULL, GAIM_CBFLAGS_NONE);
 	}
 }
 
 void yahoo_chat_add_user(GaimConvChat *chat, const char *user, const char *reason)
 {
-	GList *users;
-
-	users = gaim_conv_chat_get_users(chat);
-
-	if ((g_list_find_custom(users, user, _mystrcmpwrapper)))
+	if (gaim_conv_chat_find_user(chat, user))
 		return;
 
-	gaim_conv_chat_add_user(chat, user, reason);
+	gaim_conv_chat_add_user(chat, user, reason, GAIM_CBFLAGS_NONE);
 }
 
 static GaimConversation *yahoo_find_conference(GaimConnection *gc, const char *name)
@@ -413,6 +401,10 @@
 	if (room && (!c || gaim_conv_chat_has_left(GAIM_CONV_CHAT(c))) && members &&
 	   ((g_list_length(members) > 1) ||
 	     !g_ascii_strcasecmp(members->data, gaim_connection_get_display_name(gc)))) {
+		int i;
+		GList *flags = NULL;
+		for (i = 0; i < g_list_length(members); i++)
+			flags = g_list_append(flags, GINT_TO_POINTER(GAIM_CBFLAGS_NONE));
 		if (c && gaim_conv_chat_has_left(GAIM_CONV_CHAT(c))) {
 			/* this might be a hack, but oh well, it should nicely */
 			char *tmpmsg;
@@ -424,7 +416,7 @@
 				gaim_conv_chat_set_topic(GAIM_CONV_CHAT(c), NULL, topic);
 			yd->in_chat = 1;
 			yd->chat_name = g_strdup(room);
-			gaim_conv_chat_add_users(GAIM_CONV_CHAT(c), members);
+			gaim_conv_chat_add_users(GAIM_CONV_CHAT(c), members, flags);
 
 			tmpmsg = g_strdup_printf(_("You are now chatting in %s."), room);
 			gaim_conv_chat_write(GAIM_CONV_CHAT(c), "", tmpmsg, GAIM_MESSAGE_SYSTEM, time(NULL));
@@ -435,7 +427,7 @@
 				gaim_conv_chat_set_topic(GAIM_CONV_CHAT(c), NULL, topic);
 			yd->in_chat = 1;
 			yd->chat_name = g_strdup(room);
-			gaim_conv_chat_add_users(GAIM_CONV_CHAT(c), members);
+			gaim_conv_chat_add_users(GAIM_CONV_CHAT(c), members, flags);
 		}
 	} else if (c) {
 		yahoo_chat_add_users(GAIM_CONV_CHAT(c), members);
@@ -600,7 +592,8 @@
 
 	yahoo_packet_hash(pkt, 1, dn);
 	for (w = who; w; w = w->next) {
-		yahoo_packet_hash(pkt, 3, (char *)w->data);
+		const char *name = gaim_conv_chat_cb_get_name(w->data);
+		yahoo_packet_hash(pkt, 3, name);
 	}
 
 	yahoo_packet_hash(pkt, 57, room);
@@ -626,8 +619,10 @@
 	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YAHOO_STATUS_AVAILABLE, 0);
 
 	yahoo_packet_hash(pkt, 1, dn);
-	for (who = members; who; who = who->next)
-		yahoo_packet_hash(pkt, 53, (char *)who->data);
+	for (who = members; who; who = who->next) {
+		const char *name = gaim_conv_chat_cb_get_name(who->data);
+		yahoo_packet_hash(pkt, 53, name);
+	}
 	yahoo_packet_hash(pkt, 57, room);
 	yahoo_packet_hash(pkt, 14, msg2);
 	if (utf8)
@@ -663,7 +658,7 @@
 			if (!strcmp(memarr[i], "") || !strcmp(memarr[i], dn))
 					continue;
 			yahoo_packet_hash(pkt, 3, memarr[i]);
-			gaim_conv_chat_add_user(GAIM_CONV_CHAT(c), memarr[i], NULL);
+			gaim_conv_chat_add_user(GAIM_CONV_CHAT(c), memarr[i], NULL, GAIM_CBFLAGS_NONE);
 		}
 	}
 	yahoo_send_packet(yd, pkt);
@@ -695,10 +690,11 @@
 	yahoo_packet_hash(pkt, 58, msg?msg2:"");
 	yahoo_packet_hash(pkt, 13, "0");
 	for(; members; members = members->next) {
-		if (!strcmp(members->data, dn))
+		const char *name = gaim_conv_chat_cb_get_name(members->data);
+		if (!strcmp(name, dn))
 			continue;
-		yahoo_packet_hash(pkt, 52, (char *)members->data);
-		yahoo_packet_hash(pkt, 53, (char *)members->data);
+		yahoo_packet_hash(pkt, 52, name);
+		yahoo_packet_hash(pkt, 53, name);
 	}
 	yahoo_send_packet(yd, pkt);
 
--- a/src/protocols/yahoo/ycht.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/yahoo/ycht.c	Sat Jul 17 18:11:12 2004 +0000
@@ -116,12 +116,9 @@
 
 	for (i = 0; members[i]; i++) {
 		if (new_room) {
-			GList l;
 			/*if (!strcmp(members[i], gaim_connection_get_display_name(ycht->gc)))
 				continue;*/
-			l.data = members[i];
-			l.next = l.prev = NULL;
-			gaim_conv_chat_add_users(GAIM_CONV_CHAT(c), &l);
+			gaim_conv_chat_add_user(GAIM_CONV_CHAT(c), members[i], NULL, GAIM_CBFLAGS_NONE);
 		} else {
 			yahoo_chat_add_user(GAIM_CONV_CHAT(c), members[i], NULL);
 		}
--- a/src/protocols/zephyr/zephyr.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/protocols/zephyr/zephyr.c	Sat Jul 17 18:11:12 2004 +0000
@@ -689,8 +689,6 @@
 				g_free(stripped_sender);
 			} else {
 				zephyr_triple *zt1, *zt2;
-                                GList *gltmp;
-                                int found = 0;
                                 gchar *send_inst_utf8;
                                 
 				zt1 = new_triple(notice.z_class, notice.z_class_inst, notice.z_recipient);
@@ -722,18 +720,14 @@
                                 gconv1 = gaim_find_conversation_with_account(zt2->name, zgc->account);
                                 gcc = gaim_conversation_get_chat_data(gconv1);
                                 
-                                for (gltmp = gaim_conv_chat_get_users(gcc); gltmp; gltmp = gltmp->next) {
-                                        if (!g_ascii_strcasecmp(gltmp->data, sendertmp))
-                                                found = 1;
-                                }
-                                if (!found) {
+                                if (!gaim_conv_chat_find_user(gcc, sendertmp)) {
                                         /* force interpretation in network byte order */
                                         unsigned char *addrs = (unsigned char *)&(notice.z_sender_addr.s_addr);
                                         gchar* ipaddr = g_strdup_printf("%hhd.%hhd.%hhd.%hhd", (unsigned char)addrs[0], 
                                                                         (unsigned char)addrs[1], (unsigned char)addrs[2], 
                                                                         (unsigned char) addrs[3]);
                                         
-                                        gaim_conv_chat_add_user(gcc, sendertmp, ipaddr);
+                                        gaim_conv_chat_add_user(gcc, sendertmp, ipaddr, GAIM_CBFLAGS_NONE);
                                         g_free(ipaddr); /* fix memory leak? */
                                         
                                 }
--- a/src/signals.c	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/signals.c	Sat Jul 17 18:11:12 2004 +0000
@@ -610,6 +610,18 @@
 }
 
 void
+gaim_marshal_VOID__POINTER_POINTER_UINT_UINT(GaimCallback cb, va_list args,
+										     void *data, void **return_val)
+{
+	void *arg1 = va_arg(args, void *);
+	void *arg2 = va_arg(args, void *);
+	guint arg3 = va_arg(args, guint);
+	guint arg4 = va_arg(args, guint);
+
+	((void (*)(void *, void *, guint, guint, void *))cb)(arg1, arg2, arg3, arg4, data);
+}
+
+void
 gaim_marshal_VOID__POINTER_POINTER_POINTER(GaimCallback cb, va_list args,
 										   void *data, void **return_val)
 {
--- a/src/signals.h	Sat Jul 17 17:08:24 2004 +0000
+++ b/src/signals.h	Sat Jul 17 18:11:12 2004 +0000
@@ -225,6 +225,8 @@
 		GaimCallback cb, va_list args, void *data, void **return_val);
 void gaim_marshal_VOID__POINTER_POINTER_UINT(
 		GaimCallback cb, va_list args, void *data, void **return_val);
+void gaim_marshal_VOID__POINTER_POINTER_UINT_UINT(
+		GaimCallback cb, va_list args, void *data, void **return_val);
 void gaim_marshal_VOID__POINTER_POINTER_POINTER(
 		GaimCallback cb, va_list args, void *data, void **return_val);
 void gaim_marshal_VOID__POINTER_POINTER_POINTER_POINTER(