diff --git a/src/Makefile b/src/Makefile
deleted file mode 100644
index 7d5053885ba1b3e234282ff7929c7bfe5664fc8d..0000000000000000000000000000000000000000
--- a/src/Makefile
+++ /dev/null
@@ -1,35 +0,0 @@
-# Set the C flags to include the GTK+ and Python libraries
-PYTHON ?= python
-PYTHONVER = `$(PYTHON) -c 'import sys; print sys.version[:3]'`
-gtk_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fPIC -I/usr/include/python$(PYTHONVER) -I.
-gtk_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -lpython$(PYTHONVER)
-
-all: trayicon.so gtkspell.so
-
-# Build the shared objects
-trayicon.so: trayicon.o eggtrayicon.o trayiconmodule.o
-	$(CC) -shared $^ -o $@ $(LDFLAGS) $(gtk_LDFLAGS)
-
-gtkspell.so:
-	$(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_CFLAGS) $(gtk_LDFLAGS) `pkg-config --libs --cflags gtkspell-2.0` -shared gtkspellmodule.c $^ -o $@
-
-# The path to the GTK+ python types
-DEFS=`pkg-config --variable=defsdir pygtk-2.0`
-
-%.o: %.c
-	$(CC) -o $@ -c $< $(CFLAGS) $(gtk_CFLAGS)
-
-# Generate the C wrapper from the defs and our override file
-trayicon.c: trayicon.defs trayicon.override
-	pygtk-codegen-2.0 --prefix trayicon \
-	--register $(DEFS)/gdk-types.defs \
-	--register $(DEFS)/gtk-types.defs \
-	--override trayicon.override \
-	trayicon.defs > $@
-
-
-# A rule to clean the generated files
-clean:
-	rm -f trayicon.so *.o trayicon.c gtkspell.so *~  
-
-.PHONY: clean
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..30eb995a95f44390ba2784d6328cc48a304b3c88
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,73 @@
+SUBDIRS = common
+
+CLEANFILES = \
+	trayicon.c
+INCLUDES = \
+	$(PYTHON_INCLUDES) 
+
+if BUILD_GTKSPELL
+gtkspelllib_LTLIBRARIES = gtkspell.la
+gtkspelllibdir = $(libdir)/gajim
+
+gtkspell_la_LIBADD = \
+	$(GTKSPELL_LIBS) $(PYGTK_LIBS) 
+
+gtkspell_la_SOURCES = \
+	gtkspellmodule.c
+
+gtkspell_la_LDFLAGS = \
+	-module -avoid-version 
+
+gtkspell_la_CFLAGS = $(GTKSPELL_CFLAGS) $(PYGTK_CFLAGS)
+endif
+if BUILD_TRAYICON
+trayiconlib_LTLIBRARIES = trayicon.la
+trayiconlibdir = $(libdir)/gajim
+trayicon_la_LIBADD = $(PYGTK_LIBS)
+trayicon_la_SOURCES = \
+					  eggtrayicon.c \
+					  trayiconmodule.c
+
+nodist_trayicon_la_SOURCES = \
+					trayicon.c
+
+trayicon_la_LDFLAGS = \
+	-module -avoid-version 
+trayicon_la_CFLAGS = $(PYGTK_CFLAGS)
+
+trayicon.c: 
+	    pygtk-codegen-2.0 --prefix trayicon \
+		    --register $(PYGTK_DEFS)/gdk-types.defs \
+		    --register $(PYGTK_DEFS)/gtk-types.defs \
+		    --override $(srcdir)/trayicon.override \
+		    $(srcdir)/trayicon.defs > $@
+endif
+gajimsrcdir = $(pkgdatadir)/src
+gajimsrc_DATA = $(srcdir)/*.py 
+
+gajimsrc1dir = $(pkgdatadir)/src/common
+gajimsrc1_DATA = \
+				$(srcdir)/common/*.py 
+
+gajimsrc2dir = $(pkgdatadir)/src/common/xmpp
+gajimsrc2_DATA = \
+				$(srcdir)/common/xmpp/*.py 
+
+gajimsrc3dir = $(pkgdatadir)/src/common/zeroconf
+gajimsrc3_DATA = \
+				$(srcdir)/common/zeroconf/*.py 
+
+DISTCLEANFILES = 
+
+EXTRA_DIST = $(gajimsrc_DATA) \
+			$(gajimsrc1_DATA) \
+			$(gajimsrc2_DATA) \
+			$(gajimsrc3_DATA) \
+			gtkspellmodule.c \
+			eggtrayicon.c \
+			trayiconmodule.c \
+			eggtrayicon.h \
+			trayicon.defs \
+			trayicon.override 
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/advanced.py b/src/advanced.py
index 76bf6ed4f783f298f87841ccbfd42cf23fa1e4cb..0640c02d70036e096b102c30fdb75b77bc2948ba 100644
--- a/src/advanced.py
+++ b/src/advanced.py
@@ -1,18 +1,8 @@
 ##	advanced.py
 ##
-## Contributors for this file:
-## - Yann Le Boulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <kourem@gmail.com>
-## - Vincent Hanquez <tab@snarc.org>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005 Vincent Hanquez <tab@snarc.org>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -42,7 +32,7 @@ C_TYPE
 
 GTKGUI_GLADE = 'manage_accounts_window.glade'
 
-class AdvancedConfigurationWindow:
+class AdvancedConfigurationWindow(object):
 	def __init__(self):
 		self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade')
 		self.window = self.xml.get_widget('advanced_configuration_window')
diff --git a/src/cell_renderer_image.py b/src/cell_renderer_image.py
index f09dfa7ac7206d4cb7471bc83c3fbdc0e7e575df..d6a9fbc63902bacd6a3651347718efe6151b3219 100644
--- a/src/cell_renderer_image.py
+++ b/src/cell_renderer_image.py
@@ -8,7 +8,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
diff --git a/src/chat_control.py b/src/chat_control.py
index b9cf730a7918a091839395a3d46a8c6d211c4a99..7a0a9effcdcccb81721a37dadcc8a4d53b398b81 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -34,6 +34,8 @@ from message_textview import MessageTextView
 from common.contacts import GC_Contact
 from common.logger import Constants
 constants = Constants()
+from common.rst_xhtml_generator import create_xhtml
+from common.xmpp.protocol import NS_XHTML
 
 try:
 	import gtkspell
@@ -112,7 +114,7 @@ class ChatControlBase(MessageControl):
 	acct, resource = None):
 		MessageControl.__init__(self, type_id, parent_win, widget_name,
 			display_names,	contact, acct, resource = resource);
-		# when/if we do XHTML we will but formatting buttons back
+		# when/if we do XHTML we will put formatting buttons back
 		widget = self.xml.get_widget('emoticons_button')
 		id = widget.connect('clicked', self.on_emoticons_button_clicked)
 		self.handlers[id] = widget
@@ -196,7 +198,6 @@ class ChatControlBase(MessageControl):
 					self.msg_textview.lang = lang
 					spell.set_language(lang)
 			except (gobject.GError, RuntimeError), msg:
-				#FIXME: add a ui for this use spell.set_language()
 				dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
 					'for which you want to highlight misspelled words, then please '
 					'set your $LANG as appropriate. Eg. for French do export '
@@ -229,6 +230,11 @@ class ChatControlBase(MessageControl):
 		item = gtk.SeparatorMenuItem()
 		menu.prepend(item)
 
+		item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+		menu.prepend(item)
+		id = item.connect('activate', self.msg_textview.clear)
+		self.handlers[id] = item
+
 		if gajim.config.get('use_speller') and HAS_GTK_SPELL:
 			item = gtk.MenuItem(_('Spelling language'))
 			menu.prepend(item)
@@ -242,11 +248,6 @@ class ChatControlBase(MessageControl):
 				id = item.connect('activate', _on_select_dictionary, langs[lang])
 				self.handlers[id] = item
 
-		item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
-		menu.prepend(item)
-		id = item.connect('activate', self.msg_textview.clear)
-		self.handlers[id] = item
-
 		menu.show_all()
 
 	# moved from ChatControl 
@@ -425,7 +426,8 @@ class ChatControlBase(MessageControl):
 		message_textview = widget
 		message_buffer = message_textview.get_buffer()
 		start_iter, end_iter = message_buffer.get_bounds()
-		message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
+		message = message_buffer.get_text(start_iter, end_iter, False).decode(
+			'utf-8')
 
 		# construct event instance from binding
 		event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
@@ -471,10 +473,11 @@ class ChatControlBase(MessageControl):
 				self.send_message(message) # send the message
 		else:
 			# Give the control itself a chance to process
-			self.handle_message_textview_mykey_press(widget, event_keyval, event_keymod)
+			self.handle_message_textview_mykey_press(widget, event_keyval,
+				event_keymod)
 
 	def _process_command(self, message):
-		if not message:
+		if not message or message[0] != '/':
 			return False
 
 		message = message[1:]
@@ -534,7 +537,7 @@ class ChatControlBase(MessageControl):
 	def print_conversation_line(self, text, kind, name, tim,
 		other_tags_for_name = [], other_tags_for_time = [], 
 		other_tags_for_text = [], count_as_new = True,
-		subject = None, old_kind = None):
+		subject = None, old_kind = None, xhtml = None):
 		'''prints 'chat' type messages'''
 		jid = self.contact.jid
 		full_jid = self.get_full_jid()
@@ -544,20 +547,26 @@ class ChatControlBase(MessageControl):
 			end = True
 		textview.print_conversation_line(text, jid, kind, name, tim,
 			other_tags_for_name, other_tags_for_time, other_tags_for_text,
-			subject, old_kind)
+			subject, old_kind, xhtml)
 
 		if not count_as_new:
 			return
 		if kind == 'incoming':
 			gajim.last_message_time[self.account][full_jid] = time.time()
-		urgent = True
 		if (not self.parent_win.get_active_jid() or \
 		full_jid != self.parent_win.get_active_jid() or \
 		not self.parent_win.is_active() or not end) and \
 		kind in ('incoming', 'incoming_queue'):
-			if self.notify_on_new_messages():
+			gc_message = False
+			if self.type_id  == message_control.TYPE_GC:
+				gc_message = True
+			if not gc_message or \
+			(gc_message and (other_tags_for_text == ['marked'] or \
+			gajim.config.get('notify_on_all_muc_messages'))):
+			# we want to have save this message in events list
+			# other_tags_for_text == ['marked'] --> highlighted gc message
 				type_ = 'printed_' + self.type_id
-				if self.type_id == message_control.TYPE_GC:
+				if gc_message:
 					type_ = 'printed_gc_msg'
 				show_in_roster = notify.get_show_in_roster('message_received',
 					self.account, self.contact)
@@ -572,10 +581,11 @@ class ChatControlBase(MessageControl):
 					gajim.interface.roster.draw_contact(self.contact.jid,
 						self.account)
 			self.parent_win.redraw_tab(self)
+			ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, self.account)
 			if not self.parent_win.is_active():
-				ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
-					self.account)
-				self.parent_win.show_title(urgent, ctrl)
+				self.parent_win.show_title(True, ctrl) # Enabled Urgent hint
+			else:
+				self.parent_win.show_title(False, ctrl) # Disabled Urgent hint
 
 	def toggle_emoticons(self):
 		'''hide show emoticons_button and make sure emoticons_menu is always there
@@ -647,17 +657,7 @@ class ChatControlBase(MessageControl):
 				if not gajim.events.remove_events(self.account, self.get_full_jid(),
 				types = [type_]):
 					# There were events to remove
-					self.parent_win.redraw_tab(self)
-					self.parent_win.show_title()
-					# redraw roster
-					if self.type_id == message_control.TYPE_PM:
-						room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
-						groupchat_control = gajim.interface.msg_win_mgr.get_control(
-							room_jid, self.account)
-						groupchat_control.draw_contact(nick)
-					else:
-						gajim.interface.roster.draw_contact(jid, self.account)
-						gajim.interface.roster.show_title()
+					self.redraw_after_event_removed(jid)
 			self.msg_textview.grab_focus()
 			# Note, we send None chatstate to preserve current
 			self.parent_win.redraw_tab(self)
@@ -750,8 +750,22 @@ class ChatControlBase(MessageControl):
 			if not gajim.events.remove_events(self.account, self.get_full_jid(),
 			types = ['printed_' + type_, type_]):
 				# There were events to remove
-				self.parent_win.redraw_tab(self)
-				self.parent_win.show_title()
+				self.redraw_after_event_removed(jid)
+
+	def redraw_after_event_removed(self, jid):
+		''' We just removed a 'printed_*' event, redraw contact in roster or 
+		gc_roster and titles in	roster and msg_win '''
+		self.parent_win.redraw_tab(self)
+		self.parent_win.show_title()
+		# TODO : get the contact and check notify.get_show_in_roster()
+		if self.type_id == message_control.TYPE_PM:
+			room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+			groupchat_control = gajim.interface.msg_win_mgr.get_control(
+				room_jid, self.account)
+			groupchat_control.draw_contact(nick)
+		else:
+			gajim.interface.roster.draw_contact(jid, self.account)
+			gajim.interface.roster.show_title()
 
 	def sent_messages_scroll(self, direction, conv_buf):
 		size = len(self.sent_history) 
@@ -760,7 +774,8 @@ class ChatControlBase(MessageControl):
 			#whatever is already typed
 			start_iter = conv_buf.get_start_iter()
 			end_iter = conv_buf.get_end_iter()
-			self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode('utf-8')
+			self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode(
+				'utf-8')
 			self.typing_new = False
 		if direction == 'up':
 			if self.sent_history_pos == 0:
@@ -820,8 +835,8 @@ class ChatControl(ChatControlBase):
 	old_msg_kind = None # last kind of the printed message
 	
 	def __init__(self, parent_win, contact, acct, resource = None):
-		ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_child_vbox',
-			(_('Chat'), _('Chats')), contact, acct, resource)
+		ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
+			'chat_child_vbox', (_('Chat'), _('Chats')), contact, acct, resource)
 			
 		# for muc use:
 		# widget = self.xml.get_widget('muc_window_actions_button')
@@ -829,13 +844,16 @@ class ChatControl(ChatControlBase):
 		id = widget.connect('clicked', self.on_actions_button_clicked)
 		self.handlers[id] = widget
 
-		self.hide_chat_buttons_always = gajim.config.get('always_hide_chat_buttons')
+		self.hide_chat_buttons_always = gajim.config.get(
+			'always_hide_chat_buttons')
 		self.chat_buttons_set_visible(self.hide_chat_buttons_always)
-		self.widget_set_visible(self.xml.get_widget('banner_eventbox'), gajim.config.get('hide_chat_banner'))
+		self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
+			gajim.config.get('hide_chat_banner'))
 		# Initialize drag-n-drop
 		self.TARGET_TYPE_URI_LIST = 80
 		self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ) ]
-		id = self.widget.connect('drag_data_received', self._on_drag_data_received)
+		id = self.widget.connect('drag_data_received',
+			self._on_drag_data_received)
 		self.handlers[id] = self.widget
 		self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
 			gtk.DEST_DEFAULT_HIGHLIGHT |
@@ -857,17 +875,21 @@ class ChatControl(ChatControlBase):
 			self._on_window_motion_notify)
 		self.handlers[id] = self.parent_win.window
 		message_tv_buffer = self.msg_textview.get_buffer()
-		id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed)
+		id = message_tv_buffer.connect('changed',
+			self._on_message_tv_buffer_changed)
 		self.handlers[id] = message_tv_buffer
 		
 		widget = self.xml.get_widget('avatar_eventbox')
-		id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
+		id = widget.connect('enter-notify-event',
+			self.on_avatar_eventbox_enter_notify_event)
 		self.handlers[id] = widget
 
-		id = widget.connect('leave-notify-event', self.on_avatar_eventbox_leave_notify_event)
+		id = widget.connect('leave-notify-event',
+			self.on_avatar_eventbox_leave_notify_event)
 		self.handlers[id] = widget
 
-		id = widget.connect('button-press-event', self.on_avatar_eventbox_button_press_event)
+		id = widget.connect('button-press-event',
+			self.on_avatar_eventbox_button_press_event)
 		self.handlers[id] = widget
 
 		widget = self.xml.get_widget('gpg_togglebutton')
@@ -881,12 +903,7 @@ class ChatControl(ChatControlBase):
 		self.update_ui()
 		# restore previous conversation
 		self.restore_conversation()
-		# is account displayed after nick in banner ?
-		self.account_displayed= False
 
-	def notify_on_new_messages(self):
-		return gajim.config.get('trayicon_notification_on_new_messages')
-	
 	def on_avatar_eventbox_enter_notify_event(self, widget, event):
 		'''we enter the eventbox area so we under conditions add a timeout
 		to show a bigger avatar after 0.5 sec'''
@@ -924,7 +941,8 @@ class ChatControl(ChatControlBase):
 			menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
 			id = menuitem.connect('activate', 
 				gtkgui_helpers.on_avatar_save_as_menuitem_activate,
-				self.contact.jid, self.account, self.contact.name + '.jpeg')
+				self.contact.jid, self.account, self.contact.get_shown_name() +  
+					'.jpeg')
 			self.handlers[id] = menuitem
 			menu.append(menuitem)
 			menu.show_all()
@@ -1001,39 +1019,33 @@ class ChatControl(ChatControlBase):
 
 		banner_name_label = self.xml.get_widget('banner_name_label')
 		name = contact.get_shown_name()
-		avoid_showing_account_too = False
 		if self.resource:
 			name += '/' + self.resource
-			avoid_showing_account_too = True
 		if self.TYPE_ID == message_control.TYPE_PM:
-			room_jid = self.contact.jid.split('/')[0]
-			room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid,
-				self.account)
-			name = _('%s from room %s') % (name, room_ctrl.name)
+			name = _('%(nickname)s from group chat %(room_name)s') %\
+				{'nickname': name, 'room_name': self.room_name}
 		name = gtkgui_helpers.escape_for_pango_markup(name)
 
-		# We know our contacts nick, but if there are any other controls 
-		# with the same nick we need to also display the account
+		# We know our contacts nick, but if another contact has the same nick
+		# in another account we need to also display the account.
 		# except if we are talking to two different resources of the same contact
 		acct_info = ''
-		self.account_displayed = False
-		for ctrl in self.parent_win.controls():
-			if ctrl == self:
+		for account in gajim.contacts.get_accounts():
+			if account == self.account:
 				continue
-			if self.contact.get_shown_name() == ctrl.contact.get_shown_name()\
-			and not avoid_showing_account_too:
-				self.account_displayed = True
-				if not ctrl.account_displayed:
-					# do that after this instance exists
-					gobject.idle_add(ctrl.draw_banner)
-				acct_info = ' (%s)' % \
-						gtkgui_helpers.escape_for_pango_markup(self.account)
+			if acct_info: # We already found a contact with same nick
 				break
+			for jid in gajim.contacts.get_jid_list(account):
+				contact_ = gajim.contacts.get_first_contact_from_jid(account, jid)
+				if contact_.get_shown_name() == self.contact.get_shown_name():
+					acct_info = ' (%s)' % \
+						gtkgui_helpers.escape_for_pango_markup(self.account)
+					break
 
 		status = contact.status
 		if status is not None:
 			banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
-			status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 2)
+			status = helpers.reduce_chars_newlines(status, max_lines = 2)
 		status_escaped = gtkgui_helpers.escape_for_pango_markup(status)
 
 		font_attrs, font_attrs_small = self.get_font_attrs()
@@ -1071,8 +1083,8 @@ class ChatControl(ChatControlBase):
 		banner_name_label.set_markup(label_text)
 
 	def on_toggle_gpg_togglebutton(self, widget):
-		gajim.config.set_per('contacts', self.contact.get_full_jid(),
-			'gpg_enabled', widget.get_active())
+		gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled',
+			widget.get_active())
 
 	def _update_gpg(self):
 		tb = self.xml.get_widget('gpg_togglebutton')
@@ -1166,8 +1178,8 @@ class ChatControl(ChatControlBase):
 			if current_state == 'composing':
 				self.send_chatstate('paused') # pause composing
 
-		# assume no activity and let the motion-notify or 'insert-text' make them True
-		# refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
+		# assume no activity and let the motion-notify or 'insert-text' make them
+		# True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
 		self.reset_kbd_mouse_timeout_vars()
 		return True # loop forever		
 
@@ -1186,11 +1198,12 @@ class ChatControl(ChatControlBase):
 		if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
 			return True # loop forever
 
-		if not self.mouse_over_in_last_30_secs or self.kbd_activity_in_last_30_secs:
+		if not self.mouse_over_in_last_30_secs or \
+		self.kbd_activity_in_last_30_secs:
 			self.send_chatstate('inactive', contact)
 
-		# assume no activity and let the motion-notify or 'insert-text' make them True
-		# refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
+		# assume no activity and let the motion-notify or 'insert-text' make them
+		# True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
 		self.reset_kbd_mouse_timeout_vars()
 		return True # loop forever
 
@@ -1201,7 +1214,7 @@ class ChatControl(ChatControlBase):
 		self.kbd_activity_in_last_30_secs = False
 
 	def print_conversation(self, text, frm = '', tim = None,
-		encrypted = False, subject = None):
+		encrypted = False, subject = None, xhtml = None):
 		'''Print a line in the conversation:
 		if contact is set to status: it's a status message
 		if contact is set to another value: it's an outgoing message
@@ -1240,8 +1253,12 @@ class ChatControl(ChatControlBase):
 			else:
 				kind = 'outgoing'
 				name = gajim.nicks[self.account]
+				if not xhtml and not encrypted and gajim.config.get('rst_formatting_outgoing_messages'):
+					xhtml = create_xhtml(text)
+					if xhtml:
+						xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml)
 		ChatControlBase.print_conversation_line(self, text, kind, name, tim,
-			subject = subject, old_kind = self.old_msg_kind)
+			subject = subject, old_kind = self.old_msg_kind, xhtml = xhtml)
 		if text.startswith('/me ') or text.startswith('/me\n'):
 			self.old_msg_kind = None
 		else:
@@ -1276,9 +1293,6 @@ class ChatControl(ChatControlBase):
 			elif chatstate == 'paused':
 				color = gajim.config.get_per('themes', theme,
 						'state_paused_color')
-			else:
-				color = gajim.config.get_per('themes', theme,
-						'state_active_color')
 		if color:
 			# We set the color for when it's the current tab or not
 			color = gtk.gdk.colormap_get_system().alloc_color(color)
@@ -1287,6 +1301,9 @@ class ChatControl(ChatControlBase):
 			if chatstate in ('inactive', 'gone') and\
 			self.parent_win.get_active_control() != self:
 				color = self.lighten_color(color)
+		elif chatstate == 'active' : # active, get color from gtk
+			color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
+		
 
 		name = self.contact.get_shown_name()
 		if self.resource:
@@ -1459,18 +1476,20 @@ class ChatControl(ChatControlBase):
 
 		# prevent going paused if we we were not composing (JEP violation)
 		if state == 'paused' and not contact.our_chatstate == 'composing':
-			MessageControl.send_message(self, None, chatstate = 'active') # go active before
+			# go active before
+			MessageControl.send_message(self, None, chatstate = 'active')
 			contact.our_chatstate = 'active'
 			self.reset_kbd_mouse_timeout_vars()
 		
 		# if we're inactive prevent composing (JEP violation)
 		elif contact.our_chatstate == 'inactive' and state == 'composing':
-			MessageControl.send_message(self, None, chatstate = 'active') # go active before
+			# go active before
+			MessageControl.send_message(self, None, chatstate = 'active')
 			contact.our_chatstate = 'active'
 			self.reset_kbd_mouse_timeout_vars()
 
-		MessageControl.send_message(self, None, chatstate = state, msg_id = contact.msg_id,
-									composing_jep = contact.composing_jep)
+		MessageControl.send_message(self, None, chatstate = state,
+			msg_id = contact.msg_id, composing_jep = contact.composing_jep)
 		contact.our_chatstate = state
 		if contact.our_chatstate == 'active':
 			self.reset_kbd_mouse_timeout_vars()
@@ -1501,7 +1520,7 @@ class ChatControl(ChatControlBase):
 		self.msg_textview.destroy()
 		
 
-	def allow_shutdown(self):
+	def allow_shutdown(self, method):
 		if time.time() - gajim.last_message_time[self.account]\
 		[self.get_full_jid()] < 2:
 			# 2 seconds
@@ -1593,13 +1612,16 @@ class ChatControl(ChatControlBase):
 			return
 		timeout = gajim.config.get('restore_timeout') # in minutes
 
-		events = gajim.events.get_events(self.account, jid, ['chat'])
 		# number of messages that are in queue and are already logged, we want
 		# to avoid duplication
-		pending_how_many = len(events)
+		pending_how_many = len(gajim.events.get_events(self.account, jid,
+			['chat', 'pm']))
+		if self.resource:
+			pending_how_many += len(gajim.events.get_events(self.account,
+				self.contact.get_full_jid(), ['chat', 'pm']))
 
 		rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
-			pending_how_many, timeout, self.account)
+			pending_how_many, timeout, self.account)
 		local_old_kind = None
 		for row in rows: # row[0] time, row[1] has kind, row[2] the message
 			if not row[2]: # message is empty, we don't print it
@@ -1657,7 +1679,7 @@ class ChatControl(ChatControlBase):
 			else:
 				kind = 'print_queue'
 			self.print_conversation(data[0], kind, tim = data[3],
-						encrypted = data[4], subject = data[1])
+				encrypted = data[4], subject = data[1], xhtml = data[7])
 			if len(data) > 6 and isinstance(data[6], int):
 				message_ids.append(data[6])
 		if message_ids:
@@ -1665,30 +1687,19 @@ class ChatControl(ChatControlBase):
 		gajim.events.remove_events(self.account, jid_with_resource,
 			types = [self.type_id])
 
-		self.parent_win.show_title()
-		self.parent_win.redraw_tab(self)
-		# redraw roster
-		gajim.interface.roster.show_title()
-
 		typ = 'chat' # Is it a normal chat or a pm ?
 		# reset to status image in gc if it is a pm
 		if is_pm:
 			control.update_ui()
 			typ = 'pm'
 
-		if is_pm:
-			room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
-			groupchat_control = gajim.interface.msg_win_mgr.get_control(
-				room_jid, self.account)
-			groupchat_control.draw_contact(nick)
-		else:
-			gajim.interface.roster.draw_contact(jid, self.account)
-		# Redraw parent too
-		gajim.interface.roster.draw_parent_contact(jid, self.account)
-		if (self.contact.show == 'offline' or self.contact.show == 'error'):
-			showOffline = gajim.config.get('showoffline')
-			if not showOffline and typ == 'chat' and \
-			len(gajim.contacts.get_contact(self.account, jid)) < 2:
+		self.redraw_after_event_removed(jid)
+		if (self.contact.show in ('offline', 'error')):
+			show_offline = gajim.config.get('showoffline')
+			show_transports = gajim.config.get('show_transports_group')
+			if (not show_transports and gajim.jid_is_transport(jid)) or \
+			(not show_offline and typ == 'chat' and \
+			len(gajim.contacts.get_contact(self.account, jid)) < 2):
 				gajim.interface.roster.really_remove_contact(self.contact,
 					self.account)
 			elif typ == 'pm':
diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py
index bb6753fca8e401295778ae74d948a4aff7e4d379..af38cddf9132bb946368e50bfaf98d0bfff38b7b 100644
--- a/src/common/GnuPG.py
+++ b/src/common/GnuPG.py
@@ -2,13 +2,13 @@
 ##
 ## Contributors for this file:
 ##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <nkour@jabber.org>
+##	- Nikos Kouremenos <kourem@gmail.com>
 ##
 ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
diff --git a/src/common/Makefile b/src/common/Makefile
deleted file mode 100644
index c6d05a2d4e24af78bbb4f91bfbd2b99ac7cdc6a3..0000000000000000000000000000000000000000
--- a/src/common/Makefile
+++ /dev/null
@@ -1,25 +0,0 @@
-# Set the C flags to include the GTK+ and Python libraries
-PYTHON ?= python
-PYTHONVER = `$(PYTHON) -c 'import sys; print sys.version[:3]'`
-
-HAVE_XSCRNSAVER = $(shell pkg-config --exists xscrnsaver && echo 'YES')
-
-ifeq ($(HAVE_XSCRNSAVER),YES)
-# We link with libXScrnsaver from modular X.Org X11
-gtk_and_x_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0 xscrnsaver` -fpic -I/usr/include/python$(PYTHONVER) -I.
-gtk_and_x_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0 xscrnsaver` -lpython$(PYTHONVER)
-else
-# # We link with libXScrnsaver from monolithic X.Org X11
-gtk_and_x_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fpic -I/usr/include/python$(PYTHONVER) -I.
-gtk_and_x_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` \
-	-L/usr/X11R6$(LIBDIR) -lX11 -lXss -lXext -lpython$(PYTHONVER)
-endif
-
-all: idle.so
-
-idle.so:
-	$(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_and_x_CFLAGS) $(gtk_and_x_LDFLAGS) -shared idle.c $^ -o $@
-
-clean:
-	rm -f *.so
-	rm -rf build
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..ff75958c0b3c427b06b6c4782674d089f68e27c4
--- /dev/null
+++ b/src/common/Makefile.am
@@ -0,0 +1,22 @@
+
+INCLUDES = \
+	$(PYTHON_INCLUDES) 
+if BUILD_IDLE
+idlelib_LTLIBRARIES = idle.la
+idlelibdir = $(libdir)/gajim
+
+idle_la_LIBADD = $(XSCRNSAVER_LIBS) 
+
+idle_la_SOURCES = idle.c
+
+idle_la_LDFLAGS = \
+	-module -avoid-version
+
+idle_la_CFLAGS = $(XSCRNSAVER_CFLAGS) $(PYTHON_INCLUDES)
+endif
+
+DISTCLEANFILES = 
+
+EXTRA_DIST = 
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/common/check_paths.py b/src/common/check_paths.py
index 03913473d4837cbff1ecf2ca71384b22fda15b0a..1008e8c4f514ff74e4c6cecf55b1bcd56735676b 100644
--- a/src/common/check_paths.py
+++ b/src/common/check_paths.py
@@ -1,16 +1,7 @@
-## Contributors for this file:
-## - Yann Le Boulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <kourem@gmail.com>
-## - Travis Shirk <travis@pobox.com>
 ##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -29,7 +20,14 @@ import stat
 from common import gajim
 import logger
 
-from pysqlite2 import dbapi2 as sqlite # DO NOT MOVE ABOVE OF import gajim
+# DO NOT MOVE ABOVE OF import gajim
+try:
+	import sqlite3 as sqlite # python 2.5
+except ImportError:
+	try:
+		from pysqlite2 import dbapi2 as sqlite
+	except ImportError:
+		raise exceptions.PysqliteNotAvailable
 
 def create_log_db():
 	print _('creating logs database')
@@ -57,11 +55,13 @@ def create_log_db():
 			jid_id INTEGER
 		);
 		
+		CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
+		
 		CREATE TABLE transports_cache (
 			transport TEXT UNIQUE,
 			type INTEGER
 		);
-																
+		
 		CREATE TABLE logs(
 			log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
 			jid_id INTEGER,
@@ -72,6 +72,8 @@ def create_log_db():
 			message TEXT,
 			subject TEXT
 		);
+		
+		CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
 		'''
 		)
 
diff --git a/src/common/config.py b/src/common/config.py
index 96f51ea6b72eee868a748850bf1cda88d1a42d65..23b997d47af3dab4e3e5f35a567055b15511afed 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -6,7 +6,8 @@
 ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
 ## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
 ## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
-##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+## 
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
 ## by the Free Software Foundation; version 2 only.
@@ -20,6 +21,7 @@
 
 import sre
 import copy
+import defs
 
 
 (
@@ -37,6 +39,8 @@ opt_bool = [ 'boolean', 0 ]
 opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ]
 opt_one_window_types = ['never', 'always', 'peracct', 'pertype']
 
+DEFAULT_ICONSET = 'dcraven'
+
 class Config:
 
 	__options = {
@@ -50,7 +54,8 @@ class Config:
 		'autopopupaway': [ opt_bool, False ],
 		'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ],
 		'ignore_unknown_contacts': [ opt_bool, False ],
-		'showoffline': [ opt_bool, False, '', True ],
+		'showoffline': [ opt_bool, False ],
+		'show_transports_group': [ opt_bool, True ],
 		'autoaway': [ opt_bool, True ],
 		'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ],
 		'autoaway_message': [ opt_str, _('Away as a result of being idle') ],
@@ -67,7 +72,7 @@ class Config:
 		'last_status_msg_invisible': [ opt_str, '' ],
 		'last_status_msg_offline': [ opt_str, '' ],
 		'trayicon': [ opt_bool, True, '', True ],
-		'iconset': [ opt_str, 'dcraven', '', True ],
+		'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
 		'use_transports_iconsets': [ opt_bool, True, '', True ],
 		'inmsgcolor': [ opt_color, '#a34526', '', True ],
 		'outmsgcolor': [ opt_color, '#164e6f', '', True ],
@@ -79,15 +84,19 @@ class Config:
 		'saveposition': [ opt_bool, True ],
 		'mergeaccounts': [ opt_bool, False, '', True ],
 		'sort_by_show': [ opt_bool, True, '', True ],
+		'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')],
 		'use_speller': [ opt_bool, False, ],
+		'ignore_incoming_xhtml': [ opt_bool, False, ],
 		'speller_language': [ opt_str, '', _('Language used by speller')],
 		'print_time': [ opt_str, 'always',  _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
-		'print_time_fuzzy': [ opt_int, 0, _('Value of fuzziness from 1 to 4 or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the less precise one.') ],
+		'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the less precise one. This is used only if print_time is \'sometimes\'.') ],
 		'emoticons_theme': [opt_str, 'static', '', True ],
 		'ascii_formatting': [ opt_bool, True,
 			_('Treat * / _ pairs as possible formatting characters.'), True],
 		'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not '
 			'remove */_ . So *abc* will be bold but with * * not removed.')],
+		'rst_formatting_outgoing_messages': [ opt_bool, False,
+			_('Uses ReStructured text markup for HTML, plus ascii formatting if selected. (If you want to use this, install docutils)')],
 		'sounds_on': [ opt_bool, True ],
 		# 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
 		'soundplayer': [ opt_str, '' ],
@@ -125,6 +134,7 @@ class Config:
 		'before_nickname': [ opt_str, '' ],
 		'after_nickname': [ opt_str, ':' ],
 		'send_os_info': [ opt_bool, True ],
+		'set_status_msg_from_current_music_track': [ opt_bool, False ],
 		'notify_on_new_gmail_email': [ opt_bool, True ],
 		'notify_on_new_gmail_email_extra': [ opt_bool, False ],
 		'usegpg': [ opt_bool, False, '', True ],
@@ -135,25 +145,26 @@ class Config:
 		'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
 		'show_roster_on_startup': [opt_bool, True],
 		'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
-		'version': [ opt_str, '0.10.1.3' ], # which version created the config
+		'version': [ opt_str, defs.version ], # which version created the config
 		'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'],
 		'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
 		'always_english_wikipedia': [opt_bool, False],
 		'always_english_wiktionary': [opt_bool, True],
 		'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
+		'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True],
 		'chat_state_notifications': [opt_str, 'all'], # 'all', 'composing_only', 'disabled'
 		'autodetect_browser_mailer': [opt_bool, False, '', True],
 		'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
 		'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
-		'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of room jids.')],
-		'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of room jids.')],
+		'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')],
+		'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')],
 		'notify_on_file_complete': [opt_bool, True],
 		'file_transfers_port': [opt_int, 28011],
 		'ft_override_host_to_send': [opt_str, '', _('Overrides the host we send for File Transfer in case of address translation/port forwarding.')], 
 		'conversation_font': [opt_str, ''],
 		'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')],
 		'notify_on_all_muc_messages': [opt_bool, False],
-		'trayicon_notification_on_new_messages': [opt_bool, True],
+		'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')],
 		'last_save_dir': [opt_str, ''],
 		'last_send_dir': [opt_str, ''],
 		'last_emoticons_dir': [opt_str, ''],
@@ -174,7 +185,7 @@ class Config:
 		'notification_position_y': [opt_int, -1],
 		'notification_avatar_width': [opt_int, 48],
 		'notification_avatar_height': [opt_int, 48],
-		'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in multi-user chat.')],
+		'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')],
 		'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')],
 		'set_xmpp://_handler_everytime': [opt_bool, False, _('If True, Gajim registers for xmpp:// on each startup.')],
 		'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
@@ -182,15 +193,15 @@ class Config:
 		'show_avatars_in_roster': [opt_bool, True, '', True],
 		'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
 		'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
-		'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", gajim will only print FOO enters/leaves room.')],
+		'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", gajim will only print FOO enters/leaves group chat.')],
 		'log_contact_status_changes': [opt_bool, False],
 		'restored_messages_color': [opt_str, 'grey'],
 		'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')],
 		'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')],
-		'roster_window_skip_taskbar': [opt_bool, False],
+		'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')],
 		'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')],
 		'notification_timeout': [opt_int, 5],
-		'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected room. Turn this option to False to stop sending sha info in group chat presences.')],
+		'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
 		'one_message_window': [opt_str, 'always',
 #always, never, peracct, pertype should not be translated
 			_('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window. Note, changing this option requires restarting Gajim before the changes will take effect.')],
@@ -200,11 +211,12 @@ class Config:
 		'always_hide_chat_buttons': [opt_bool, False, _('Hides the buttons in two persons chat window.')],
 		'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
 		'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
-		'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the room occupants list in group chat window.')],
-		'chat_merge_consecutive_nickname': [opt_bool, False, _('Merge consecutive nickname in chat window.')],
+		'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
+		'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
 		'chat_merge_consecutive_nickname_indent': [opt_str, '  ', _('Indentation when using merge consecutive nickame.')],
 		'gc_nicknames_colors': [ opt_str, '#a34526:#c000ff:#0012ff:#388a99:#38995d:#519938:#ff8a00:#94452d:#244b5a:#32645a', _('List of colors that will be used to color nicknames in group chats.'), True ],
 		'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
+		'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
 	}
 
 	__options_per_key = {
@@ -215,6 +227,13 @@ class Config:
 			'password': [ opt_str, '' ],
 			'resource': [ opt_str, 'gajim', '', True ],
 			'priority': [ opt_int, 5, '', True ],
+			'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ],
+			'autopriority_online': [ opt_int, 50],
+			'autopriority_chat': [ opt_int, 50],
+			'autopriority_away': [ opt_int, 40],
+			'autopriority_xa': [ opt_int, 30],
+			'autopriority_dnd': [ opt_int, 20],
+			'autopriority_invisible': [ opt_int, 10],
 			'autoconnect': [ opt_bool, False, '', True ],
 			'autoreconnect': [ opt_bool, True ],
 			'active': [ opt_bool, True],
@@ -246,6 +265,12 @@ class Config:
 			'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
 			'msgwin-width': [opt_int, 480],
 			'msgwin-height': [opt_int, 440],
+			'listen_to_network_manager' : [opt_bool, True],
+			'is_zeroconf': [opt_bool, False],
+			'zeroconf_first_name': [ opt_str, '', '', True ],
+			'zeroconf_last_name': [ opt_str, '', '', True ],
+			'zeroconf_jabber_id': [ opt_str, '', '', True ],
+			'zeroconf_email': [ opt_str, '', '', True ],
 		}, {}),
 		'statusmsg': ({
 			'message': [ opt_str, '' ],
@@ -284,8 +309,6 @@ class Config:
 			'bannerfontattrs': [ opt_str, 'B', '', True ],
 				
 			# http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
-			# FIXME: not black but the default color from gtk+ theme
-			'state_active_color': [ opt_color, 'black' ],
 			'state_inactive_color': [ opt_color, 'grey62' ],
 			'state_composing_color': [ opt_color, 'green4' ],
 			'state_paused_color': [ opt_color, 'mediumblue' ],
@@ -327,15 +350,15 @@ class Config:
 		_('Movie'): _("I'm watching a movie."),
 		_('Working'): _("I'm working."),
 		_('Phone'): _("I'm on the phone."),
-		_('Out'): _("I'm out enjoying life"),
+		_('Out'): _("I'm out enjoying life."),
 	}
 
 	defaultstatusmsg_default = {
-		'online': [ False, _("I'm available") ],
-		'chat': [ False, _("I'm free for chat") ],
-		'away': [ False, _('Be right back') ],
-		'xa': [ False, _("I'm not available") ],
-		'dnd': [ False, _('Do not disturb') ],
+		'online': [ False, _("I'm available.") ],
+		'chat': [ False, _("I'm free for chat.") ],
+		'away': [ False, _('Be right back.') ],
+		'xa': [ False, _("I'm not available.") ],
+		'dnd': [ False, _('Do not disturb.') ],
 		'invisible': [ False, _('Bye!') ],
 		'offline': [ False, _('Bye!') ],
 	}
@@ -346,8 +369,8 @@ class Config:
 		'contact_connected': [ True, '../data/sounds/connected.wav' ],
 		'contact_disconnected': [ True, '../data/sounds/disconnected.wav' ],
 		'message_sent': [ True, '../data/sounds/sent.wav' ],
-		'muc_message_highlight': [ True, '../data/sounds/gc_message1.wav', _('Sound to play when a MUC message contains one of the words in muc_highlight_words, or when a MUC message contains your nickname.')],
-		'muc_message_received': [ True, '../data/sounds/gc_message2.wav', _('Sound to play when any MUC message arrives. (This setting is taken into account only if notify_on_all_muc_messages is True)') ],
+		'muc_message_highlight': [ True, '../data/sounds/gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')],
+		'muc_message_received': [ False, '../data/sounds/gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
 	}
 
 	themes_default = {
diff --git a/src/common/configpaths.py b/src/common/configpaths.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fce3d1dac646d310c47137834f3339ff1c074d7
--- /dev/null
+++ b/src/common/configpaths.py
@@ -0,0 +1,115 @@
+import os
+import sys
+import tempfile
+
+# Note on path and filename encodings:
+#
+# In general it is very difficult to do this correctly.
+# We may pull information from environment variables, and what encoding that is
+# in is anyone's guess. Any information we request directly from the file
+# system will be in filesystemencoding, and (parts of) paths that we write in
+# this source code will be in whatever encoding the source is in. (I hereby
+# declare this file to be UTF-8 encoded.)
+#
+# To make things more complicated, modern Windows filesystems use UTF-16, but
+# the API tends to hide this from us.
+#
+# I tried to minimize problems by passing Unicode strings to OS functions as
+# much as possible. Hopefully this makes the function return an Unicode string
+# as well. If not, we get an 8-bit string in filesystemencoding, which we can
+# happily pass to functions that operate on files and directories, so we can
+# just leave it as is. Since these paths are meant to be internal to Gajim and
+# not displayed to the user, Unicode is not really necessary here.
+
+def fse(s):
+	'''Convert from filesystem encoding if not already Unicode'''
+	return unicode(s, sys.getfilesystemencoding())
+
+class ConfigPaths:
+	def __init__(this, root=None):
+		this.root = root
+		this.paths = {}
+
+		if this.root is None:
+			if os.name == 'nt':
+				try:
+					# Documents and Settings\[User Name]\Application Data\Gajim
+
+					# How are we supposed to know what encoding the environment
+					# variable 'appdata' is in? Assuming it to be in filesystem
+					# encoding.
+					this.root = os.path.join(fse(os.environ[u'appdata']), u'Gajim')
+				except KeyError:
+					# win9x, in cwd
+					this.root = u''
+			else: # Unices
+				# Pass in an Unicode string, and hopefully get one back.
+				this.root = os.path.expanduser(u'~/.gajim')
+
+	def add_from_root(this, name, path):
+		this.paths[name] = (True, path)
+
+	def add(this, name, path):
+		this.paths[name] = (False, path)
+
+	def __getitem__(this, key):
+		relative, path = this.paths[key]
+		if not relative:
+			return path
+		return os.path.join(this.root, path)
+
+	def get(this, key, default=None):
+		try:
+			return this[key]
+		except KeyError:
+			return default
+
+	def iteritems(this):
+		for key in this.paths.iterkeys():
+			yield (key, this[key])
+
+def windowsify(s):
+	if os.name == 'nt':
+		return s.capitalize()
+	return s
+
+def init():
+	paths = ConfigPaths()
+
+	# LOG is deprecated
+	k = ( 'LOG',   'LOG_DB',   'VCARD',   'AVATAR',   'MY_EMOTS' )
+	v = (u'logs', u'logs.db', u'vcards', u'avatars', u'emoticons')
+
+	if os.name == 'nt':
+		v = map(lambda x: x.capitalize(), v)
+
+	for n, p in zip(k, v):
+		paths.add_from_root(n, p)
+
+	paths.add('DATA', os.path.join(u'..', windowsify(u'data')))
+	paths.add('HOME', os.path.expanduser(u'~'))
+	paths.add('TMP', fse(tempfile.gettempdir()))
+
+	try:
+		import svn_config
+		svn_config.configure(paths)
+	except (ImportError, AttributeError):
+		pass
+
+	#for k, v in paths.iteritems():
+	#	print "%s: %s" % (k, v)
+
+	return paths
+
+gajimpaths = init()
+
+def init_profile(profile, paths=gajimpaths):
+	conffile = windowsify(u'config')
+	pidfile = windowsify(u'gajim')
+
+	if len(profile) > 0:
+		conffile += u'.' + profile
+		pidfile += u'.' + profile
+	pidfile += u'.pid'
+	paths.add_from_root('CONFIG_FILE', conffile)
+	paths.add_from_root('PID_FILE', pidfile)
diff --git a/src/common/connection.py b/src/common/connection.py
index f270cceef9ea75d65a7ae28042b23efeef0507a1..771fb31b9d9ed04827f18b8fec99f6454a0bf32b 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -1,19 +1,11 @@
 ##	common/connection.py
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <nkour@jabber.org>
-##	- Dimitur Kirov <dkirov@gmail.com>
-##	- Travis Shirk <travis@pobox.com>
 ##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org>
+## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
+## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -38,10 +30,13 @@ import common.xmpp
 from common import helpers
 from common import gajim
 from common import GnuPG
+from common import passwords
 
 from connection_handlers import *
 USE_GPG = GnuPG.USE_GPG
 
+from common.rst_xhtml_generator import create_xhtml
+
 class Connection(ConnectionHandlers):
 	'''Connection class'''
 	def __init__(self, name):
@@ -51,8 +46,10 @@ class Connection(ConnectionHandlers):
 		self.connection = None # xmpppy ClientCommon instance
 		# this property is used to prevent double connections
 		self.last_connection = None # last ClientCommon instance
+		self.is_zeroconf = False
 		self.gpg = None
 		self.status = ''
+		self.priority = gajim.get_priority(name, 'offline')
 		self.old_show = ''
 		# increase/decrease default timeout for server responses
 		self.try_connecting_for_foo_secs = 45
@@ -61,11 +58,12 @@ class Connection(ConnectionHandlers):
 		self.time_to_reconnect = None
 		self.new_account_info = None
 		self.bookmarks = []
+		self.annotations = {}
 		self.on_purpose = False
 		self.last_io = gajim.idlequeue.current_time()
 		self.last_sent = []
 		self.last_history_line = {}
-		self.password = gajim.config.get_per('accounts', name, 'password')
+		self.password = passwords.get_password(name)
 		self.server_resource = gajim.config.get_per('accounts', name, 'resource')
 		if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
 			self.keepalives = gajim.config.get_per('accounts', self.name,'keep_alive_every_foo_secs')
@@ -88,6 +86,7 @@ class Connection(ConnectionHandlers):
 		self.available_transports = {} # list of available transports on this
 		# server {'icq': ['icq.server.com', 'icq2.server.com'], }
 		self.vcard_supported = True
+		self.metacontacts_supported = True
 	# END __init__
 
 	def put_event(self, ev):
@@ -118,6 +117,7 @@ class Connection(ConnectionHandlers):
 		self.on_purpose = on_purpose
 		self.connected = 0
 		self.time_to_reconnect = None
+		self.privacy_rules_supported = False
 		if self.connection:
 			# make sure previous connection is completely closed
 			gajim.proxy65_manager.disconnect(self.connection)
@@ -128,23 +128,22 @@ class Connection(ConnectionHandlers):
 	def _disconnectedReconnCB(self):
 		'''Called when we are disconnected'''
 		gajim.log.debug('disconnectedReconnCB')
-		if self.connected > 1:
-			# we cannot change our status to offline or connectiong
+		if gajim.account_is_connected(self.name):
+			# we cannot change our status to offline or connecting
 			# after we auth to server
 			self.old_show = STATUS_LIST[self.connected]
 		self.connected = 0
 		self.dispatch('STATUS', 'offline')
 		if not self.on_purpose:
 			self.disconnect()
-			if gajim.config.get_per('accounts', self.name, 'autoreconnect') \
-			and self.retrycount <= 10:
+			if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
 				self.connected = 1
 				self.dispatch('STATUS', 'connecting')
 				# this check has moved from _reconnect method
 				if self.retrycount > 5:
-					self.time_to_reconnect = 20
+					self.time_to_reconnect = random.randint(15, 25)
 				else:
-					self.time_to_reconnect = 10
+					self.time_to_reconnect = random.randint(5, 15)
 				gajim.idlequeue.set_alarm(self._reconnect_alarm,
 					self.time_to_reconnect)
 			elif self.on_connect_failure:
@@ -163,7 +162,7 @@ class Connection(ConnectionHandlers):
 		self.dispatch('STATUS', 'offline')
 		self.dispatch('CONNECTION_LOST',
 			(_('Connection with account "%s" has been lost') % self.name,
-			_('To continue sending and receiving messages, you will need to reconnect.')))
+			_('Reconnect manually.')))
 
 	def _event_dispatcher(self, realm, event, data):
 		if realm == common.xmpp.NS_REGISTER:
@@ -185,7 +184,6 @@ class Connection(ConnectionHandlers):
 						if not common.xmpp.isResultNode(result):
 							self.dispatch('ACC_NOT_OK', (result.getError()))
 							return
-						self.connected = 0
 						self.password = self.new_account_info['password']
 						if USE_GPG:
 							self.gpg = GnuPG.GnuPG()
@@ -195,7 +193,9 @@ class Connection(ConnectionHandlers):
 						gajim.connections[self.name] = self
 						self.dispatch('ACC_OK', (self.new_account_info))
 						self.new_account_info = None
-						self.connection = None
+						if self.connection:
+							self.connection.UnregisterDisconnectHandler(self._on_new_account)
+						self.disconnect(on_purpose=True)
 					common.xmpp.features_nb.register(self.connection, data[0],
 						req, _on_register_result)
 					return
@@ -368,8 +368,7 @@ class Connection(ConnectionHandlers):
 				secure = self._secure)
 			return
 		else:
-			if not retry or self.retrycount > 10:
-				self.retrycount = 0
+			if not retry and self.retrycount == 0:
 				self.time_to_reconnect = None
 				if self.on_connect_failure:
 					self.on_connect_failure()
@@ -404,8 +403,6 @@ class Connection(ConnectionHandlers):
 		con.RegisterDisconnectHandler(self._disconnectedReconnCB)
 		gajim.log.debug(_('Connected to server %s:%s with %s') % (self._current_host['host'],
 			self._current_host['port'], con_type))
-		# Ask metacontacts before roster
-		self.get_metacontacts()
 		self._register_handlers(con, con_type)
 		return True
 
@@ -459,7 +456,7 @@ class Connection(ConnectionHandlers):
 	# END connect
 
 	def quit(self, kill_core):
-		if kill_core and self.connected > 1:
+		if kill_core and gajim.account_is_connected(self.name):
 			self.disconnect(on_purpose = True)
 	
 	def get_privacy_lists(self):
@@ -531,14 +528,15 @@ class Connection(ConnectionHandlers):
 			# active the privacy rule
 			self.privacy_rules_supported = True
 			self.activate_privacy_rule('invisible')
-		prio = unicode(gajim.config.get_per('accounts', self.name, 'priority'))
-		p = common.xmpp.Presence(typ = ptype, priority = prio, show = show)
+		priority = unicode(gajim.get_priority(self.name, show))
+		p = common.xmpp.Presence(typ = ptype, priority = priority, show = show)
 		p = self.add_sha(p, ptype != 'unavailable')
 		if msg:
 			p.setStatus(msg)
 		if signed:
 			p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
 		self.connection.send(p)
+		self.priority = priority
 		self.dispatch('STATUS', 'invisible')
 		if initial:
 			#ask our VCard
@@ -546,6 +544,9 @@ class Connection(ConnectionHandlers):
 
 			#Get bookmarks from private namespace
 			self.get_bookmarks()
+			
+			#Get annotations
+			self.get_annotations()
 
 			#Inform GUI we just signed in
 			self.dispatch('SIGNED_IN', ())
@@ -591,8 +592,11 @@ class Connection(ConnectionHandlers):
 		if self.connection:
 			con.set_send_timeout(self.keepalives, self.send_keepalive)
 			self.connection.onreceive(None)
-			# Ask metacontacts before roster
-			self.get_metacontacts()
+			iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
+			id = self.connection.getAnID()
+			iq.setID(id)
+			self.awaiting_answers[id] = (PRIVACY_ARRIVED, )
+			self.connection.send(iq)
 
 	def change_status(self, show, msg, auto = False):
 		if not show in STATUS_LIST:
@@ -646,9 +650,8 @@ class Connection(ConnectionHandlers):
 				iq = self.build_privacy_rule('visible', 'allow')
 				self.connection.send(iq)
 				self.activate_privacy_rule('visible')
-			prio = unicode(gajim.config.get_per('accounts', self.name,
-				'priority'))
-			p = common.xmpp.Presence(typ = None, priority = prio, show = sshow)
+			priority = unicode(gajim.get_priority(self.name, sshow))
+			p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
 			p = self.add_sha(p)
 			if msg:
 				p.setStatus(msg)
@@ -656,6 +659,7 @@ class Connection(ConnectionHandlers):
 				p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
 			if self.connection:
 				self.connection.send(p)
+				self.priority = priority
 			self.dispatch('STATUS', show)
 
 	def _on_disconnected(self):
@@ -666,17 +670,22 @@ class Connection(ConnectionHandlers):
 	def get_status(self):
 		return STATUS_LIST[self.connected]
 
-	def send_motd(self, jid, subject = '', msg = ''):
+
+	def send_motd(self, jid, subject = '', msg = '', xhtml = None):
 		if not self.connection:
 			return
-		msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject)
+		msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
+			xhtml = xhtml)
+
 		self.connection.send(msg_iq)
 
 	def send_message(self, jid, msg, keyID, type = 'chat', subject='',
 	chatstate = None, msg_id = None, composing_jep = None, resource = None,
-	user_nick = None):
+	user_nick = None, xhtml = None):
 		if not self.connection:
 			return
+		if msg and not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
+			xhtml = create_xhtml(msg)
 		if not msg and chatstate is None:
 			return
 		fjid = jid
@@ -690,18 +699,24 @@ class Connection(ConnectionHandlers):
 			if msgenc:
 				msgtxt = '[This message is encrypted]'
 				lang = os.getenv('LANG')
-				if lang is not None or lang != 'en': # we're not english
-					msgtxt = _('[This message is encrypted]') +\
-						' ([This message is encrypted])' # one  in locale and one en
+				if lang is not None and lang != 'en': # we're not english
+					# one  in locale and one en
+					msgtxt = _('[This message is *encrypted* (See :JEP:`27`]') +\
+						' ([This message is *encrypted* (See :JEP:`27`])'
+		if msgtxt and not xhtml and gajim.config.get(
+			'rst_formatting_outgoing_messages'):
+			# Generate a XHTML part using reStructured text markup
+			xhtml = create_xhtml(msgtxt)
 		if type == 'chat':
-			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
+			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type,
+				xhtml = xhtml)
 		else:
 			if subject:
 				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
-					typ = 'normal', subject = subject)
+					typ = 'normal', subject = subject, xhtml = xhtml)
 			else:
 				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
-					typ = 'normal')
+					typ = 'normal', xhtml = xhtml)
 		if msgenc:
 			msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
 
@@ -719,7 +734,8 @@ class Connection(ConnectionHandlers):
 				msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
 			if composing_jep == 'JEP-0022' or not composing_jep:
 				# JEP-0022
-				chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
+				chatstate_node = msg_iq.setTag('x',
+					namespace = common.xmpp.NS_EVENT)
 				if not msgtxt: # when no <body>, add <id>
 					if not msg_id: # avoid putting 'None' in <id> tag
 						msg_id = ''
@@ -729,7 +745,8 @@ class Connection(ConnectionHandlers):
 					chatstate_node.addChild(name = 'composing') 
 
 		self.connection.send(msg_iq)
-		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
+		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
+			.split()
 		ji = gajim.get_jid_without_resource(jid)
 		if self.name not in no_log_for and ji not in no_log_for:
 			log_msg = msg
@@ -832,7 +849,7 @@ class Connection(ConnectionHandlers):
 		if self.connection:
 			self.connection.getRoster().setItem(jid = jid, name = name,
 				groups = groups)
-	
+
 	def new_account(self, name, config, sync = False):
 		# If a connection already exist we cannot create a new account
 		if self.connection:
@@ -851,6 +868,8 @@ class Connection(ConnectionHandlers):
 			return
 		self.on_connect_failure = None
 		self.connection = con
+		if con:
+			con.RegisterDisconnectHandler(self._on_new_account)
 		common.xmpp.features_nb.getRegInfo(con, self._hostname)
 
 	def account_changed(self, new_name):
@@ -914,14 +933,39 @@ class Connection(ConnectionHandlers):
 			# Only add optional elements if not empty
 			# Note: need to handle both None and '' as empty
 			#   thus shouldn't use "is not None"
-			if bm['nick']:
+			if bm.get('nick', None):
 				iq5 = iq4.setTagData('nick', bm['nick'])
-			if bm['password']:
+			if bm.get('password', None):
 				iq5 = iq4.setTagData('password', bm['password'])
-			if bm['print_status']:
+			if bm.get('print_status', None):
 				iq5 = iq4.setTagData('print_status', bm['print_status'])
 		self.connection.send(iq)
 
+	def get_annotations(self):
+		'''Get Annonations from storage as described in XEP 0048, and XEP 0145'''
+		self.annotations = {}
+		if not self.connection:
+			return
+		iq = common.xmpp.Iq(typ='get')
+		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
+		iq2.addChild(name='storage', namespace='storage:rosternotes')
+		self.connection.send(iq)
+
+	def store_annotations(self):
+		'''Set Annonations in private storage as described in XEP 0048, and XEP 0145'''
+		if not self.connection:
+			return
+		iq = common.xmpp.Iq(typ='set')
+		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
+		iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
+		for jid in self.annotations.keys():
+			if self.annotations[jid]:
+				iq4 = iq3.addChild(name = "note")
+				iq4.setAttr('jid', jid)
+				iq4.setData(self.annotations[jid])
+		self.connection.send(iq)
+
+
 	def get_metacontacts(self):
 		'''Get metacontacts list from storage as described in JEP 0049'''
 		if not self.connection:
@@ -958,14 +1002,15 @@ class Connection(ConnectionHandlers):
 		p = self.add_sha(p, ptype != 'unavailable')
 		self.connection.send(p)
 
-	def join_gc(self, nick, room, server, password):
+	def join_gc(self, nick, room_jid, password):
+		# FIXME: This room JID needs to be normalized; see #1364
 		if not self.connection:
 			return
 		show = helpers.get_xmpp_show(STATUS_LIST[self.connected])
 		if show == 'invisible':
 			# Never join a room when invisible
 			return
-		p = common.xmpp.Presence(to = '%s@%s/%s' % (room, server, nick),
+		p = common.xmpp.Presence(to = '%s/%s' % (room_jid, nick),
 			show = show, status = self.status)
 		if gajim.config.get('send_sha_in_gc_presence'):
 			p = self.add_sha(p)
@@ -974,17 +1019,18 @@ class Connection(ConnectionHandlers):
 			t.setTagData('password', password)
 		self.connection.send(p)
 		#last date/time in history to avoid duplicate
-		# FIXME: This JID needs to be normalized; see #1364
-		jid='%s@%s' % (room, server)
-		last_log = gajim.logger.get_last_date_that_has_logs(jid, is_room = True)
+		last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
+			is_room = True)
 		if last_log is None:
 			last_log = 0
-		self.last_history_line[jid]= last_log
+		self.last_history_line[room_jid]= last_log
 
-	def send_gc_message(self, jid, msg):
+	def send_gc_message(self, jid, msg, xhtml = None):
 		if not self.connection:
 			return
-		msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat')
+		if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
+			xhtml = create_xhtml(msg)
+		msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
 		self.connection.send(msg_iq)
 		self.dispatch('MSGSENT', (jid, msg))
 
@@ -1110,11 +1156,11 @@ class Connection(ConnectionHandlers):
 		self.connection.send(iq)
 
 	def unregister_account(self, on_remove_success):
-		# no need to write this as a class method and keep the value of on_remove_success
-		# as a class property as pass it as an argument
+		# no need to write this as a class method and keep the value of
+		# on_remove_success as a class property as pass it as an argument
 		def _on_unregister_account_connect(con):
 			self.on_connect_auth = None
-			if self.connected > 1:
+			if gajim.account_is_connected(self.name):
 				hostname = gajim.config.get_per('accounts', self.name, 'hostname')
 				iq = common.xmpp.Iq(typ = 'set', to = hostname)
 				q = iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove')
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index d87883bbb41592886cc54c28575beaa1247cf2f4..9c56796fd31f366ea3013a40d730de5f87ce0f0d 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -3,7 +3,7 @@
 ##
 ## Contributors for this file:
 ##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <nkour@jabber.org>
+##	- Nikos Kouremenos <kourem@gmail.com>
 ##	- Dimitur Kirov <dkirov@gmail.com>
 ##	- Travis Shirk <travis@pobox.com>
 ##
@@ -45,15 +45,13 @@ VCARD_PUBLISHED = 'vcard_published'
 VCARD_ARRIVED = 'vcard_arrived'
 AGENT_REMOVED = 'agent_removed'
 METACONTACTS_ARRIVED = 'metacontacts_arrived'
+PRIVACY_ARRIVED = 'privacy_arrived'
 HAS_IDLE = True
 try:
-	import common.idle as idle # when we launch gajim from sources
+	import idle
 except:
-	try:
-		import idle # when Gajim is installed
-	except:
-		gajim.log.debug(_('Unable to load idle module'))
-		HAS_IDLE = False
+	gajim.log.debug(_('Unable to load idle module'))
+	HAS_IDLE = False
 
 class ConnectionBytestream:
 	def __init__(self):
@@ -94,7 +92,7 @@ class ConnectionBytestream:
 			if contact.jid == receiver_jid:
 				file_props['error'] = -5
 				self.remove_transfer(file_props)
-				self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
+				self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
 			sender_jid = unicode(file_props['sender']).split('/')[0]
 			if contact.jid == sender_jid:
 				file_props['error'] = -3
@@ -179,11 +177,12 @@ class ConnectionBytestream:
 		except socket.gaierror:
 			self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.')))
 			ft_override_host_to_send = self.peerhost[0]
-		listener = gajim.socks5queue.start_listener(self.peerhost[0], port,
+		listener = gajim.socks5queue.start_listener(port,
 			sha_str, self._result_socks5_sid, file_props['sid'])
 		if listener == None:
 			file_props['error'] = -5
-			self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props))
+			self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props,
+				''))
 			self._connect_error(unicode(receiver), file_props['sid'],
 				file_props['sid'], code = 406)
 			return
@@ -225,8 +224,8 @@ class ConnectionBytestream:
 		iq = common.xmpp.Protocol(name = 'iq',
 			to = unicode(file_props['sender']), typ = 'error')
 		iq.setAttr('id', file_props['request-id'])
-		err = common.xmpp.ErrorNode(code = '406', typ = 'auth', name =
-			'not-acceptable')
+		err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name =
+			'forbidden', text = 'Offer Declined')
 		iq.addChild(node=err)
 		self.connection.send(iq)
 
@@ -318,8 +317,8 @@ class ConnectionBytestream:
 			if file_props is not None:
 				self.disconnect_transfer(file_props)
 				file_props['error'] = -3
-				self.dispatch('FILE_REQUEST_ERROR', (to, file_props))
-	
+				self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
+
 	def _proxy_auth_ok(self, proxy):
 		'''cb, called after authentication to proxy server '''
 		file_props = self.files_props[proxy['sid']]
@@ -348,7 +347,7 @@ class ConnectionBytestream:
 			return
 		file_props = self.files_props[id]
 		file_props['error'] = -4
-		self.dispatch('FILE_REQUEST_ERROR', (jid, file_props))
+		self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
 		raise common.xmpp.NodeProcessed
 	
 	def _bytestreamSetCB(self, con, iq_obj):
@@ -565,7 +564,7 @@ class ConnectionBytestream:
 			return
 		jid = helpers.get_jid_from_iq(iq_obj)
 		file_props['error'] = -3
-		self.dispatch('FILE_REQUEST_ERROR', (jid, file_props))
+		self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
 		raise common.xmpp.NodeProcessed
 
 class ConnectionDisco:
@@ -696,6 +695,13 @@ class ConnectionDisco:
 			attr = {}
 			for key in i.getAttrs():
 				attr[key] = i.getAttrs()[key]
+			if 'jid' not in attr:
+				continue
+			try:
+				helpers.parse_jid(attr['jid'])
+			except common.helpers.InvalidFormat:
+				# jid is not conform
+				continue
 			items.append(attr)
 		jid = helpers.get_full_jid_from_iq(iq_obj)
 		hostname = gajim.config.get_per('accounts', self.name, 
@@ -732,6 +738,7 @@ class ConnectionDisco:
 			q.addChild('feature', attrs = {'var': common.xmpp.NS_SI})
 			q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE})
 			q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC})
+			q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM})
 			self.connection.send(iq)
 			raise common.xmpp.NodeProcessed
 
@@ -839,6 +846,8 @@ class ConnectionVcard:
 		puny_jid = helpers.sanitize_filename(jid)
 		path = os.path.join(gajim.VCARD_PATH, puny_jid)
 		if jid in self.room_jids or os.path.isdir(path):
+			if not nick:
+				return
 			# remove room_jid file if needed
 			if os.path.isfile(path):
 				os.remove(path)
@@ -976,9 +985,7 @@ class ConnectionVcard:
 					'invisible':
 					self.vcard_sha = new_sha
 					sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected])
-					prio = unicode(gajim.config.get_per('accounts', self.name,
-						'priority'))
-					p = common.xmpp.Presence(typ = None, priority = prio,
+					p = common.xmpp.Presence(typ = None, priority = self.priority,
 						show = sshow, status = self.status)
 					p = self.add_sha(p)
 					self.connection.send(p)
@@ -1023,8 +1030,15 @@ class ConnectionVcard:
 					else:
 						meta_list[tag] = [data]
 				self.dispatch('METACONTACTS', meta_list)
+			else:
+				self.metacontacts_supported = False
 			# We can now continue connection by requesting the roster
 			self.connection.initRoster()
+		elif self.awaiting_answers[id][0] == PRIVACY_ARRIVED:
+			if iq_obj.getType() != 'error':
+				self.privacy_rules_supported = True
+			# Ask metacontacts before roster
+			self.get_metacontacts()
 
 		del self.awaiting_answers[id]
 	
@@ -1104,10 +1118,8 @@ class ConnectionVcard:
 				if STATUS_LIST[self.connected] == 'invisible':
 					return
 				sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected])
-				prio = unicode(gajim.config.get_per('accounts', self.name,
-					'priority'))
-				p = common.xmpp.Presence(typ = None, priority = prio, show = sshow,
-					status = self.status)
+				p = common.xmpp.Presence(typ = None, priority = self.priority,
+					show = sshow, status = self.status)
 				p = self.add_sha(p)
 				self.connection.send(p)
 			else:
@@ -1137,11 +1149,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 	
 	def build_http_auth_answer(self, iq_obj, answer):
 		if answer == 'yes':
-			iq = iq_obj.buildReply('result')
+			self.connection.send(iq_obj.buildReply('result'))
 		elif answer == 'no':
-			iq = iq_obj.buildReply('error')
-			iq.setError('not-authorized', 401)
-		self.connection.send(iq)
+			err = common.xmpp.Error(iq_obj,
+				common.xmpp.protocol.ERR_NOT_AUTHORIZED)
+			self.connection.send(err)
 	
 	def _HttpAuthCB(self, con, iq_obj):
 		gajim.log.debug('HttpAuthCB')
@@ -1156,7 +1168,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 		raise common.xmpp.NodeProcessed
 
 	def _ErrorCB(self, con, iq_obj):
-		errmsg = iq_obj.getError()
+		gajim.log.debug('ErrorCB')
+		if iq_obj.getQueryNS() == common.xmpp.NS_VERSION:
+			who = helpers.get_full_jid_from_iq(iq_obj)
+			jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+			self.dispatch('OS_INFO', (jid_stripped, resource, '', ''))
+			return
+		errmsg = iq_obj.getErrorMsg()
 		errcode = iq_obj.getErrorCode()
 		jid_from = helpers.get_full_jid_from_iq(iq_obj)
 		id = unicode(iq_obj.getID())
@@ -1197,6 +1215,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 				# http://www.jabber.org/jeps/jep-0049.html
 				#TODO: implement this
 				pass
+			elif ns == 'storage:rosternotes':
+				# Annotations
+				# http://www.xmpp.org/extensions/xep-0145.html
+				notes = storage.getTags('note')
+				for note in notes:
+					jid = note.getAttr('jid')
+					annotation = note.getData()
+					self.annotations[jid] = annotation
 
 	def _PrivateErrorCB(self, con, iq_obj):
 		gajim.log.debug('PrivateErrorCB')
@@ -1205,6 +1231,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 		if storage_tag:
 			ns = storage_tag.getNamespace()
 			if ns == 'storage:metacontacts':
+				self.metacontacts_supported = False
 				# Private XML Storage (JEP49) is not supported by server
 				# Continue connecting
 				self.connection.initRoster()
@@ -1312,7 +1339,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 				if gm.getTag('mailbox').getTag('mail-thread-info'):
 					gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
 					for gmessage in gmail_messages:
-						gmail_from = gmessage.getTag('senders').getTag('sender').getAttr('address')
+						sender = gmessage.getTag('senders').getTag('sender')
+						if not sender:
+							continue
+						gmail_from = sender.getAttr('address')
 						gmail_subject = gmessage.getTag('subject').getData()
 						gmail_snippet = gmessage.getTag('snippet').getData()
 						gmail_messages_list.append({ \
@@ -1331,6 +1361,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 			self._pubsubEventCB(con, msg)
 			return
 		msgtxt = msg.getBody()
+		msghtml = msg.getXHTML()
 		mtype = msg.getType()
 		subject = msg.getSubject() # if not there, it's None
 		tim = msg.getTimestamp()
@@ -1405,15 +1436,18 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 			self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
 				tim))
 		elif mtype == 'groupchat':
+			has_timestamp = False
+			if msg.timestamp:
+				has_timestamp = True
 			if subject:
-				self.dispatch('GC_SUBJECT', (frm, subject, msgtxt))
+				self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp))
 			else:
 				if not msg.getTag('body'): #no <body>
 					return
 				# Ignore message from room in which we are not
 				if not self.last_history_line.has_key(jid):
 					return
-				self.dispatch('GC_MSG', (frm, msgtxt, tim))
+				self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
 				if self.name not in no_log_for and not int(float(time.mktime(tim))) <= \
 					self.last_history_line[jid] and msgtxt:
 					gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
@@ -1425,11 +1459,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 				msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
 					subject = subject)
 			self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
-				chatstate, msg_id, composing_jep, user_nick))
+						chatstate, msg_id, composing_jep, user_nick, msghtml))
 		else: # it's single message
-			if self.name not in no_log_for and jid not in no_log_for and msgtxt:
-				gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
-					subject = subject)
 			if invite is not None:
 				item = invite.getTag('invite')
 				jid_from = item.getAttr('from')
@@ -1437,9 +1468,12 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 				item = invite.getTag('password')
 				password = invite.getTagData('password')
 				self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
-			else:
-				self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
-					subject, chatstate, msg_id, composing_jep, user_nick))
+				return
+			if self.name not in no_log_for and jid not in no_log_for and msgtxt:
+				gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
+					subject = subject)
+			self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
+				subject, chatstate, msg_id, composing_jep, user_nick, msghtml))
 	# END messageCB
 
 	def _pubsubEventCB(self, con, msg):
@@ -1482,8 +1516,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 				# one
 				who = str(prs.getFrom())
 				jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
-				self.dispatch('GC_MSG', (jid_stripped, _('Nickname not allowed: %s') % \
-					resource, None))
+				self.dispatch('GC_MSG', (jid_stripped,
+					_('Nickname not allowed: %s') % resource, None, False, None))
 			return
 		jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
 		timestamp = None
@@ -1545,22 +1579,22 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 					self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
 						prio, keyID, timestamp))
 				elif errcode == '401': # password required to join
-					self.dispatch('ERROR', (_('Unable to join room'),
-						_('A password is required to join this room.')))
+					self.dispatch('ERROR', (_('Unable to join group chat'),
+						_('A password is required to join this group chat.')))
 				elif errcode == '403': # we are banned
-					self.dispatch('ERROR', (_('Unable to join room'),
-						_('You are banned from this room.')))
-				elif errcode == '404': # room does not exist
-					self.dispatch('ERROR', (_('Unable to join room'),
-						_('Such room does not exist.')))
+					self.dispatch('ERROR', (_('Unable to join group chat'),
+						_('You are banned from this group chat.')))
+				elif errcode == '404': # group chat does not exist
+					self.dispatch('ERROR', (_('Unable to join group chat'),
+						_('Such group chat does not exist.')))
 				elif errcode == '405':
-					self.dispatch('ERROR', (_('Unable to join room'),
-						_('Room creation is restricted.')))
+					self.dispatch('ERROR', (_('Unable to join group chat'),
+						_('Group chat creation is restricted.')))
 				elif errcode == '406':
-					self.dispatch('ERROR', (_('Unable to join room'),
+					self.dispatch('ERROR', (_('Unable to join group chat'),
 						_('Your registered nickname must be used.')))
 				elif errcode == '407':
-					self.dispatch('ERROR', (_('Unable to join room'),
+					self.dispatch('ERROR', (_('Unable to join group chat'),
 						_('You are not in the members list.')))
 				elif errcode == '409': # nick conflict
 					# the jid_from in this case is FAKE JID: room_jid/nick
@@ -1568,7 +1602,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 					proposed_nickname = resource + \
 						gajim.config.get('gc_proposed_nick_char')
 					room_jid = gajim.get_room_from_fjid(who)
-					self.dispatch('ASK_NEW_NICK', (room_jid, _('Unable to join room'),
+					self.dispatch('ASK_NEW_NICK', (room_jid, _('Unable to join group chat'),
 		_('Your desired nickname is in use or registered by another occupant.\nPlease specify another nickname below:'), proposed_nickname))
 				else:	# print in the window the error
 					self.dispatch('ERROR_ANSWER', ('', jid_stripped,
@@ -1839,12 +1873,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 			if show == 'invisible':
 				self.send_invisible_presence(msg, signed, True)
 				return
-			prio =  unicode(gajim.config.get_per('accounts', self.name,
-				'priority'))
+			priority = gajim.get_priority(self.name, sshow)
 			vcard = self.get_cached_vcard(jid)
 			if vcard and vcard.has_key('PHOTO') and vcard['PHOTO'].has_key('SHA'):
 				self.vcard_sha = vcard['PHOTO']['SHA']
-			p = common.xmpp.Presence(typ = None, priority = prio, show = sshow)
+			p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
 			p = self.add_sha(p)
 			if msg:
 				p.setStatus(msg)
@@ -1853,6 +1886,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 
 			if self.connection:
 				self.connection.send(p)
+				self.priority = priority
 			self.dispatch('STATUS', show)
 			# ask our VCard
 			self.request_vcard(None)
@@ -1860,6 +1894,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 			# Get bookmarks from private namespace
 			self.get_bookmarks()
 
+			# Get annotations from private namespace
+			self.get_annotations()
+
 			# If it's a gmail account,
 			# inform the server that we want e-mail notifications
 			if gajim.get_server_from_jid(our_jid) in gajim.gmail_domains:
diff --git a/src/common/contacts.py b/src/common/contacts.py
index e6d095641402951ad075a6285b9d68ad469352cb..eac033c8c69b3fef77174d260780be4af383dfc1 100644
--- a/src/common/contacts.py
+++ b/src/common/contacts.py
@@ -1,16 +1,8 @@
 ## common/contacts.py
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
 ##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -27,8 +19,8 @@ import common.gajim
 class Contact:
 	'''Information concerning each contact'''
 	def __init__(self, jid='', name='', groups=[], show='', status='', sub='',
-			ask='', resource='', priority=0, keyID='', our_chatstate=None,
-			chatstate=None, last_status_time=None, msg_id = None, composing_jep = None):
+	ask='', resource='', priority=0, keyID='', our_chatstate=None,
+	chatstate=None, last_status_time=None, msg_id = None, composing_jep = None):
 		self.jid = jid
 		self.name = name
 		self.groups = groups
@@ -67,10 +59,40 @@ class Contact:
 			return self.name
 		return self.jid.split('@')[0]
 
+	def is_hidden_from_roster(self):
+		'''if contact should not be visible in roster'''
+		# XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
+		if self.is_transport():
+			return False
+		if self.sub in ('both', 'to'):
+			return False
+		if self.sub in ('none', 'from') and self.ask == 'subscribe':
+			return False
+		if self.sub in ('none', 'from') and (self.name or len(self.groups)):
+			return False
+		if _('Not in Roster') in self.groups:
+			return False
+		return True
+
+	def is_observer(self):
+		# XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
+		is_observer = False
+		if self.sub == 'from' and not self.is_transport()\
+		and self.is_hidden_from_roster():
+			is_observer = True
+		return is_observer
+
+	def is_transport(self):
+		# if not '@' or '@' starts the jid then contact is transport
+		if self.jid.find('@') <= 0:
+			return True
+		return False
+
+
 class GC_Contact:
 	'''Information concerning each groupchat contact'''
 	def __init__(self, room_jid='', name='', show='', status='', role='',
-			affiliation='', jid = '', resource = ''):
+	affiliation='', jid = '', resource = ''):
 		self.room_jid = room_jid
 		self.name = name
 		self.show = show
@@ -220,6 +242,51 @@ class Contacts:
 			return self._contacts[account][jid][0]
 		return None
 
+	def get_contacts_from_group(self, account, group):
+		'''Returns all contacts in the given group'''
+		group_contacts = []
+		for jid in self._contacts[account]:
+			contacts = self.get_contacts_from_jid(account, jid)
+			if group in contacts[0].groups:
+				group_contacts += contacts
+		return group_contacts
+
+	def get_nb_online_total_contacts(self, accounts = [], groups = []):
+		'''Returns the number of online contacts and the total number of
+		contacts'''
+		if accounts == []:
+			accounts = self.get_accounts()
+		nbr_online = 0
+		nbr_total = 0
+		for account in accounts:
+			our_jid = common.gajim.get_jid_from_account(account)
+			for jid in self.get_jid_list(account):
+				if jid == our_jid:
+					continue
+				if common.gajim.jid_is_transport(jid) and not \
+				common.gajim.config.get('show_transports_group'):
+					# do not count transports
+					continue
+				contact = self.get_contact_with_highest_priority(account, jid)
+				in_groups = False
+				if groups == []:
+					in_groups = True
+				else:
+					contact_groups = contact.groups
+					if not contact_groups:
+						# Contact is not in a group, so count it in General group
+						contact_groups.append(_('General'))
+					for group in groups:
+						if group in contact_groups:
+							in_groups = True
+							break
+
+				if in_groups:
+					if contact.show not in ('offline', 'error'):
+						nbr_online += 1
+					nbr_total += 1
+		return nbr_online, nbr_total
+
 	def define_metacontacts(self, account, tags_list):
 		self._metacontacts_tags[account] = tags_list
 
diff --git a/src/dbus_support.py b/src/common/dbus_support.py
similarity index 68%
rename from src/dbus_support.py
rename to src/common/dbus_support.py
index 59e751c417ee84b0b3975ec51adcb2fb5d6d7971..d554f7ab23e2b17193470225a77771f43275df6d 100644
--- a/src/dbus_support.py
+++ b/src/common/dbus_support.py
@@ -16,32 +16,56 @@
 ##
 
 import os
-import sys
 
 from common import gajim
 from common import exceptions
 
+_GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error'
+
 try:
 	import dbus
-	version = getattr(dbus, 'version', (0, 20, 0))
-	supported = True
+	import dbus.service
+	import dbus.glib
+	supported = True # does use have D-Bus bindings?
 except ImportError:
-	version = (0, 0, 0)
 	supported = False
 	if not os.name == 'nt': # only say that to non Windows users
 		print _('D-Bus python bindings are missing in this computer')
 		print _('D-Bus capabilities of Gajim cannot be used')
+
+class SystemBus:
+	'''A Singleton for the DBus SystemBus'''
+	def __init__(self):
+		self.system_bus = None
 	
-# dbus 0.23 leads to segfault with threads_init()
-if sys.version[:4] >= '2.4' and version[1] < 30:
-	supported = False
+	def SystemBus(self):
+		if not supported:
+			raise exceptions.DbusNotSupported
 
-if version >= (0, 41, 0):
-	import dbus.service
-	import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it
+		if not self.present():
+				raise exceptions.SystemBusNotPresent
+		return self.system_bus
+
+	def bus(self):
+		return self.SystemBus()
+
+	def present(self):
+		if not supported:
+			return False
+		if self.system_bus is None:
+			try:
+				self.system_bus = dbus.SystemBus()
+			except dbus.dbus_bindings.DBusException:
+				self.system_bus = None
+				return False
+			if self.system_bus is None:
+				return False
+		return True
+
+system_bus = SystemBus()
 
 class SessionBus:
-	'''A Singleton for the DBus SessionBus'''
+	'''A Singleton for the D-Bus SessionBus'''
 	def __init__(self):
 		self.session_bus = None
 	
@@ -102,4 +126,13 @@ def get_interface(interface, path):
 
 def get_notifications_interface():
 	'''Returns the notifications interface.'''
-	return get_interface('org.freedesktop.Notifications','/org/freedesktop/Notifications')
+	return get_interface('org.freedesktop.Notifications',
+		'/org/freedesktop/Notifications')
+
+if supported:
+	class MissingArgument(dbus.DBusException):
+		_dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument'
+	
+	class InvalidArgument(dbus.DBusException):
+		'''Raised when one of the provided arguments is invalid.'''
+		_dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument'
diff --git a/src/common/defs.py b/src/common/defs.py
new file mode 100644
index 0000000000000000000000000000000000000000..82d11b301fcf3b45bcce1a03cc851162ecc406e7
--- /dev/null
+++ b/src/common/defs.py
@@ -0,0 +1,9 @@
+docdir = '../'
+
+datadir = '../'
+
+version = '0.10.1.7'
+
+import sys, os.path
+for base in ('.', 'common'):
+	sys.path.append(os.path.join(base, '.libs'))
diff --git a/src/common/events.py b/src/common/events.py
index 4a7ba9e87f4dd69aa6fee6b587bdcb7bc1cc85f8..99cd98cfc3a761c7257d5bf1ad64ab730baa0c59 100644
--- a/src/common/events.py
+++ b/src/common/events.py
@@ -5,7 +5,7 @@
 ##
 ## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -81,7 +81,7 @@ class Events:
 			gajim.interface.systray.set_img()
 
 	def remove_events(self, account, jid, event = None, types = []):
-		'''if event is not speficied, remove all events from this jid,
+		'''if event is not specified, remove all events from this jid,
 		optionnaly only from given type
 		return True if no such event found'''
 		if not self._events.has_key(account):
@@ -118,11 +118,11 @@ class Events:
 		if gajim.interface.systray_capabilities:
 			gajim.interface.systray.set_img()
 
-	def get_nb_events(self, types = []):
-		return self._get_nb_events(types = types)
+	def get_nb_events(self, types = [], account = None):
+		return self._get_nb_events(types = types, account = account)
 
 	def get_events(self, account, jid = None, types = []):
-		'''if event is not speficied, remove all events from this jid,
+		'''if event is not specified, get all events from this jid,
 		optionnaly only from given type'''
 		if not self._events.has_key(account):
 			return []
@@ -149,7 +149,7 @@ class Events:
 		return first_event
 
 	def _get_nb_events(self, account = None, jid = None, attribute = None, types = []):
-		'''return the number of events'''
+		'''return the number of pending events'''
 		nb = 0
 		if account:
 			accounts = [account]
@@ -223,7 +223,7 @@ class Events:
 		return self._get_first_event_with_attribute(events)
 
 	def get_nb_roster_events(self, account = None, jid = None, types = []):
-		'''returns the number of events displayedin roster'''
+		'''returns the number of events displayed in roster'''
 		return self._get_nb_events(attribute = 'roster', account = account,
 			jid = jid, types = types)
 
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
index 0b1bc8c4a72f5825f29609e727b5df2b2a7fe6cc..bceef79a4e3743f82532e3605ac6281f2d96e19c 100644
--- a/src/common/exceptions.py
+++ b/src/common/exceptions.py
@@ -1,17 +1,7 @@
 ## exceptions.py
 ##
-## Contributors for this file:
-## - Yann Le Boulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <kourem@gmail.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -54,3 +44,12 @@ class SessionBusNotPresent(Exception):
 
 	def __str__(self):
 		return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus')
+
+class GajimGeneralException(Exception):
+	'''This exception ir our general exception'''
+	def __init__(self, text=''):
+		Exception.__init__(self)
+		self.text = text
+
+	def __str__(self):
+		return self.text
diff --git a/src/common/gajim.py b/src/common/gajim.py
index dc7794e6e3fa0536fa681fc184c8718489016379..9f03b09d4913024e8469b96a642ea739d88bd279 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -15,9 +15,7 @@
 ## GNU General Public License for more details.
 ##
 
-import os
 import sys
-import tempfile
 import logging
 import locale
 
@@ -25,10 +23,36 @@ import config
 from contacts import Contacts
 from events import Events
 
+try:
+	import defs
+except ImportError:
+	print >> sys.stderr, '''defs.py is missing!
+
+If you start gajim from svn:
+ * Make sure you have GNU autotools installed.
+   This includes the following packages:
+    automake >= 1.8
+    autoconf >= 2.59
+    intltool-0.35
+    libtool
+ * Run
+    $ sh autogen.sh
+ * Optionally, install gajim
+    $ make
+    $ sudo make install
+
+**** Note for translators ****
+ You can get the latest string updates, by running:
+    $ cd po/
+    $ make update-po
+
+'''
+	sys.exit(1)
+
 interface = None # The actual interface (the gtk one for the moment)
 config = config.Config()
 version = config.get('version')
-connections = {}
+connections = {} # 'account name': 'account (connection.Connection) instance'
 verbose = False
 
 h = logging.StreamHandler()
@@ -40,38 +64,17 @@ log.addHandler(h)
 import logger
 logger = logger.Logger() # init the logger
 
-if os.name == 'nt':
-	DATA_DIR = os.path.join('..', 'data')
-	try:
-		# Documents and Settings\[User Name]\Application Data\Gajim
-		LOGPATH = os.path.join(os.environ['appdata'], 'Gajim', 'Logs') # deprecated
-		VCARD_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'Vcards')
-		AVATAR_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'Avatars')
-		MY_EMOTS_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'Emoticons')
-	except KeyError:
-		# win9x, in cwd
-		LOGPATH = 'Logs' # deprecated
-		VCARD_PATH = 'Vcards'
-		AVATAR_PATH = 'Avatars'
-		MY_EMOTS_PATH = 'Emoticons'
-else: # Unices
-	DATA_DIR = '../data'
-	LOGPATH = os.path.expanduser('~/.gajim/logs') # deprecated
-	VCARD_PATH = os.path.expanduser('~/.gajim/vcards')
-	AVATAR_PATH = os.path.expanduser('~/.gajim/avatars')
-	MY_EMOTS_PATH = os.path.expanduser('~/.gajim/emoticons')
-
-HOME_DIR = os.path.expanduser('~')
-TMP = tempfile.gettempdir()
+import configpaths
+gajimpaths = configpaths.gajimpaths
+
+LOGPATH = gajimpaths['LOG'] # deprecated
+VCARD_PATH = gajimpaths['VCARD']
+AVATAR_PATH = gajimpaths['AVATAR']
+MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
+TMP = gajimpaths['TMP']
+DATA_DIR = gajimpaths['DATA']
+HOME_DIR = gajimpaths['HOME']
 
-try:
-	LOGPATH = LOGPATH.decode(sys.getfilesystemencoding())
-	VCARD_PATH = VCARD_PATH.decode(sys.getfilesystemencoding())
-	TMP = TMP.decode(sys.getfilesystemencoding())
-	AVATAR_PATH = AVATAR_PATH.decode(sys.getfilesystemencoding())
-	MY_EMOTS_PATH = MY_EMOTS_PATH.decode(sys.getfilesystemencoding())
-except:
-	pass
 try:
 	LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
 except (ValueError, locale.Error):
@@ -120,6 +123,12 @@ status_before_autoaway = {}
 SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
 	'invisible']
 
+# zeroconf account name
+ZEROCONF_ACC_NAME = 'Local'
+priority_dict = {}
+for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+	priority_dict[status] = config.get('autopriority' + status)
+
 def get_nick_from_jid(jid):
 	pos = jid.find('@')
 	return jid[:pos]
@@ -133,10 +142,10 @@ def get_nick_from_fjid(jid):
 	# gaim@conference.jabber.no/nick/nick-continued
 	return jid.split('/', 1)[1]
 
-def get_room_name_and_server_from_room_jid(jid):
-	room_name = get_nick_from_jid(jid)
+def get_name_and_server_from_jid(jid):
+	name = get_nick_from_jid(jid)
 	server = get_server_from_jid(jid)
-	return room_name, server
+	return name, server
 
 def get_room_and_nick_from_fjid(jid):
 	# fake jid is the jid for a contact in a room
@@ -207,11 +216,36 @@ def get_number_of_connected_accounts(accounts_list = None):
 		accounts = connections.keys()
 	else:
 		accounts = accounts_list
-	for acct in accounts:
-		if connections[acct].connected > 1:
+	for account in accounts:
+		if account_is_connected(account):
 			connected_accounts = connected_accounts + 1
 	return connected_accounts
 
+def account_is_connected(account):
+	if account not in connections:
+		return False
+	if connections[account].connected > 1: # 0 is offline, 1 is connecting
+		return True
+	else:
+		return False
+
+def account_is_disconnected(account):
+	return not account_is_connected(account)
+
+def get_number_of_securely_connected_accounts():
+	'''returns the number of the accounts that are SSL/TLS connected'''
+	num_of_secured = 0
+	for account in connections:
+		if account_is_securely_connected(account):
+			num_of_secured += 1
+	return num_of_secured
+
+def account_is_securely_connected(account):
+	if account in con_types and con_types[account] in ('tls', 'ssl'):
+		return True
+	else:
+		return False
+
 def get_transport_name_from_jid(jid, use_config_setting = True):
 	'''returns 'aim', 'gg', 'irc' etc
 	if JID is not from transport returns None'''
@@ -299,3 +333,13 @@ def get_name_from_jid(account, jid):
 	else:
 		actor = jid
 	return actor
+
+def get_priority(account, show):
+	'''return the priority an account must have'''
+	if not show:
+		show = 'online'
+
+	if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \
+	config.get_per('accounts', account, 'adjust_priority_with_status'):
+		return config.get_per('accounts', account, 'autopriority_' + show)
+	return config.get_per('accounts', account, 'priority')
diff --git a/src/common/helpers.py b/src/common/helpers.py
index 1226f2cd54f6dd8216b93ff00affef4c88f3d27f..5dc224ff26529f9ef44223c785abadce4d708e69 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -17,19 +17,21 @@
 ##
 
 import sre
+import locale
 import os
 import subprocess
 import urllib
 import errno
 import select
-import sys
 import sha
 from encodings.punycode import punycode_encode
 
 import gajim
 from i18n import Q_
+from i18n import ngettext
 from xmpp_stringprep import nodeprep, resourceprep, nameprep
 
+
 try:
 	import winsound # windows-only built-in module for playing wav
 	import win32api
@@ -290,6 +292,21 @@ def get_uf_role(role, plural = False):
 		else:
 			role_name = _('Visitor')
 	return role_name
+	
+def get_uf_affiliation(affiliation):
+	'''Get a nice and translated affilition for muc'''
+	if affiliation == 'none': 
+		affiliation_name = Q_('?Group Chat Contact Affiliation:None')
+	elif affiliation == 'owner':
+		affiliation_name = _('Owner')
+	elif affiliation == 'admin':
+		affiliation_name = _('Administrator')
+	elif affiliation == 'member':
+		affiliation_name = _('Member')
+	else: # Argl ! An unknown affiliation !
+		affiliation_name = affiliation.capitalize()
+	return affiliation_name
+
 
 def get_sorted_keys(adict):
 	keys = adict.keys()
@@ -362,9 +379,14 @@ def is_in_path(name_of_command, return_abs_path = False):
 		return is_in_dir
 
 def exec_command(command):
-	'''command is a string that contain arguments'''
-#	os.system(command)
-	subprocess.Popen(command.split())
+	subprocess.Popen(command, shell = True)
+
+def build_command(executable, parameter):
+	# we add to the parameter (can hold path with spaces)
+	# "" so we have good parsing from shell
+	parameter = parameter.replace('"', '\\"') # but first escape "
+	command = '%s "%s"' % (executable, parameter)
+	return command
 
 def launch_browser_mailer(kind, uri):
 	#kind = 'url' or 'mail'
@@ -382,6 +404,8 @@ def launch_browser_mailer(kind, uri):
 			command = 'gnome-open'
 		elif gajim.config.get('openwith') == 'kfmclient exec':
 			command = 'kfmclient exec'
+		elif gajim.config.get('openwith') == 'exo-open':
+			command = 'exo-open'
 		elif gajim.config.get('openwith') == 'custom':
 			if kind == 'url':
 				command = gajim.config.get('custombrowser')
@@ -389,7 +413,8 @@ def launch_browser_mailer(kind, uri):
 				command = gajim.config.get('custommailapp')
 			if command == '': # if no app is configured
 				return
-		command = command + ' ' + uri
+
+		command = build_command(command, uri)
 		try:
 			exec_command(command)
 		except:
@@ -406,11 +431,13 @@ def launch_file_manager(path_to_open):
 			command = 'gnome-open'
 		elif gajim.config.get('openwith') == 'kfmclient exec':
 			command = 'kfmclient exec'
+		elif gajim.config.get('openwith') == 'exo-open':
+			command = 'exo-open'
 		elif gajim.config.get('openwith') == 'custom':
 			command = gajim.config.get('custom_file_manager')
 		if command == '': # if no app is configured
 			return
-		command = command + ' ' + path_to_open
+		command = build_command(command, path_to_open)
 		try:
 			exec_command(command)
 		except:
@@ -438,7 +465,7 @@ def play_sound_file(path_to_soundfile):
 		if gajim.config.get('soundplayer') == '':
 			return
 		player = gajim.config.get('soundplayer')
-		command = player + ' ' + path_to_soundfile
+		command = build_command(player, path_to_soundfile)
 		exec_command(command)
 
 def get_file_path_from_dnd_dropped_uri(uri):
@@ -523,8 +550,10 @@ def get_icon_name_to_show(contact, account = None):
 
 def decode_string(string):
 	'''try to decode (to make it Unicode instance) given string'''
+	if isinstance(string, unicode):
+		return string
 	# by the time we go to iso15 it better be the one else we show bad characters
-	encodings = (sys.getfilesystemencoding(), 'utf-8', 'iso-8859-15')
+	encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
 	for encoding in encodings:
 		try:
 			string = string.decode(encoding)
@@ -599,7 +628,6 @@ def get_documents_path():
 		path = os.path.expanduser('~')
 	return path
 
-# moved from connection.py
 def get_full_jid_from_iq(iq_obj):
 	'''return the full jid (with resource) from an iq as unicode'''
 	return parse_jid(str(iq_obj.getFrom()))
@@ -674,6 +702,7 @@ def get_os_info():
 			output = temp_failure_retry(child_stdout.readline).strip()
 			child_stdout.close()
 			child_stdin.close()
+			os.wait()
 			# some distros put n/a in places, so remove those
 			output = output.replace('n/a', '').replace('N/A', '')
 			return output
@@ -726,23 +755,21 @@ def sanitize_filename(filename):
 	
 	return filename
 
-def allow_showing_notification(account, type = None, advanced_notif_num = None,
-first = True):
+def allow_showing_notification(account, type = 'notify_on_new_message',
+advanced_notif_num = None, is_first_message = True):
 	'''is it allowed to show nofication?
 	check OUR status and if we allow notifications for that status
-	type is the option that need to be True ex: notify_on_signing
-	first: set it to false when it's not the first message'''
-	if advanced_notif_num != None:
+	type is the option that need to be True e.g.: notify_on_signing
+	is_first_message: set it to false when it's not the first message'''
+	if advanced_notif_num is not None:
 		popup = gajim.config.get_per('notifications', str(advanced_notif_num),
 			'popup')
 		if popup == 'yes':
 			return True
 		if popup == 'no':
 			return False
-	if type and (not gajim.config.get(type) or not first):
+	if type and (not gajim.config.get(type) or not is_first_message):
 		return False
-	if type and gajim.config.get(type) and first:
-		return True
 	if gajim.config.get('autopopupaway'): # always show notification
 		return True
 	if gajim.connections[account].connected in (2, 3): # we're online or chat
@@ -766,7 +793,7 @@ def allow_popup_window(account, advanced_notif_num = None):
 	return False
 
 def allow_sound_notification(sound_event, advanced_notif_num = None):
-	if advanced_notif_num != None:
+	if advanced_notif_num is not None:
 		sound = gajim.config.get_per('notifications', str(advanced_notif_num),
 			'sound')
 		if sound == 'yes':
@@ -796,3 +823,110 @@ def get_chat_control(account, contact):
 	highest_contact.resource:
 		return None
 	return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
+
+def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
+	'''Cut the chars after 'max_chars' on each line
+	and show only the first 'max_lines'.
+	If any of the params is not present (None or 0) the action
+	on it is not performed'''
+
+	def _cut_if_long(string):
+		if len(string) > max_chars:
+			string = string[:max_chars - 3] + '...'
+		return string
+
+	if isinstance(text, str):
+		text = text.decode('utf-8')
+
+	if max_lines == 0:
+		lines = text.split('\n')
+	else:
+		lines = text.split('\n', max_lines)[:max_lines]
+	if max_chars > 0:
+		if lines:
+			lines = map(lambda e: _cut_if_long(e), lines)
+	if lines:
+		reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines)
+	else:
+		reduced_text = ''
+	return reduced_text
+
+def get_notification_icon_tooltip_text():
+	text = None
+	unread_chat = gajim.events.get_nb_events(types = ['printed_chat',
+		'chat'])
+	unread_single_chat = gajim.events.get_nb_events(types = ['normal'])
+	unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg',
+		'gc_msg'])
+	unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm'])
+
+	accounts = get_accounts_info()
+
+	if unread_chat or unread_single_chat or unread_gc or unread_pm:
+		text = 'Gajim '
+		awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm
+		if awaiting_events == unread_chat or awaiting_events == unread_single_chat \
+			or awaiting_events == unread_gc or awaiting_events == unread_pm:
+			# This condition is like previous if but with xor... 
+			# Print in one line
+			text += '-'
+		else:
+			# Print in multiple lines
+			text += '\n   '
+		if unread_chat:
+			text += ngettext(
+				' %d unread message',
+				' %d unread messages',
+				unread_chat, unread_chat, unread_chat)
+			text += '\n   '
+		if unread_single_chat:
+			text += ngettext(
+				' %d unread single message',
+				' %d unread single messages',
+				unread_single_chat, unread_single_chat, unread_single_chat)
+			text += '\n   '
+		if unread_gc:
+			text += ngettext(
+				' %d unread group chat message',
+				' %d unread group chat messages',
+				unread_gc, unread_gc, unread_gc)
+			text += '\n   '
+		if unread_pm:
+			text += ngettext(
+				' %d unread private message',
+				' %d unread private messages',
+				unread_pm, unread_pm, unread_pm)
+			text += '\n   '
+		text = text[:-4] # remove latest '\n   '
+	elif len(accounts) > 1:
+		text = _('Gajim')
+	elif len(accounts) == 1:
+		message = accounts[0]['status_line']
+		message = reduce_chars_newlines(message, 100, 1)
+		text = _('Gajim - %s') % message
+	else:
+		text = _('Gajim - %s') % get_uf_show('offline')
+		
+	return text
+
+def get_accounts_info():
+	'''helper for notification icon tooltip'''
+	accounts = []
+	accounts_list = gajim.contacts.get_accounts()
+	accounts_list.sort()
+	for account in accounts_list:
+		status_idx = gajim.connections[account].connected
+		# uncomment the following to hide offline accounts
+		# if status_idx == 0: continue
+		status = gajim.SHOW_LIST[status_idx]
+		message = gajim.connections[account].status
+		single_line = get_uf_show(status)
+		if message is None:
+			message = ''
+		else:
+			message = message.strip()
+		if message != '':
+			single_line += ': ' + message
+		accounts.append({'name': account, 'status_line': single_line, 
+				'show': status, 'message': message})
+	return accounts
diff --git a/src/common/i18n.py b/src/common/i18n.py
index ae23f0e2ffcccec577b6b68a60ec9360915f85a2..8eae356213d8abedba1505a4708edec8839f2300 100644
--- a/src/common/i18n.py
+++ b/src/common/i18n.py
@@ -8,7 +8,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
diff --git a/src/common/logger.py b/src/common/logger.py
index 11f78440659485854d139bf536b5287a6d44c646..9de23a017d65252649fcbae49648ce4dfddb9143 100644
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -1,17 +1,7 @@
 ## logger.py
 ##
-## Contributors for this file:
-## - Yann Le Boulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <kourem@gmail.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -32,24 +22,15 @@ import exceptions
 import gajim
 
 try:
-	from pysqlite2 import dbapi2 as sqlite
+	import sqlite3 as sqlite # python 2.5
 except ImportError:
-	raise exceptions.PysqliteNotAvailable
-
-if os.name == 'nt':
 	try:
-		# Documents and Settings\[User Name]\Application Data\Gajim\logs.db
-		LOG_DB_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'logs.db')
-	except KeyError:
-		# win9x, ./logs.db
-		LOG_DB_PATH = 'logs.db'
-else: # Unices
-	LOG_DB_PATH = os.path.expanduser('~/.gajim/logs.db')
+		from pysqlite2 import dbapi2 as sqlite
+	except ImportError:
+		raise exceptions.PysqliteNotAvailable
 
-try:
-	LOG_DB_PATH = LOG_DB_PATH.decode(sys.getfilesystemencoding())
-except:
-	pass
+import configpaths
+LOG_DB_PATH = configpaths.gajimpaths['LOG_DB']
 
 class Constants:
 	def __init__(self):
@@ -107,15 +88,33 @@ class Logger:
 			return
 		self.init_vars()
 
-	def init_vars(self):
-		# if locked, wait up to 20 sec to unlock
-		# before raise (hopefully should be enough)
+	def close_db(self):
 		if self.con:
 			self.con.close()
+		self.con = None
+		self.cur = None
+
+	def open_db(self):
+		self.close_db()
+
+		# if locked, wait up to 20 sec to unlock
+		# before raise (hopefully should be enough)
 		self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
 			isolation_level = 'IMMEDIATE')
 		self.cur = self.con.cursor()
+		self.set_synchronous(False)
+
+	def set_synchronous(self, sync):
+		try:
+			if sync:
+				self.cur.execute("PRAGMA synchronous = NORMAL")
+			else:
+				self.cur.execute("PRAGMA synchronous = OFF")
+		except sqlite.Error, e:
+			gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
 
+	def init_vars(self):
+		self.open_db()
 		self.get_jids_already_in_db()
 
 	def get_jids_already_in_db(self):
@@ -136,9 +135,11 @@ class Logger:
 		and after that all okay'''
 		
 		possible_room_jid, possible_nick = jid.split('/', 1)
+		return self.jid_is_room_jid(possible_room_jid)
 
+	def jid_is_room_jid(self, jid):
 		self.cur.execute('SELECT jid_id FROM jids WHERE jid=?  AND type=?', 
-			(possible_room_jid, constants.JID_ROOM_TYPE))
+			(jid, constants.JID_ROOM_TYPE))
 		row = self.cur.fetchone()
 		if row is None:
 			return False
@@ -344,10 +345,8 @@ class Logger:
 		ROOM_JID/nick if pm-related.'''
 
 		if self.jids_already_in == []: # only happens if we just created the db
-			self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
-				isolation_level = 'IMMEDIATE')
-			self.cur = self.con.cursor()
-			
+			self.open_db()
+
 		jid = jid.lower()
 		contact_name_col = None # holds nickname for kinds gcstatus, gc_msg
 		# message holds the message unless kind is status or gcstatus,
diff --git a/src/common/optparser.py b/src/common/optparser.py
index 5459905b7b29380b0d27140bd071732296c2d93e..c64446f9045e7ed3063520fe2989198d9fa013c2 100644
--- a/src/common/optparser.py
+++ b/src/common/optparser.py
@@ -1,16 +1,6 @@
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -27,11 +17,21 @@ import sys
 import locale
 from common import gajim
 
+import exceptions
+try:
+	import sqlite3 as sqlite # python 2.5
+except ImportError:
+	try:
+		from pysqlite2 import dbapi2 as sqlite
+	except ImportError:
+		raise exceptions.PysqliteNotAvailable
+import logger
+
 class OptionsParser:
 	def __init__(self, filename):
 		self.__filename = filename
-		self.old_values = {} # values that are saved in the file and maybe
-									# no longer valid
+		self.old_values = {}	# values that are saved in the file and maybe
+								# no longer valid
 
 	def read_line(self, line):
 		index = line.find(' = ')
@@ -126,8 +126,7 @@ class OptionsParser:
 		os.chmod(self.__filename, 0600)
 
 	def update_config(self, old_version, new_version):
-		# Convert '0.x.y' to (0, x, y)
-		old_version_list = old_version.split('.')
+		old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
 		old = []
 		while len(old_version_list):
 			old.append(int(old_version_list.pop(0)))
@@ -146,7 +145,15 @@ class OptionsParser:
 			self.update_config_to_01012()
 		if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
 			self.update_config_to_01013()
-	
+		if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]:
+			self.update_config_to_01014()
+		if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]:
+			self.update_config_to_01015()
+		if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]:
+			self.update_config_to_01016()
+		if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]:
+			self.update_config_to_01017()
+
 		gajim.logger.init_vars()
 		gajim.config.set('version', new_version)
 	
@@ -202,13 +209,6 @@ class OptionsParser:
 
 	def assert_unread_msgs_table_exists(self):
 		'''create table unread_messages if there is no such table'''
-		import exceptions
-		try:
-			from pysqlite2 import dbapi2 as sqlite
-		except ImportError:
-			raise exceptions.PysqliteNotAvailable
-		import logger
-
 		con = sqlite.connect(logger.LOG_DB_PATH) 
 		cur = con.cursor()
 		try:
@@ -278,13 +278,6 @@ class OptionsParser:
 
 	def update_config_to_01013(self):
 		'''create table transports_cache if there is no such table'''
-		import exceptions
-		try:
-			from pysqlite2 import dbapi2 as sqlite
-		except ImportError:
-			raise exceptions.PysqliteNotAvailable
-		import logger
-
 		con = sqlite.connect(logger.LOG_DB_PATH) 
 		cur = con.cursor()
 		try:
@@ -301,3 +294,56 @@ class OptionsParser:
 			pass
 		con.close()
 		gajim.config.set('version', '0.10.1.3')
+
+	def update_config_to_01014(self):
+		'''apply indeces to the logs database'''
+		print _('migrating logs database to indeces')
+		con = sqlite.connect(logger.LOG_DB_PATH) 
+		cur = con.cursor()
+		# apply indeces
+		try:
+			cur.executescript(
+				'''
+				CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
+				CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
+				'''
+			)
+
+			con.commit()
+		except:
+			pass
+		con.close()
+		gajim.config.set('version', '0.10.1.4')
+
+	def update_config_to_01015(self):
+		'''clean show values in logs database'''
+		con = sqlite.connect(logger.LOG_DB_PATH)
+		cur = con.cursor()
+		status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \
+			logger.constants.__dict__.keys() if i.startswith('SHOW_'))
+		for show in status:
+			cur.execute('update logs set show = ? where show = ?;', (status[show],
+				show))
+		cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);')
+		con.commit()
+		cur.close() # remove this in 2007 [pysqlite old versions need this]
+		con.close()
+		gajim.config.set('version', '0.10.1.5')
+		
+	def update_config_to_01016(self):
+		'''#2494 : Now we play gc_received_message sound even if 
+		notify_on_all_muc_messages is false. Keep precedent behaviour.'''
+		if self.old_values.has_key('notify_on_all_muc_messages') and \
+		self.old_values['notify_on_all_muc_messages'] == 'False' and \
+		gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
+			gajim.config.set_per('soundevents',\
+				'muc_message_received', 'enabled', False)
+		gajim.config.set('version', '0.10.1.6')
+
+	def update_config_to_01017(self):
+		'''trayicon_notification_on_new_messages ->
+		trayicon_notification_on_events '''
+		if self.old_values.has_key('trayicon_notification_on_new_messages'):
+			gajim.config.set('trayicon_notification_on_events',
+				 self.old_values['trayicon_notification_on_new_messages']) 
+		gajim.config.set('version', '0.10.1.7')
diff --git a/src/common/passwords.py b/src/common/passwords.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6050bc6a7ea01fecaa6ac11820f1546a8764c5e
--- /dev/null
+++ b/src/common/passwords.py
@@ -0,0 +1,117 @@
+##
+## Copyright (C) 2006 Gustavo J. A. M. Carneiro <gjcarneiro@gmail.com>
+## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+__all__ = ['get_password', 'save_password']
+
+import gobject
+
+from common import gajim
+
+try:
+	import gnomekeyring
+except ImportError:
+	USER_USES_GNOMEKEYRING = False
+else:
+	if gnomekeyring.is_available():
+		USER_USES_GNOMEKEYRING = True
+	else:
+		USER_USES_GNOMEKEYRING = False
+
+class PasswordStorage(object):
+	def get_password(self, account_name):
+		raise NotImplementedError
+	def save_password(self, account_name, password):
+		raise NotImplementedError
+
+
+class SimplePasswordStorage(PasswordStorage):
+	def get_password(self, account_name):
+		return gajim.config.get_per('accounts', account_name, 'password')
+
+	def save_password(self, account_name, password):
+		gajim.config.set_per('accounts', account_name, 'password', password)
+		gajim.connections[account_name].password = password
+
+
+class GnomePasswordStorage(PasswordStorage):
+	def __init__(self):
+		# self.keyring = gnomekeyring.get_default_keyring_sync() 
+
+		## above line commented and code below inserted as workaround 
+		## for the bug http://bugzilla.gnome.org/show_bug.cgi?id=363019 
+		self.keyring = "default" 
+		try: 
+			gnomekeyring.create_sync(self.keyring, None) 
+		except gnomekeyring.AlreadyExistsError: 
+			pass
+
+	def get_password(self, account_name):
+		conf = gajim.config.get_per('accounts', account_name, 'password')
+		if conf is None:
+			return None
+		try:
+			unused, auth_token = conf.split('gnomekeyring:')
+			auth_token = int(auth_token)
+		except ValueError:
+			password = conf
+			## migrate the password over to keyring
+			try:
+				self.save_password(account_name, password, update=False)
+			except gnomekeyring.NoKeyringDaemonError:
+				## no keyring daemon: in the future, stop using it
+				set_storage(SimplePasswordStorage())
+			return password
+		try:
+			return gnomekeyring.item_get_info_sync(self.keyring,
+				auth_token).get_secret()
+		except gnomekeyring.DeniedError:
+			return None
+		except gnomekeyring.NoKeyringDaemonError:
+			## no keyring daemon: in the future, stop using it
+			set_storage(SimplePasswordStorage())
+			return None
+
+	def save_password(self, account_name, password, update=True):
+		display_name = _('Gajim account %s') % account_name
+		attributes = dict(account_name=str(account_name), gajim=1)
+		auth_token = gnomekeyring.item_create_sync(
+			self.keyring, gnomekeyring.ITEM_GENERIC_SECRET,
+			display_name, attributes, password, update)
+		token = 'gnomekeyring:%i' % auth_token
+		gajim.config.set_per('accounts', account_name, 'password', token)
+
+
+storage = None
+def get_storage():
+	global storage
+	if storage is None: # None is only in first time get_storage is called
+		if USER_USES_GNOMEKEYRING:
+			try:
+				storage = GnomePasswordStorage()
+			except gnomekeyring.NoKeyringDaemonError:
+				storage = SimplePasswordStorage()
+		else:
+			storage = SimplePasswordStorage()
+	return storage
+
+def set_storage(storage_):
+	global storage
+	storage = storage_
+
+
+def get_password(account_name):
+	return get_storage().get_password(account_name)
+
+def save_password(account_name, password):
+	return get_storage().save_password(account_name, password)
diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py
index 5f2a9f528edcb5a17a0e8ea021d095de1f0b3689..5145632db5eacc9bedd7b586b6fda5ec9ccff9f6 100644
--- a/src/common/proxy65_manager.py
+++ b/src/common/proxy65_manager.py
@@ -186,6 +186,9 @@ class HostTester(Socks5, IdleObject):
 	
 	def connect(self):
 		''' create the socket and plug it to the idlequeue '''
+		if self.host is None:
+			self.on_failure()
+			return None
 		self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 		self._sock.setblocking(False)
 		self.fd = self._sock.fileno()
diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..c69771506ee2185804b527b2b7d8d3ab251fdef8
--- /dev/null
+++ b/src/common/rst_xhtml_generator.py
@@ -0,0 +1,126 @@
+##	rst_xhtml_generator.py
+##
+## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2006 Santiago Gala
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+try:
+	from docutils import io
+	from docutils.core import Publisher
+	from docutils.parsers.rst import roles
+	from docutils import nodes,utils
+	from docutils.parsers.rst.roles import set_classes
+except:
+	def create_xhtml(text):
+		return None
+else:
+	def jep_reference_role(role, rawtext, text, lineno, inliner,
+		options={}, content=[]):
+		'''Role to make handy references to Jabber Enhancement Proposals (JEP).
+
+	 	Use as :JEP:`71` (or jep, or jep-reference).
+		Modeled after the sample in docutils documentation.
+		'''
+
+		jep_base_url = 'http://www.jabber.org/jeps/'
+		jep_url = 'jep-%04d.html'
+		try:
+			jepnum = int(text)
+			if jepnum <= 0:
+				raise ValueError
+		except ValueError:
+			msg = inliner.reporter.error(
+			'JEP number must be a number greater than or equal to 1; '
+			'"%s" is invalid.' % text, line=lineno)
+			prb = inliner.problematic(rawtext, rawtext, msg)
+			return [prb], [msg]
+		ref = jep_base_url + jep_url % jepnum
+		set_classes(options)
+		node = nodes.reference(rawtext, 'JEP ' + utils.unescape(text), refuri=ref,
+			**options)
+		return [node], []
+
+	roles.register_canonical_role('jep-reference', jep_reference_role)
+	from docutils.parsers.rst.languages.en import roles
+	roles['jep-reference'] = 'jep-reference'
+	roles['jep'] = 'jep-reference'
+
+	class HTMLGenerator:
+		'''Really simple HTMLGenerator starting from publish_parts.
+
+		It reuses the docutils.core.Publisher class, which means it is *not*
+		threadsafe.
+		'''
+		def __init__(self,
+			settings_spec=None,
+			settings_overrides=dict(report_level=5, halt_level=5),
+			config_section='general'):
+			self.pub = Publisher(reader=None, parser=None, writer=None,
+				settings=None,
+				source_class=io.StringInput,
+				destination_class=io.StringOutput)
+			self.pub.set_components(reader_name='standalone',
+				parser_name='restructuredtext',
+				writer_name='html')
+			# hack: JEP-0071 does not allow HTML char entities, so we hack our way
+			# out of it.
+			# &mdash; == u"\u2014"
+			# a setting to only emit charater entities in the writer would be nice
+			# FIXME: several &nbsp; are emitted, and they are explicitly forbidden
+			# in the JEP
+			# &nbsp; ==  u"\u00a0"
+			self.pub.writer.translator_class.attribution_formats['dash'] = (
+				u'\u2014', '')
+			self.pub.process_programmatic_settings(settings_spec,
+				settings_overrides,
+				config_section)
+
+
+		def create_xhtml(self, text,
+			destination=None,
+			destination_path=None,
+			enable_exit_status=None):
+			''' Create xhtml for a fragment of IM dialog.
+			We can use the source_name to store info about
+			the message.'''
+			self.pub.set_source(text, None)
+			self.pub.set_destination(destination, destination_path)
+			output = self.pub.publish(enable_exit_status=enable_exit_status)
+			# kludge until we can get docutils to stop generating (rare) &nbsp;
+			# entities
+			return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
+				'&nbsp;'))
+
+	Generator = HTMLGenerator()
+
+	def create_xhtml(text):
+		return Generator.create_xhtml(text)
+	
+
+if __name__ == '__main__':
+	print Generator.create_xhtml('''
+test::
+
+  >>> print 1
+  1
+
+*I* like it. It is for :JEP:`71`
+
+this `` should    trigger`` should trigger the &nbsp; problem.
+
+''')
+	print Generator.create_xhtml('''
+*test1
+
+test2_
+''')
diff --git a/src/common/sleepy.py b/src/common/sleepy.py
index fa6315c4dd85a755cb7180e851ebb4dfcdb28f5e..a0ce13ae7204c0ac8aa55418860df873dcfb0599 100644
--- a/src/common/sleepy.py
+++ b/src/common/sleepy.py
@@ -8,7 +8,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -33,13 +33,10 @@ STATE_AWAKE    = 'awake'
 
 SUPPORTED = True
 try:
-	import common.idle as idle # when we launch gajim from sources
+	import idle
 except:
-	try:
-		import idle # when Gajim is installed
-	except:
-		gajim.log.debug('Unable to load idle module')
-		SUPPORTED = False
+	gajim.log.debug('Unable to load idle module')
+	SUPPORTED = False
 
 class Sleepy:
 
diff --git a/src/common/socks5.py b/src/common/socks5.py
index 9039dab5473517671991ef78da4cd394e7275d05..80c9a9a67fc0c80d521d4d35f40b15308a9aaa4d 100644
--- a/src/common/socks5.py
+++ b/src/common/socks5.py
@@ -3,14 +3,14 @@
 ##
 ## Contributors for this file:
 ##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <nkour@jabber.org>
+##	- Nikos Kouremenos <kourem@gmail.com>
 ##	- Dimitur Kirov <dkirov@gmail.com>
 ##
 ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -27,11 +27,8 @@
 
 
 import socket
-import select
-import os
 import struct
 import sha
-import time
 from dialogs import BindPortError
 
 from errno import EWOULDBLOCK
@@ -74,13 +71,13 @@ class SocksQueue:
 		self.on_success = None
 		self.on_failure = None
 	
-	def start_listener(self, host, port, sha_str, sha_handler, sid):
+	def start_listener(self, port, sha_str, sha_handler, sid):
 		''' start waiting for incomming connections on (host, port)
 		and do a socks5 authentication using sid for generated sha
 		'''
 		self.sha_handlers[sha_str] = (sha_handler, sid)
 		if self.listener == None:
-			self.listener = Socks5Listener(self.idlequeue, host, port)
+			self.listener = Socks5Listener(self.idlequeue, port)
 			self.listener.queue = self
 			self.listener.bind()
 			if self.listener.started is False:
@@ -213,7 +210,7 @@ class SocksQueue:
 			
 			sender = self.senders[file_props['hash']]
 			sender.account = account
-			result = get_file_contents(0)
+			result = self.get_file_contents(0)
 			self.process_result(result, sender)
 	
 	def result_sha(self, sha_str, idx):
@@ -350,7 +347,10 @@ class SocksQueue:
 class Socks5:
 	def __init__(self, idlequeue, host, port, initiator, target, sid):
 		if host is not None:
-			self.host = socket.gethostbyname(host)
+			try:
+				self.host = socket.gethostbyname(host)
+			except socket.gaierror:
+				self.host = None
 		self.idlequeue = idlequeue
 		self.fd = -1
 		self.port = port
@@ -787,12 +787,12 @@ class Socks5Sender(Socks5, IdleObject):
 			self.queue.remove_sender(self.queue_idx, False)
 
 class Socks5Listener(IdleObject):
-	def __init__(self, idlequeue, host, port):
-		''' handle all incomming connections on (host, port) 
+	def __init__(self, idlequeue, port):
+		''' handle all incomming connections on (0.0.0.0, port) 
 		This class implements IdleObject, but we will expect
 		only pollin events though
 		'''
-		self.host, self.port = host, port
+		self.port = port
 		self.queue_idx = -1	
 		self.idlequeue = idlequeue
 		self.queue = None
diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py
index 40753ef4e014213d059e86b9f2132eae61b9914d..cf8fa67be595fd976b68b60af093963615da5913 100644
--- a/src/common/xmpp/client_nb.py
+++ b/src/common/xmpp/client_nb.py
@@ -74,10 +74,8 @@ class NBCommonClient(CommonClient):
 		''' Called on disconnection. Calls disconnect handlers and cleans things up. '''
 		self.connected=''
 		self.DEBUG(self.DBG,'Disconnect detected','stop')
-		self.disconnect_handlers.reverse()
-		for i in self.disconnect_handlers: 
+		for i in reversed(self.disconnect_handlers):
 			i()
-		self.disconnect_handlers.reverse()
 		if self.__dict__.has_key('NonBlockingRoster'):
 			self.NonBlockingRoster.PlugOut()
 		if self.__dict__.has_key('NonBlockingBind'):
@@ -125,6 +123,8 @@ class NBCommonClient(CommonClient):
 			self.on_connect_failure(retry)
 
 	def _on_connected(self):
+		# connect succeded, so no need of this callback anymore
+		self.on_connect_failure = None
 		self.connected = 'tcp'
 		if self._Ssl:
 			transports_nb.NonBlockingTLS().PlugIn(self, now=1)
diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py
index ca13af184327e9ff6c0091dfbb6d6bd7f0ca0674..30eb811b0d6c5899d9580977bd345a0adadc6df0 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -74,7 +74,6 @@ class Dispatcher(PlugIn):
 		self.RegisterProtocol('presence', Presence)
 		self.RegisterProtocol('message', Message)
 		self.RegisterDefaultHandler(self.returnStanzaHandler)
-		# Register Gajim's event handler as soon as dispatcher begins
 		self.RegisterEventHandler(self._owner._caller._event_dispatcher)
 		self.on_responses = {}
 		
@@ -84,7 +83,10 @@ class Dispatcher(PlugIn):
 		self._owner.lastErrNode = None
 		self._owner.lastErr = None
 		self._owner.lastErrCode = None
-		self.StreamInit()
+		if hasattr(self._owner, 'StreamInit'):
+			self._owner.StreamInit()
+		else:
+			self.StreamInit()
 	
 	def plugout(self):
 		''' Prepares instance to be destructed. '''
@@ -129,17 +131,18 @@ class Dispatcher(PlugIn):
 		try:
 			self.Stream.Parse(data)
 			# end stream:stream tag received
-			if self.Stream and self.Stream._NodeBuilder__depth == 0:
+			if self.Stream and self.Stream.has_received_endtag():
 				self._owner.Connection.disconnect()
 				return 0
 		except ExpatError:
 			sys.exc_clear()
-			self.DEBUG('Invalid XML received from server. Forcing disconnect.')
+			self.DEBUG('Invalid XML received from server. Forcing disconnect.', 'error')
 			self._owner.Connection.pollend()
 			return 0
 		if len(self._pendingExceptions) > 0:
 			 _pendingException = self._pendingExceptions.pop()
 			 raise _pendingException[0], _pendingException[1], _pendingException[2]
+		if len(data) == 0: return '0'
 		return len(data)
 	
 	def RegisterNamespace(self, xmlns, order='info'):
@@ -396,7 +399,7 @@ class Dispatcher(PlugIn):
 			Additional callback arguments can be specified in args. '''
 		self.SendAndWaitForResponse(stanza, 0, func, args)
 	
-	def send(self, stanza):
+	def send(self, stanza, is_message = False):
 		''' Serialise stanza and put it on the wire. Assign an unique ID to it before send.
 			Returns assigned ID.'''
 		if type(stanza) in [type(''), type(u'')]: 
@@ -423,7 +426,10 @@ class Dispatcher(PlugIn):
 			stanza=route
 		stanza.setNamespace(self._owner.Namespace)
 		stanza.setParent(self._metastream)
-		self._owner.Connection.send(stanza)
+		if is_message:
+			self._owner.Connection.send(stanza, True)
+		else:
+			self._owner.Connection.send(stanza)
 		return _ID
 	
 	def disconnect(self):
diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py
index 03b79109396067aa41b84ba4ad05ba750bb93fa2..db0c575bdd28f340977dcda39002fd9d5ff3dbc3 100644
--- a/src/common/xmpp/idlequeue.py
+++ b/src/common/xmpp/idlequeue.py
@@ -53,7 +53,6 @@ class IdleQueue:
 		self.selector = select.poll()
 	
 	def remove_timeout(self, fd):
-		''' self explanatory, remove the timeout from 'read_timeouts' dict  '''
 		if self.read_timeouts.has_key(fd):
 			del(self.read_timeouts[fd])
 	
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 1add2a8e4ee73c600355fc4024e7abda3d018a3a..daf3097738572b77707716f51c8a8a3675fa81ec 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -19,7 +19,7 @@ Protocol module contains tools that is needed for processing of
 xmpp-related data structures.
 """
 
-from simplexml import Node,ustr
+from simplexml import Node,NodeBuilder,ustr
 import time
 NS_ACTIVITY     ='http://jabber.org/protocol/activity'                  # JEP-0108
 NS_ADDRESS      ='http://jabber.org/protocol/address'                   # JEP-0033
@@ -94,6 +94,7 @@ NS_VCARD_UPDATE =NS_VCARD+':x:update'
 NS_VERSION      ='jabber:iq:version'
 NS_WAITINGLIST  ='http://jabber.org/protocol/waitinglist'               # JEP-0130
 NS_XHTML_IM     ='http://jabber.org/protocol/xhtml-im'                  # JEP-0071
+NS_XHTML        = 'http://www.w3.org/1999/xhtml'                        #  "
 NS_DATA_LAYOUT  ='http://jabber.org/protocol/xdata-layout'              # JEP-0141
 NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate'            # JEP-0122
 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
@@ -348,11 +349,18 @@ class Protocol(Node):
             for tag in errtag.getChildren():
                 if tag.getName()<>'text': return tag.getName()
             return errtag.getData()
+    def getErrorMsg(self):
+        """ Return the textual description of the error (if present) or the error condition """
+        errtag=self.getTag('error')
+        if errtag:
+            for tag in errtag.getChildren():
+                if tag.getName()=='text': return tag.getData()
+            return self.getError()
     def getErrorCode(self):
-        """ Return the error code. Obsolette. """
+        """ Return the error code. Obsolete. """
         return self.getTagAttr('error','code')
     def setError(self,error,code=None):
-        """ Set the error code. Obsolette. Use error-conditions instead. """
+        """ Set the error code. Obsolete. Use error-conditions instead. """
         if code:
             if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error)
             else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
@@ -378,16 +386,29 @@ class Protocol(Node):
 
 class Message(Protocol):
     """ XMPP Message stanza - "push" mechanism."""
-    def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
+    def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
         """ Create message object. You can specify recipient, text of message, type of message
             any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
             Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """
         Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
         if body: self.setBody(body)
+        if xhtml: self.setXHTML(xhtml)
         if subject: self.setSubject(subject)
     def getBody(self):
         """ Returns text of the message. """
         return self.getTagData('body')
+    def getXHTML(self, xmllang=None):
+        """ Returns serialized xhtml-im element text of the message.
+
+            TODO: Returning a DOM could make rendering faster."""
+        xhtml = self.getTag('html')
+        if xhtml:
+            if xmllang:
+                body = xhtml.getTag('body', attrs={'xml:lang':xmllang})
+            else:
+                body = xhtml.getTag('body')
+            return str(body)
+        return None
     def getSubject(self):
         """ Returns subject of the message. """
         return self.getTagData('subject')
@@ -397,6 +418,22 @@ class Message(Protocol):
     def setBody(self,val):
         """ Sets the text of the message. """
         self.setTagData('body',val)
+
+    def setXHTML(self,val,xmllang=None):
+        """ Sets the xhtml text of the message (JEP-0071).
+            The parameter is the "inner html" to the body."""
+        try:
+            if xmllang:
+                dom = NodeBuilder('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom()
+            else:
+                dom = NodeBuilder('<body xmlns="'+NS_XHTML+'">'+val+'</body>',0).getDom()
+            if self.getTag('html'):
+                self.getTag('html').addChild(node=dom)
+            else:
+                self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom)
+        except Exception, e:
+            print "Error", e
+            pass #FIXME: log. we could not set xhtml (parse error, whatever)
     def setSubject(self,val):
         """ Sets the subject of the message. """
         self.setTagData('subject',val)
diff --git a/src/common/xmpp/session.py b/src/common/xmpp/session.py
index 3921937ed95f5b05dd60f3e01c88eb4f53e86dd4..b61e4f6deeb7ed7db18d7c3cab18fa919c4e6e25 100644
--- a/src/common/xmpp/session.py
+++ b/src/common/xmpp/session.py
@@ -183,7 +183,7 @@ class Session:
         if self.sendbuffer:
             try:
                 # LOCK_QUEUE
-                sent=self._send(self.sendbuffer)    # âÌÏËÉÒÕÀÝÁÑ ÛÔÕÞËÁ!
+                sent=self._send(self.sendbuffer)    # blocking socket
             except:
                 # UNLOCK_QUEUE
                 self.set_socket_state(SOCKET_DEAD)
diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py
index af304af805d51455006a2aa3173893765303ed90..8aa8321ddf6844bde159accf4d4820e285ed4831 100644
--- a/src/common/xmpp/simplexml.py
+++ b/src/common/xmpp/simplexml.py
@@ -302,6 +302,8 @@ class NodeBuilder:
 		self.Parse = self._parser.Parse
 
 		self.__depth = 0
+		self.__last_depth = 0
+		self.__max_depth = 0
 		self._dispatch_depth = 1
 		self._document_attrs = None
 		self._mini_dom=initial_node
@@ -338,7 +340,7 @@ class NodeBuilder:
 			ns=attr[:sp]           #
 			attrs[self.namespaces[ns]+attr[sp+1:]]=attrs[attr]
 			del attrs[attr]        #
-		self.__depth += 1
+		self._inc_depth()
 		self.DEBUG(DBG_NODEBUILDER, "DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`), 'down')
 		if self.__depth == self._dispatch_depth:
 			if not self._mini_dom : 
@@ -366,7 +368,7 @@ class NodeBuilder:
 			self._ptr = self._ptr.parent
 		else:
 			self.DEBUG(DBG_NODEBUILDER, "Got higher than dispatch level. Stream terminated?", 'stop')
-		self.__depth -= 1
+		self._dec_depth()
 		self.last_is_data = 0
 		if self.__depth == 0: self.stream_footer_received()
 	
@@ -374,7 +376,7 @@ class NodeBuilder:
 		if self.last_is_data:
 			if self.data_buffer:
 				self.data_buffer.append(data)
-		else:
+		elif self._ptr:
 			self.data_buffer = [data]
 			self.last_is_data = 1
 	
@@ -399,6 +401,19 @@ class NodeBuilder:
 		""" Method called when stream just closed. """
 		self.check_data_buffer()
 
+	def has_received_endtag(self, level=0):
+		""" Return True if at least one end tag was seen (at level) """
+		return self.__depth <= level and self.__max_depth > level
+
+	def _inc_depth(self):
+		self.__last_depth = self.__depth
+		self.__depth += 1
+		self.__max_depth = max(self.__depth, self.__max_depth)
+
+	def _dec_depth(self):
+		self.__last_depth = self.__depth
+		self.__depth -= 1
+
 def XML2Node(xml):
 	""" Converts supplied textual string into XML node. Handy f.e. for reading configuration file.
 		Raises xml.parser.expat.parsererror if provided string is not well-formed XML. """
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index ad3456e6a6a0f8cd922d651aa7b9afd5a4529d53..528b9e08f1d537f27ac79b2e03e263411536cb4c 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -143,11 +143,11 @@ class NonBlockingTcp(PlugIn, IdleObject):
 	def pollin(self):
 		self._do_receive() 
 	
-	def pollend(self):
+	def pollend(self, retry = False):
 		conn_failure_cb = self.on_connect_failure
 		self.disconnect()
 		if conn_failure_cb:
-			conn_failure_cb()
+			conn_failure_cb(retry)
 		
 	def disconnect(self):
 		if self.state == -2: # already disconnected
@@ -216,15 +216,19 @@ class NonBlockingTcp(PlugIn, IdleObject):
 			# "received" will be empty anyhow 
 		if errnum == socket.SSL_ERROR_WANT_READ:
 			pass
-		elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
+		elif errnum  == errno.ECONNRESET:
+			self.pollend(True)
+			# don't proccess result, caus it will raise error
+			return
+		elif errnum in [errno.ENOTCONN, errno.ESHUTDOWN]:
 			self.pollend()
-			# don't proccess result, cas it will raise error
+			# don't proccess result, caus it will raise error
 			return
 		elif not received :
 			if errnum != socket.SSL_ERROR_EOF: 
 				# 8 EOF occurred in violation of protocol
 				self.DEBUG('Socket error while receiving data', 'error')
-				self.pollend()
+				self.pollend(True)
 			if self.state >= 0:
 				self.disconnect()
 			return
diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..da15b7ad5ec730d0de0ce7b9d44e435e67c925c3
--- /dev/null
+++ b/src/common/zeroconf/client_zeroconf.py
@@ -0,0 +1,647 @@
+##      common/zeroconf/client_zeroconf.py
+##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+## 				2006 Dimitur Kirov <dkirov@gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+from common import gajim
+import common.xmpp
+from common.xmpp.idlequeue import IdleObject
+from common.xmpp import dispatcher_nb, simplexml
+from common.xmpp.client import *
+from common.xmpp.simplexml import ustr
+from common.zeroconf import zeroconf
+
+from  common.xmpp.protocol import *
+import socket
+import errno
+import sys
+
+from common.zeroconf import roster_zeroconf
+
+MAX_BUFF_LEN = 65536
+DATA_RECEIVED='DATA RECEIVED'
+DATA_SENT='DATA SENT'
+TYPE_SERVER, TYPE_CLIENT = range(2)
+
+# wait XX sec to establish a connection
+CONNECT_TIMEOUT_SECONDS = 10
+
+# after XX sec with no activity, close the stream
+ACTIVITY_TIMEOUT_SECONDS = 30
+
+class ZeroconfListener(IdleObject):
+	def __init__(self, port, conn_holder):
+		''' handle all incomming connections on ('0.0.0.0', port)'''
+		self.port = port
+		self.queue_idx = -1	
+		#~ self.queue = None
+		self.started = False
+		self._sock = None
+		self.fd = -1
+		self.caller = conn_holder.caller
+		self.conn_holder = conn_holder
+		
+	def bind(self):
+		self._serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+		self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+		self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+		# will fail when port is busy, or we don't have rights to bind
+		try:
+			self._serv.bind(('0.0.0.0', self.port))
+		except Exception, e:
+			# unable to bind, show error dialog
+			return None
+		self._serv.listen(socket.SOMAXCONN)
+		self._serv.setblocking(False)
+		self.fd = self._serv.fileno()
+		gajim.idlequeue.plug_idle(self, False, True)
+		self.started = True
+	
+	def pollend(self):
+		''' called when we stop listening on (host, port) '''
+		self.disconnect()
+	
+	def pollin(self):
+		''' accept a new incomming connection and notify queue'''
+		sock = self.accept_conn()
+		P2PClient(sock[0], sock[1][0], sock[1][1], self.conn_holder)
+	
+	def disconnect(self):
+		''' free all resources, we are not listening anymore '''
+		gajim.idlequeue.remove_timeout(self.fd)
+		gajim.idlequeue.unplug_idle(self.fd)
+		self.fd = -1
+		self.started = False
+		try:
+			self._serv.close()
+		except:
+			pass
+		self.conn_holder.kill_all_connections()
+	
+	def accept_conn(self):
+		''' accepts a new incoming connection '''
+		_sock  = self._serv.accept()
+		_sock[0].setblocking(False)
+		return _sock
+
+class P2PClient(IdleObject):
+	def __init__(self, _sock, host, port, conn_holder, stanzaqueue = [], to = None):
+		self._owner = self
+		self.Namespace = 'jabber:client'
+		self.defaultNamespace = self.Namespace
+		self._component = 0
+		self._registered_name = None
+		self._caller = conn_holder.caller
+		self.conn_holder = conn_holder
+		self.stanzaqueue = stanzaqueue
+		self.to = to
+		self.Server = host
+		self.DBG = 'client'
+		self.Connection = None
+		if gajim.verbose:
+			debug = ['always', 'nodebuilder']
+		else:
+			debug = []
+		self._DEBUG = Debug.Debug(debug)
+		self.DEBUG = self._DEBUG.Show
+		self.debug_flags = self._DEBUG.debug_flags
+		self.debug_flags.append(self.DBG)
+		self.sock_hash = None
+		if _sock:
+			self.sock_type = TYPE_SERVER
+		else:
+			self.sock_type = TYPE_CLIENT
+		conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect, self)
+		self.sock_hash = conn._sock.__hash__
+		self.fd = conn.fd
+		self.conn_holder.add_connection(self, self.Server, port, self.to)
+		# count messages in queue
+		for val in self.stanzaqueue:
+			stanza, is_message = val
+			if is_message:
+				if self.conn_holder.number_of_awaiting_messages.has_key(self.fd):
+					self.conn_holder.number_of_awaiting_messages[self.fd]+=1
+				else:
+					self.conn_holder.number_of_awaiting_messages[self.fd]=1
+	
+	def add_stanza(self, stanza, is_message = False):
+		if self.Connection:
+			if self.Connection.state == -1:
+				return False
+			self.send(stanza, is_message)
+		else:
+			self.stanzaqueue.append((stanza, is_message))
+
+		if is_message:
+			if self.conn_holder.number_of_awaiting_messages.has_key(self.fd):
+				self.conn_holder.number_of_awaiting_messages[self.fd]+=1
+			else:
+				self.conn_holder.number_of_awaiting_messages[self.fd] = 1
+
+		return True
+	
+	def on_message_sent(self, connection_id):
+		self.conn_holder.number_of_awaiting_messages[connection_id]-=1
+
+	def on_connect(self, conn):
+		self.Connection = conn
+		self.Connection.PlugIn(self)
+		dispatcher_nb.Dispatcher().PlugIn(self)
+		self._register_handlers()
+		if self.sock_type == TYPE_CLIENT:
+			while self.stanzaqueue:
+				stanza, is_message = self.stanzaqueue.pop(0)
+				self.send(stanza, is_message)
+
+	def StreamInit(self):
+		''' Send an initial stream header. '''
+		self.Dispatcher.Stream = simplexml.NodeBuilder()
+		self.Dispatcher.Stream._dispatch_depth = 2
+		self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
+		self.Dispatcher.Stream.stream_header_received = self._check_stream_start
+		self.debug_flags.append(simplexml.DBG_NODEBUILDER)
+		self.Dispatcher.Stream.DEBUG = self.DEBUG
+		self.Dispatcher.Stream.features = None
+		if self.sock_type == TYPE_CLIENT:
+			self.send_stream_header()
+	
+	def send_stream_header(self):
+		self.Dispatcher._metastream = Node('stream:stream')
+		self.Dispatcher._metastream.setNamespace(self.Namespace)
+		# XXX TLS support
+		#~ self._metastream.setAttr('version', '1.0')
+		self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
+		self.Dispatcher.send("<?xml version='1.0'?>%s>" % str(self.Dispatcher._metastream)[:-2])
+	
+	def _check_stream_start(self, ns, tag, attrs):
+		if ns<>NS_STREAMS or tag<>'stream':
+			self._caller.dispatch('MSGERROR',[unicode(self.to), -1, \
+				_('Connection to host could not be established: Incorrect answer from server.'), None, None])
+			self.Connection.DEBUG('Incorrect stream start: (%s,%s).Terminating! ' % (tag, ns), 'error')
+			self.Connection.disconnect()
+			return
+		if self.sock_type == TYPE_SERVER:
+			self.send_stream_header()
+			while self.stanzaqueue:
+				stanza, is_message = self.stanzaqueue.pop(0)
+				self.send(stanza, is_message)
+
+	
+	def on_disconnect(self):
+		if self.conn_holder:
+			if self.conn_holder.number_of_awaiting_messages.has_key(self.fd):
+				if self.conn_holder.number_of_awaiting_messages[self.fd] > 0:
+					self._caller.dispatch('MSGERROR',[unicode(self.to), -1, \
+					_('Connection to host could not be established'), None, None])
+				del self.conn_holder.number_of_awaiting_messages[self.fd]
+			self.conn_holder.remove_connection(self.sock_hash) 
+		if self.__dict__.has_key('Dispatcher'):
+			self.Dispatcher.PlugOut()
+		if self.__dict__.has_key('P2PConnection'):
+			self.P2PConnection.PlugOut()
+		self.Connection = None
+		self._caller = None
+		self.conn_holder = None
+	
+	def force_disconnect(self):
+		if self.Connection:
+			self.disconnect()
+		else:
+			self.on_disconnect()
+		
+	def _on_receive_document_attrs(self, data):
+		if data:
+			self.Dispatcher.ProcessNonBlocking(data)
+		if not hasattr(self, 'Dispatcher') or \
+			self.Dispatcher.Stream._document_attrs is None:
+			return
+		self.onreceive(None)
+		if self.Dispatcher.Stream._document_attrs.has_key('version') and \
+			self.Dispatcher.Stream._document_attrs['version'] == '1.0':
+				#~ self.onreceive(self._on_receive_stream_features)
+				#XXX continue with TLS
+				return
+		self.onreceive(None)
+		return True
+		
+	def _register_handlers(self):
+		self.RegisterHandler('message', lambda conn, data:self._caller._messageCB(self.Server, conn, data))
+		self.RegisterHandler('iq', self._caller._siSetCB, 'set',
+			common.xmpp.NS_SI)
+		self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
+			common.xmpp.NS_SI)
+		self.RegisterHandler('iq', self._caller._siResultCB, 'result',
+			common.xmpp.NS_SI)
+		self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
+			common.xmpp.NS_BYTESTREAM)
+		self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
+			common.xmpp.NS_BYTESTREAM)
+		self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
+			common.xmpp.NS_BYTESTREAM)
+	
+class P2PConnection(IdleObject, PlugIn):
+	''' class for sending file to socket over socks5 '''
+	def __init__(self, sock_hash, _sock, host = None, port = None, caller = None, on_connect = None, client = None):
+		IdleObject.__init__(self)
+		self._owner = client
+		PlugIn.__init__(self)
+		self.DBG_LINE='socket'
+		self.sendqueue = []
+		self.sendbuff = None
+		self.buff_is_message = False
+		self._sock = _sock
+		self.sock_hash = None
+		self.host, self.port = host, port
+		self.on_connect = on_connect
+		self.client = client
+		self.writable = False
+		self.readable = False
+		self._exported_methods=[self.send, self.disconnect, self.onreceive]
+		self.on_receive = None
+		if _sock:
+			self._sock = _sock
+			self.state = 1 
+			self._sock.setblocking(False)
+			self.fd = self._sock.fileno()
+			self.on_connect(self)
+		else:
+			self.state = 0
+			self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+			self._sock.setblocking(False)
+			self.fd = self._sock.fileno()
+			gajim.idlequeue.plug_idle(self, True, False)
+			self.set_timeout(CONNECT_TIMEOUT_SECONDS)
+			self.do_connect()
+	
+	def set_timeout(self, timeout):
+		gajim.idlequeue.remove_timeout(self.fd)
+		if self.state >= 0:
+			gajim.idlequeue.set_read_timeout(self.fd, timeout)
+
+	def plugin(self, owner):
+		self.onreceive(owner._on_receive_document_attrs)
+		self._plug_idle()
+		return True
+
+	def plugout(self):
+		''' Disconnect from the remote server and unregister self.disconnected method from
+			the owner's dispatcher. '''
+		self.disconnect()
+		self._owner = None
+
+	def onreceive(self, recv_handler):
+		if not recv_handler:
+			if hasattr(self._owner, 'Dispatcher'):
+				self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
+			else:
+				self.on_receive = None
+			return
+		_tmp = self.on_receive
+		# make sure this cb is not overriden by recursive calls
+		if not recv_handler(None) and _tmp == self.on_receive:
+			self.on_receive = recv_handler
+		
+	def send(self, packet, is_message = False):
+		'''Append stanza to the queue of messages to be send. 
+		If supplied data is unicode string, encode it to utf-8.
+		'''
+		if self.state <= 0:
+			return
+
+		r = packet
+
+		if isinstance(r, unicode): 
+			r = r.encode('utf-8')
+		elif not isinstance(r, str): 
+			r = ustr(r).encode('utf-8')
+
+		self.sendqueue.append((r, is_message))
+		self._plug_idle()
+		
+	def read_timeout(self):
+		if self.client.conn_holder.number_of_awaiting_messages.has_key(self.fd) \
+				and self.client.conn_holder.number_of_awaiting_messages[self.fd] > 0:
+			self.client._caller.dispatch('MSGERROR',[unicode(self.client.to), -1, \
+					_('Connection to host could not be established: Timeout while sending data.'), None, None])
+			self.client.conn_holder.number_of_awaiting_messages[self.fd] = 0 
+		self.pollend()
+
+	def do_connect(self):
+		errnum = 0
+		try:
+			self._sock.connect((self.host, self.port))
+			self._sock.setblocking(False)
+		except Exception, ee:
+			(errnum, errstr) = ee
+		if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+			return
+		# win32 needs this
+		elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
+			self.disconnect()
+			return None
+		else: # socket is already connected
+			self._sock.setblocking(False)
+		self.state = 1 # connected
+		self.on_connect(self)
+		return 1 # we are connected
+	
+	
+	def pollout(self):
+		if self.state == 0:
+			self.do_connect()
+			return
+		gajim.idlequeue.remove_timeout(self.fd)
+		self._do_send()
+	
+	def pollend(self):
+		self.state = -1
+		self.disconnect()
+	
+	def pollin(self):
+		''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
+		received = ''
+		errnum = 0
+		try: 
+			# get as many bites, as possible, but not more than RECV_BUFSIZE
+			received = self._sock.recv(MAX_BUFF_LEN)
+		except Exception, e:
+			if len(e.args)  > 0 and isinstance(e.args[0], int):
+				errnum = e[0]
+			sys.exc_clear()
+			# "received" will be empty anyhow 
+		if errnum == socket.SSL_ERROR_WANT_READ:
+			pass
+		elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
+			self.pollend()
+			# don't proccess result, cas it will raise error
+			return
+		elif not received :
+			if errnum != socket.SSL_ERROR_EOF: 
+				# 8 EOF occurred in violation of protocol
+				self.pollend()
+			if self.state >= 0:
+				self.disconnect()
+			return
+		
+		if self.state < 0:
+			return
+		if self.on_receive:
+			if self._owner.sock_type == TYPE_CLIENT:
+				self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
+			if received.strip():
+				self.DEBUG(received, 'got')
+			if hasattr(self._owner, 'Dispatcher'):
+				self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
+			self.on_receive(received)
+		else:
+			# This should never happed, so we need the debug
+			self.DEBUG('Unhandled data received: %s' % received,'error')
+			self.disconnect()
+		return True
+	
+	def onreceive(self, recv_handler):
+		if not recv_handler:
+			if hasattr(self._owner, 'Dispatcher'):
+				self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
+			else:
+				self.on_receive = None
+			return
+		_tmp = self.on_receive
+		# make sure this cb is not overriden by recursive calls
+		if not recv_handler(None) and _tmp == self.on_receive:
+			self.on_receive = recv_handler
+	
+	def disconnect(self):
+		''' Closes the socket. '''
+		gajim.idlequeue.remove_timeout(self.fd)
+		gajim.idlequeue.unplug_idle(self.fd)
+		try:
+			self._sock.shutdown(socket.SHUT_RDWR)
+			self._sock.close()
+		except:
+			# socket is already closed
+			pass
+		self.fd = -1
+		self.state = -1
+		if self._owner:
+			self._owner.on_disconnect()
+
+	def _do_send(self):
+		if not self.sendbuff:
+			if not self.sendqueue:
+				return None # nothing to send
+			self.sendbuff, self.buff_is_message = self.sendqueue.pop(0)
+			self.sent_data = self.sendbuff
+		try:
+			send_count = self._sock.send(self.sendbuff)
+			if send_count:
+				self.sendbuff = self.sendbuff[send_count:]
+				if not self.sendbuff and not self.sendqueue:
+					if self.state < 0:
+						gajim.idlequeue.unplug_idle(self.fd)
+						self._on_send()
+						self.disconnect()
+						return
+					# we are not waiting for write 
+					self._plug_idle()
+				self._on_send()
+
+		except socket.error, e:
+			sys.exc_clear()
+			if e[0] == socket.SSL_ERROR_WANT_WRITE:
+				return True		
+			if self.state < 0:
+				self.disconnect()
+				return
+			self._on_send_failure()
+			return
+		if self._owner.sock_type == TYPE_CLIENT:
+			self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
+		return True
+	
+	def _plug_idle(self):
+		readable = self.state != 0
+		if self.sendqueue or self.sendbuff:
+			writable = True
+		else:
+			writable = False
+		if self.writable != writable or self.readable != readable:
+			gajim.idlequeue.plug_idle(self, writable, readable)
+	
+	
+	def _on_send(self):
+		if self.sent_data and self.sent_data.strip():
+			self.DEBUG(self.sent_data,'sent')
+			if hasattr(self._owner, 'Dispatcher'):
+				self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
+		self.sent_data = None
+		if self.buff_is_message:
+			self._owner.on_message_sent(self.fd)
+			self.buff_is_message = False
+
+	def _on_send_failure(self):
+		self.DEBUG("Socket error while sending data",'error')
+		self._owner.disconnected()
+		self.sent_data = None
+
+
+class ClientZeroconf:
+	def __init__(self, caller):
+		self.caller = caller
+		self.zeroconf = None
+		self.roster = None
+		self.last_msg = ''
+		self.connections = {}
+		self.recipient_to_hash = {}
+		self.ip_to_hash = {}
+		self.hash_to_port = {}
+		self.listener = None
+		self.number_of_awaiting_messages = {}
+
+	def test_avahi(self):
+		try:
+			import avahi
+		except ImportError:
+			return False
+		return True
+
+	def connect(self, show, msg):
+		self.port = self.start_listener(self.caller.port)
+		if not self.port:
+			return False
+		self.zeroconf_init(show, msg)
+		if not self.zeroconf.connect():
+			self.disconnect()
+			return None
+		self.roster = roster_zeroconf.Roster(self.zeroconf)
+		return True
+
+	def remove_announce(self):
+		if self.zeroconf:
+			return self.zeroconf.remove_announce()
+	
+	def announce(self):
+		if self.zeroconf:
+			return self.zeroconf.announce()
+	
+	def set_show_msg(self, show, msg):
+		if self.zeroconf:
+			self.zeroconf.txt['msg'] = msg
+			self.last_msg = msg
+			return self.zeroconf.update_txt(show)
+	
+	def resolve_all(self):
+		if self.zeroconf:
+			self.zeroconf.resolve_all()
+	
+	def reannounce(self, txt):
+		self.remove_announce()
+		self.zeroconf.txt = txt
+		self.zeroconf.port = self.port
+		self.zeroconf.username = self.caller.username
+		return self.announce()
+
+
+	def zeroconf_init(self, show, msg):
+		self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
+			self.caller._on_remove_service, self.caller._on_name_conflictCB, 
+			self.caller._on_disconnected, self.caller._on_error, self.caller.username, self.caller.host, self.port)
+		self.zeroconf.txt['msg'] = msg
+		self.zeroconf.txt['status'] = show
+		self.zeroconf.txt['1st'] = self.caller.first
+		self.zeroconf.txt['last'] = self.caller.last
+		self.zeroconf.txt['jid'] = self.caller.jabber_id
+		self.zeroconf.txt['email'] = self.caller.email
+		self.zeroconf.username = self.caller.username
+		self.zeroconf.host = self.caller.host
+		self.zeroconf.port = self.port
+		self.last_msg = msg
+
+	def disconnect(self):
+		if self.listener:
+			self.listener.disconnect()
+			self.listener = None
+		if self.zeroconf:
+			self.zeroconf.disconnect()
+			self.zeroconf = None
+		if self.roster:
+			self.roster.zeroconf = None
+			self.roster._data = None
+			self.roster = None
+		
+	def kill_all_connections(self):
+		for connection in self.connections.values():
+			connection.force_disconnect()
+	
+	def add_connection(self, connection, ip, port, recipient):
+		sock_hash = connection.sock_hash
+		if sock_hash not in self.connections:
+			self.connections[sock_hash] = connection
+		self.ip_to_hash[ip] = sock_hash
+		self.hash_to_port[sock_hash] = port
+		if recipient:
+			self.recipient_to_hash[recipient] = sock_hash
+		
+	def remove_connection(self, sock_hash):
+		if sock_hash in self.connections:
+			del self.connections[sock_hash]
+		for i in self.recipient_to_hash:
+			if self.recipient_to_hash[i] == sock_hash:
+				del self.recipient_to_hash[i]
+				break
+		for i in self.ip_to_hash:
+			if self.ip_to_hash[i] == sock_hash:
+				del self.ip_to_hash[i]
+				break
+		if self.hash_to_port.has_key(sock_hash):
+			del self.hash_to_port[sock_hash]
+
+	def start_listener(self, port):
+		for p in range(port, port + 5):
+			self.listener = ZeroconfListener(p, self)
+			self.listener.bind()
+			if self.listener.started:
+				return p
+		self.listener = None
+		return False
+	
+	def getRoster(self):
+		if self.roster:
+			return self.roster.getRoster()
+		return {}
+
+	def send(self, stanza, is_message = False):
+		stanza.setFrom(self.roster.zeroconf.name)
+		to = stanza.getTo()
+		
+		try:
+			item = self.roster[to]
+		except KeyError:
+			self.caller.dispatch('MSGERROR', [unicode(to), '-1', _('Contact is offline. Your message could not be sent.'), None, None])
+			return False
+	
+		# look for hashed connections
+		if to in self.recipient_to_hash:
+			conn = self.connections[self.recipient_to_hash[to]]
+			if conn.add_stanza(stanza, is_message):
+				return
+
+		if item['address'] in self.ip_to_hash:
+			hash = self.ip_to_hash[item['address']]
+			if self.hash_to_port[hash] == item['port']:
+				conn = self.connections[hash]
+				if conn.add_stanza(stanza, is_message):
+					return
+
+		# otherwise open new connection
+		P2PClient(None, item['address'], item['port'], self, [(stanza, is_message)], to)
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..238de3ee8ce82b58b575080e05cdc54335d21a57
--- /dev/null
+++ b/src/common/zeroconf/connection_handlers_zeroconf.py
@@ -0,0 +1,906 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## Contributors for this file:
+##	- Yann Le Boulanger <asterix@lagaule.org>
+##	- Nikos Kouremenos <nkour@jabber.org>
+##	- Dimitur Kirov <dkirov@gmail.com>
+##	- Travis Shirk <travis@pobox.com>
+##  - Stefan Bethge <stefan@lanpartei.de> 
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+import os
+import time
+import base64
+import sha
+import socket
+import sys
+
+from calendar import timegm
+
+#import socks5
+import common.xmpp
+
+from common import GnuPG
+from common import helpers
+from common import gajim
+from common.zeroconf import zeroconf
+STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
+	'invisible']
+# kind of events we can wait for an answer
+VCARD_PUBLISHED = 'vcard_published'
+VCARD_ARRIVED = 'vcard_arrived'
+AGENT_REMOVED = 'agent_removed'
+HAS_IDLE = True
+try:
+	import idle
+except:
+	gajim.log.debug(_('Unable to load idle module'))
+	HAS_IDLE = False
+
+
+class ConnectionBytestream:
+	def __init__(self):
+		self.files_props = {}
+	
+	def is_transfer_stoped(self, file_props):
+		if file_props.has_key('error') and file_props['error'] != 0:
+			return True
+		if file_props.has_key('completed') and file_props['completed']:
+			return True
+		if file_props.has_key('connected') and file_props['connected'] == False:
+			return True
+		if not file_props.has_key('stopped') or not file_props['stopped']:
+			return False
+		return True
+	
+	def send_success_connect_reply(self, streamhost):
+		''' send reply to the initiator of FT that we
+		made a connection
+		'''
+		if streamhost is None:
+			return None
+		iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result',
+			frm = streamhost['target'])
+		iq.setAttr('id', streamhost['id'])
+		query = iq.setTag('query')
+		query.setNamespace(common.xmpp.NS_BYTESTREAM)
+		stream_tag = query.setTag('streamhost-used')
+		stream_tag.setAttr('jid', streamhost['jid'])
+		self.connection.send(iq)
+	
+	def remove_transfers_for_contact(self, contact):
+		''' stop all active transfer for contact '''
+		for file_props in self.files_props.values():
+			if self.is_transfer_stoped(file_props):
+				continue
+			receiver_jid = unicode(file_props['receiver']).split('/')[0]
+			if contact.jid == receiver_jid:
+				file_props['error'] = -5
+				self.remove_transfer(file_props)
+				self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
+			sender_jid = unicode(file_props['sender'])
+			if contact.jid == sender_jid:
+				file_props['error'] = -3
+				self.remove_transfer(file_props)
+	
+	def remove_all_transfers(self):
+		''' stops and removes all active connections from the socks5 pool '''
+		for file_props in self.files_props.values():
+			self.remove_transfer(file_props, remove_from_list = False)
+		del(self.files_props)
+		self.files_props = {}
+	
+	def remove_transfer(self, file_props, remove_from_list = True):
+		if file_props is None:
+			return
+		self.disconnect_transfer(file_props)
+		sid = file_props['sid']
+		gajim.socks5queue.remove_file_props(self.name, sid)
+
+		if remove_from_list:
+			if self.files_props.has_key('sid'):
+				del(self.files_props['sid'])
+	
+	def disconnect_transfer(self, file_props):
+		if file_props is None:
+			return
+		if file_props.has_key('hash'):
+			gajim.socks5queue.remove_sender(file_props['hash'])
+
+		if file_props.has_key('streamhosts'):
+			for host in file_props['streamhosts']:
+				if host.has_key('idx') and host['idx'] > 0:
+					gajim.socks5queue.remove_receiver(host['idx'])
+					gajim.socks5queue.remove_sender(host['idx'])
+	
+	def send_socks5_info(self, file_props, fast = True, receiver = None,
+		sender = None):
+		''' send iq for the present streamhosts and proxies '''
+		if type(self.peerhost) != tuple:
+			return
+		port = gajim.config.get('file_transfers_port')
+		ft_override_host_to_send = gajim.config.get('ft_override_host_to_send')
+		if receiver is None:
+			receiver = file_props['receiver']
+		if sender is None:
+			sender = file_props['sender']
+		proxyhosts = []
+		sha_str = helpers.get_auth_sha(file_props['sid'], sender,
+			receiver)
+		file_props['sha_str'] = sha_str
+		if not ft_override_host_to_send:
+			ft_override_host_to_send = self.peerhost[0]
+		try:
+			ft_override_host_to_send = socket.gethostbyname(
+				ft_override_host_to_send)
+		except socket.gaierror:
+			self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.')))
+			ft_override_host_to_send = self.peerhost[0]
+		listener = gajim.socks5queue.start_listener(port,
+			sha_str, self._result_socks5_sid, file_props['sid'])
+		if listener == None:
+			file_props['error'] = -5
+			self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props,
+				''))
+			self._connect_error(unicode(receiver), file_props['sid'],
+				file_props['sid'], code = 406)
+			return
+
+		iq = common.xmpp.Protocol(name = 'iq', to = unicode(receiver),
+			typ = 'set')
+		file_props['request-id'] = 'id_' + file_props['sid']
+		iq.setID(file_props['request-id'])
+		query = iq.setTag('query')
+		query.setNamespace(common.xmpp.NS_BYTESTREAM)
+		query.setAttr('mode', 'tcp')
+		query.setAttr('sid', file_props['sid'])
+		streamhost = query.setTag('streamhost')
+		streamhost.setAttr('port', unicode(port))
+		streamhost.setAttr('host', ft_override_host_to_send)
+		streamhost.setAttr('jid', sender)
+		self.connection.send(iq)
+
+	def send_file_rejection(self, file_props):
+		''' informs sender that we refuse to download the file '''
+		# user response to ConfirmationDialog may come after we've disconneted
+		if not self.connection or self.connected < 2:
+			return
+		iq = common.xmpp.Protocol(name = 'iq',
+			to = unicode(file_props['sender']), typ = 'error')
+		iq.setAttr('id', file_props['request-id'])
+		err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name =
+			'forbidden', text = 'Offer Declined')
+		iq.addChild(node=err)
+		self.connection.send(iq)
+
+	def send_file_approval(self, file_props):
+		''' send iq, confirming that we want to download the file '''
+		# user response to ConfirmationDialog may come after we've disconneted
+		if not self.connection or self.connected < 2:
+			return
+		iq = common.xmpp.Protocol(name = 'iq',
+			to = unicode(file_props['sender']), typ = 'result')
+		iq.setAttr('id', file_props['request-id'])
+		si = iq.setTag('si')
+		si.setNamespace(common.xmpp.NS_SI)
+		if file_props.has_key('offset') and file_props['offset']:
+			file_tag = si.setTag('file')
+			file_tag.setNamespace(common.xmpp.NS_FILE)
+			range_tag = file_tag.setTag('range')
+			range_tag.setAttr('offset', file_props['offset'])
+		feature = si.setTag('feature')
+		feature.setNamespace(common.xmpp.NS_FEATURE)
+		_feature = common.xmpp.DataForm(typ='submit')
+		feature.addChild(node=_feature)
+		field = _feature.setField('stream-method')
+		field.delAttr('type')
+		field.setValue(common.xmpp.NS_BYTESTREAM)
+		self.connection.send(iq)
+
+	def send_file_request(self, file_props):
+		''' send iq for new FT request '''
+		if not self.connection or self.connected < 2:
+			return
+		our_jid = gajim.get_jid_from_account(self.name)
+		frm = our_jid
+		file_props['sender'] = frm
+		fjid = file_props['receiver'].jid 
+		iq = common.xmpp.Protocol(name = 'iq', to = fjid,
+			typ = 'set')
+		iq.setID(file_props['sid'])
+		self.files_props[file_props['sid']] = file_props
+		si = iq.setTag('si')
+		si.setNamespace(common.xmpp.NS_SI)
+		si.setAttr('profile', common.xmpp.NS_FILE)
+		si.setAttr('id', file_props['sid'])
+		file_tag = si.setTag('file')
+		file_tag.setNamespace(common.xmpp.NS_FILE)
+		file_tag.setAttr('name', file_props['name'])
+		file_tag.setAttr('size', file_props['size'])
+		desc = file_tag.setTag('desc')
+		if file_props.has_key('desc'):
+			desc.setData(file_props['desc'])
+		file_tag.setTag('range')
+		feature = si.setTag('feature')
+		feature.setNamespace(common.xmpp.NS_FEATURE)
+		_feature = common.xmpp.DataForm(typ='form')
+		feature.addChild(node=_feature)
+		field = _feature.setField('stream-method')
+		field.setAttr('type', 'list-single')
+		field.addOption(common.xmpp.NS_BYTESTREAM)
+		self.connection.send(iq)
+	
+	def _result_socks5_sid(self, sid, hash_id):
+		''' store the result of sha message from auth. '''
+		if not self.files_props.has_key(sid):
+			return
+		file_props = self.files_props[sid]
+		file_props['hash'] = hash_id
+		return
+	
+	def _connect_error(self, to, _id, sid, code = 404):
+		''' cb, when there is an error establishing BS connection, or 
+		when connection is rejected'''
+		msg_dict = {
+			404: 'Could not connect to given hosts',
+			405: 'Cancel',
+			406: 'Not acceptable',
+		}
+		msg = msg_dict[code]
+		iq = None
+		iq = common.xmpp.Protocol(name = 'iq', to = to,
+			typ = 'error')
+		iq.setAttr('id', _id)
+		err = iq.setTag('error')
+		err.setAttr('code', unicode(code))
+		err.setData(msg)
+		self.connection.send(iq)
+		if code == 404:
+			file_props = gajim.socks5queue.get_file_props(self.name, sid)
+			if file_props is not None:
+				self.disconnect_transfer(file_props)
+				file_props['error'] = -3
+				self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
+
+	def _proxy_auth_ok(self, proxy):
+		'''cb, called after authentication to proxy server '''
+		file_props = self.files_props[proxy['sid']]
+		iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'],
+		typ = 'set')
+		auth_id = "au_" + proxy['sid']
+		iq.setID(auth_id)
+		query = iq.setTag('query')
+		query.setNamespace(common.xmpp.NS_BYTESTREAM)
+		query.setAttr('sid',  proxy['sid'])
+		activate = query.setTag('activate')
+		activate.setData(file_props['proxy_receiver'])
+		iq.setID(auth_id)
+		self.connection.send(iq)
+	
+	# register xmpppy handlers for bytestream and FT stanzas
+	def _bytestreamErrorCB(self, con, iq_obj):
+		gajim.log.debug('_bytestreamErrorCB')
+		id = unicode(iq_obj.getAttr('id'))
+		frm = unicode(iq_obj.getFrom())
+		query = iq_obj.getTag('query')
+		gajim.proxy65_manager.error_cb(frm, query)
+		jid = unicode(iq_obj.getFrom())
+		id = id[3:]
+		if not self.files_props.has_key(id):
+			return
+		file_props = self.files_props[id]
+		file_props['error'] = -4
+		self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
+		raise common.xmpp.NodeProcessed
+	
+	def _bytestreamSetCB(self, con, iq_obj):
+		gajim.log.debug('_bytestreamSetCB')
+		target = unicode(iq_obj.getAttr('to'))
+		id = unicode(iq_obj.getAttr('id'))
+		query = iq_obj.getTag('query')
+		sid = unicode(query.getAttr('sid'))
+		file_props = gajim.socks5queue.get_file_props(
+			self.name, sid)
+		streamhosts=[]
+		for item in query.getChildren():
+			if item.getName() == 'streamhost':
+				host_dict={
+					'state': 0,
+					'target': target,
+					'id': id,
+					'sid': sid,
+					'initiator': unicode(iq_obj.getFrom())
+				}
+				for attr in item.getAttrs():
+					host_dict[attr] = item.getAttr(attr)
+				streamhosts.append(host_dict)
+		if file_props is None:
+			if self.files_props.has_key(sid):
+				file_props = self.files_props[sid]
+				file_props['fast'] = streamhosts
+				if file_props['type'] == 's': 
+					if file_props.has_key('streamhosts'):
+						file_props['streamhosts'].extend(streamhosts)
+					else:
+						file_props['streamhosts'] = streamhosts
+					if not gajim.socks5queue.get_file_props(self.name, sid):
+						gajim.socks5queue.add_file_props(self.name, file_props)
+					gajim.socks5queue.connect_to_hosts(self.name, sid,
+						self.send_success_connect_reply, None)
+				raise common.xmpp.NodeProcessed
+
+		file_props['streamhosts'] = streamhosts
+		if file_props['type'] == 'r':
+			gajim.socks5queue.connect_to_hosts(self.name, sid,
+				self.send_success_connect_reply, self._connect_error)
+		raise common.xmpp.NodeProcessed
+
+	def _ResultCB(self, con, iq_obj):
+		gajim.log.debug('_ResultCB')
+		# if we want to respect jep-0065 we have to check for proxy
+		# activation result in any result iq
+		real_id = unicode(iq_obj.getAttr('id'))
+		if real_id[:3] != 'au_':
+			return
+		frm = unicode(iq_obj.getFrom())
+		id = real_id[3:]
+		if self.files_props.has_key(id):
+			file_props = self.files_props[id]
+			if file_props['streamhost-used']:
+				for host in file_props['proxyhosts']:
+					if host['initiator'] == frm and host.has_key('idx'):
+						gajim.socks5queue.activate_proxy(host['idx'])
+						raise common.xmpp.NodeProcessed
+	
+	def _bytestreamResultCB(self, con, iq_obj):
+		gajim.log.debug('_bytestreamResultCB')
+		frm = unicode(iq_obj.getFrom())
+		real_id = unicode(iq_obj.getAttr('id'))
+		query = iq_obj.getTag('query')
+		gajim.proxy65_manager.resolve_result(frm, query)
+		
+		try:
+			streamhost =  query.getTag('streamhost-used')
+		except: # this bytestream result is not what we need
+			pass
+		id = real_id[3:]
+		if self.files_props.has_key(id):
+			file_props = self.files_props[id]
+		else:
+			raise common.xmpp.NodeProcessed
+		if streamhost is None:
+			# proxy approves the activate query
+			if real_id[:3] == 'au_':
+				id = real_id[3:]
+				if not file_props.has_key('streamhost-used') or \
+					file_props['streamhost-used'] is False:
+					raise common.xmpp.NodeProcessed
+				if not file_props.has_key('proxyhosts'):
+					raise common.xmpp.NodeProcessed
+				for host in file_props['proxyhosts']:
+					if host['initiator'] == frm and \
+					unicode(query.getAttr('sid')) == file_props['sid']:
+						gajim.socks5queue.activate_proxy(host['idx'])
+						break
+			raise common.xmpp.NodeProcessed
+		jid = streamhost.getAttr('jid')
+		if file_props.has_key('streamhost-used') and \
+			file_props['streamhost-used'] is True:
+			raise common.xmpp.NodeProcessed
+
+		if real_id[:3] == 'au_':
+			gajim.socks5queue.send_file(file_props, self.name)
+			raise common.xmpp.NodeProcessed
+
+		proxy = None
+		if file_props.has_key('proxyhosts'):
+			for proxyhost in file_props['proxyhosts']:
+				if proxyhost['jid'] == jid:
+					proxy = proxyhost
+
+		if proxy != None:
+			file_props['streamhost-used'] = True
+			if not file_props.has_key('streamhosts'):
+				file_props['streamhosts'] = []
+			file_props['streamhosts'].append(proxy)
+			file_props['is_a_proxy'] = True
+			receiver = socks5.Socks5Receiver(gajim.idlequeue, proxy, file_props['sid'], file_props)
+			gajim.socks5queue.add_receiver(self.name, receiver)
+			proxy['idx'] = receiver.queue_idx
+			gajim.socks5queue.on_success = self._proxy_auth_ok
+			raise common.xmpp.NodeProcessed
+
+		else:
+			gajim.socks5queue.send_file(file_props, self.name)
+			if file_props.has_key('fast'):
+				fasts = file_props['fast']
+				if len(fasts) > 0:
+					self._connect_error(frm, fasts[0]['id'], file_props['sid'],
+						code = 406)
+		
+		raise common.xmpp.NodeProcessed
+	
+	def _siResultCB(self, con, iq_obj):
+		gajim.log.debug('_siResultCB')
+		self.peerhost = con._owner.Connection._sock.getsockname()
+		id = iq_obj.getAttr('id')
+		if not self.files_props.has_key(id):
+			# no such jid
+			return
+		file_props = self.files_props[id]
+		if file_props is None:
+			# file properties for jid is none
+			return
+		if file_props.has_key('request-id'):
+			# we have already sent streamhosts info
+			return
+		file_props['receiver'] = unicode(iq_obj.getFrom())
+		si = iq_obj.getTag('si')
+		file_tag = si.getTag('file')
+		range_tag = None
+		if file_tag:
+			range_tag = file_tag.getTag('range')
+		if range_tag:
+			offset = range_tag.getAttr('offset')
+			if offset:
+				file_props['offset'] = int(offset)
+			length = range_tag.getAttr('length')
+			if length:
+				file_props['length'] = int(length)
+		feature = si.setTag('feature')
+		if feature.getNamespace() != common.xmpp.NS_FEATURE:
+			return
+		form_tag = feature.getTag('x')
+		form = common.xmpp.DataForm(node=form_tag)
+		field = form.getField('stream-method')
+		if field.getValue() != common.xmpp.NS_BYTESTREAM:
+			return
+		self.send_socks5_info(file_props, fast = True)
+		raise common.xmpp.NodeProcessed
+	
+	def _siSetCB(self, con, iq_obj):
+		gajim.log.debug('_siSetCB')
+		jid = unicode(iq_obj.getFrom())
+		si = iq_obj.getTag('si')
+		profile = si.getAttr('profile')
+		mime_type = si.getAttr('mime-type')
+		if profile != common.xmpp.NS_FILE:
+			return
+		file_tag = si.getTag('file')
+		file_props = {'type': 'r'}
+		for attribute in file_tag.getAttrs():
+			if attribute in ('name', 'size', 'hash', 'date'):
+				val = file_tag.getAttr(attribute)
+				if val is None:
+					continue
+				file_props[attribute] = val
+		file_desc_tag = file_tag.getTag('desc')
+		if file_desc_tag is not None:
+			file_props['desc'] = file_desc_tag.getData()
+
+		if mime_type is not None:
+			file_props['mime-type'] = mime_type
+		our_jid = gajim.get_jid_from_account(self.name)
+		file_props['receiver'] = our_jid 
+		file_props['sender'] = unicode(iq_obj.getFrom())
+		file_props['request-id'] = unicode(iq_obj.getAttr('id'))
+		file_props['sid'] = unicode(si.getAttr('id'))
+		gajim.socks5queue.add_file_props(self.name, file_props)
+		self.dispatch('FILE_REQUEST', (jid, file_props))
+		raise common.xmpp.NodeProcessed
+
+	def _siErrorCB(self, con, iq_obj):
+		gajim.log.debug('_siErrorCB')
+		si = iq_obj.getTag('si')
+		profile = si.getAttr('profile')
+		if profile != common.xmpp.NS_FILE:
+			return
+		id = iq_obj.getAttr('id')
+		if not self.files_props.has_key(id):
+			# no such jid
+			return
+		file_props = self.files_props[id]
+		if file_props is None:
+			# file properties for jid is none
+			return
+		jid = unicode(iq_obj.getFrom())
+		file_props['error'] = -3
+		self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
+		raise common.xmpp.NodeProcessed
+
+
+
+class ConnectionVcard:
+	def __init__(self):
+		self.vcard_sha = None
+		self.vcard_shas = {} # sha of contacts
+		self.room_jids = [] # list of gc jids so that vcard are saved in a folder
+		
+	def add_sha(self, p, send_caps = True):
+		'''
+		c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE)
+		if self.vcard_sha is not None:
+			c.setTagData('photo', self.vcard_sha)
+		if send_caps:
+			return self.add_caps(p)
+		return p
+		'''
+		pass
+	
+	def add_caps(self, p):
+		'''
+		# advertise our capabilities in presence stanza (jep-0115)
+		c = p.setTag('c', namespace = common.xmpp.NS_CAPS)
+		c.setAttr('node', 'http://gajim.org/caps')
+		c.setAttr('ext', 'ftrans')
+		c.setAttr('ver', gajim.version)
+		return p
+		'''
+		pass
+	
+	def node_to_dict(self, node):
+		dict = {}
+		
+		for info in node.getChildren():
+			name = info.getName()
+			if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
+				if not dict.has_key(name):
+					dict[name] = []
+				entry = {}
+				for c in info.getChildren():
+					 entry[c.getName()] = c.getData()
+				dict[name].append(entry)
+			elif info.getChildren() == []:
+				dict[name] = info.getData()
+			else:
+				dict[name] = {}
+				for c in info.getChildren():
+					 dict[name][c.getName()] = c.getData()
+		
+		return dict
+
+	def save_vcard_to_hd(self, full_jid, card):
+		jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
+		puny_jid = helpers.sanitize_filename(jid)
+		path = os.path.join(gajim.VCARD_PATH, puny_jid)
+		if jid in self.room_jids or os.path.isdir(path):
+			# remove room_jid file if needed
+			if os.path.isfile(path):
+				os.remove(path)
+			# create folder if needed
+			if not os.path.isdir(path):
+				os.mkdir(path, 0700)
+			puny_nick = helpers.sanitize_filename(nick)
+			path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+		else:
+			path_to_file = path
+		fil = open(path_to_file, 'w')
+		fil.write(str(card))
+		fil.close()
+	
+	def get_cached_vcard(self, fjid, is_fake_jid = False):
+		'''return the vcard as a dict
+		return {} if vcard was too old
+		return None if we don't have cached vcard'''
+		jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
+		puny_jid = helpers.sanitize_filename(jid)
+		if is_fake_jid:
+			puny_nick = helpers.sanitize_filename(nick)
+			path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+		else:
+			path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
+		if not os.path.isfile(path_to_file):
+			return None
+		# We have the vcard cached
+		f = open(path_to_file)
+		c = f.read()
+		f.close()
+		card = common.xmpp.Node(node = c)
+		vcard = self.node_to_dict(card)
+		if vcard.has_key('PHOTO'):
+			if not isinstance(vcard['PHOTO'], dict):
+				del vcard['PHOTO']
+			elif vcard['PHOTO'].has_key('SHA'):
+				cached_sha = vcard['PHOTO']['SHA']
+				if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \
+					cached_sha:
+					# user change his vcard so don't use the cached one
+					return {}
+		vcard['jid'] = jid
+		vcard['resource'] = gajim.get_resource_from_jid(fjid)
+		return vcard
+
+	def request_vcard(self, jid = None, is_fake_jid = False):
+		'''request the VCARD. If is_fake_jid is True, it means we request a vcard
+		to a fake jid, like in private messages in groupchat'''
+		if not self.connection:
+			return
+		'''
+		iq = common.xmpp.Iq(typ = 'get')
+		if jid:
+			iq.setTo(jid)
+		iq.setTag(common.xmpp.NS_VCARD + ' vCard')
+
+		id = self.connection.getAnID()
+		iq.setID(id)
+		self.awaiting_answers[id] = (VCARD_ARRIVED, jid)
+		if is_fake_jid:
+			room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+			if not room_jid in self.room_jids:
+				self.room_jids.append(room_jid)
+		self.connection.send(iq)
+			#('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
+		'''
+		pass
+		
+	def send_vcard(self, vcard):
+		if not self.connection:
+			return
+		'''
+		iq = common.xmpp.Iq(typ = 'set')
+		iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard')
+		for i in vcard:
+			if i == 'jid':
+				continue
+			if isinstance(vcard[i], dict):
+				iq3 = iq2.addChild(i)
+				for j in vcard[i]:
+					iq3.addChild(j).setData(vcard[i][j])
+			elif type(vcard[i]) == type([]):
+				for j in vcard[i]:
+					iq3 = iq2.addChild(i)
+					for k in j:
+						iq3.addChild(k).setData(j[k])
+			else:
+				iq2.addChild(i).setData(vcard[i])
+
+		id = self.connection.getAnID()
+		iq.setID(id)
+		self.connection.send(iq)
+
+		# Add the sha of the avatar
+		if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \
+		vcard['PHOTO'].has_key('BINVAL'):
+			photo = vcard['PHOTO']['BINVAL']
+			photo_decoded = base64.decodestring(photo)
+			our_jid = gajim.get_jid_from_account(self.name)
+			gajim.interface.save_avatar_files(our_jid, photo_decoded)
+			avatar_sha = sha.sha(photo_decoded).hexdigest()
+			iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
+
+		self.awaiting_answers[id] = (VCARD_PUBLISHED, iq2)
+		'''
+		pass
+		
+class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
+	def __init__(self):
+		ConnectionVcard.__init__(self)
+		ConnectionBytestream.__init__(self)
+		# List of IDs we are waiting answers for {id: (type_of_request, data), }
+		self.awaiting_answers = {}
+		# List of IDs that will produce a timeout is answer doesn't arrive
+		# {time_of_the_timeout: (id, message to send to gui), }
+		self.awaiting_timeouts = {}
+		# keep the jids we auto added (transports contacts) to not send the
+		# SUBSCRIBED event to gui
+		self.automatically_added = []
+		try:
+			idle.init()
+		except:
+			HAS_IDLE = False
+			
+	def _messageCB(self, ip, con, msg):
+		'''Called when we receive a message'''
+		msgtxt = msg.getBody()
+		msghtml = msg.getXHTML()
+		mtype = msg.getType()
+		subject = msg.getSubject() # if not there, it's None
+		tim = msg.getTimestamp()
+		tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
+		tim = time.localtime(timegm(tim))
+		frm = msg.getFrom()
+		if frm == None:
+			for key in self.connection.zeroconf.contacts:
+				if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
+					frm = key
+		frm = unicode(frm)
+		jid  = frm
+		no_log_for = gajim.config.get_per('accounts', self.name,
+			'no_log_for').split()
+		encrypted = False
+		chatstate = None
+		encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
+		decmsg = ''
+		# invitations
+		invite = None
+		if not encTag:
+			invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
+			if invite and not invite.getTag('invite'):
+				invite = None
+		delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None
+		msg_id = None
+		composing_jep = None
+		xtags = msg.getTags('x')
+		# chatstates - look for chatstate tags in a message if not delayed
+		if not delayed:
+			composing_jep = False
+			children = msg.getChildren()
+			for child in children:
+				if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
+					chatstate = child.getName()
+					composing_jep = 'JEP-0085'
+					break
+			# No JEP-0085 support, fallback to JEP-0022
+			if not chatstate:
+				chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT)
+				if chatstate_child:
+					chatstate = 'active'
+					composing_jep = 'JEP-0022'
+					if not msgtxt and chatstate_child.getTag('composing'):
+						chatstate = 'composing'
+		# JEP-0172 User Nickname
+		user_nick = msg.getTagData('nick')
+		if not user_nick:
+			user_nick = ''
+
+		if encTag and GnuPG.USE_GPG:
+			#decrypt
+			encmsg = encTag.getData()
+			
+			keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+			if keyID:
+				decmsg = self.gpg.decrypt(encmsg, keyID)
+		if decmsg:
+			msgtxt = decmsg
+			encrypted = True
+		if mtype == 'error':
+			error_msg = msg.getError()
+			if not error_msg:
+				error_msg = msgtxt
+				msgtxt = None
+			if self.name not in no_log_for:
+				gajim.logger.write('error', frm, error_msg, tim = tim,
+					subject = subject)
+			self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
+				tim))
+		elif mtype == 'chat': # it's type 'chat'
+			if not msg.getTag('body') and chatstate is None: #no <body>
+				return
+			if msg.getTag('body') and self.name not in no_log_for and jid not in\
+				no_log_for and msgtxt:
+				msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
+					subject = subject)
+			self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
+				chatstate, msg_id, composing_jep, user_nick, msghtml))
+		elif mtype == 'normal': # it's single message
+			if self.name not in no_log_for and jid not in no_log_for and msgtxt:
+				gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
+					subject = subject)
+			if invite:
+				self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
+					subject, chatstate, msg_id, composing_jep, user_nick))
+	# END messageCB
+	'''
+	def build_http_auth_answer(self, iq_obj, answer):
+		if answer == 'yes':
+			iq = iq_obj.buildReply('result')
+		elif answer == 'no':
+			iq = iq_obj.buildReply('error')
+			iq.setError('not-authorized', 401)
+		self.connection.send(iq)
+	'''
+	
+	def parse_data_form(self, node):
+		dic = {}
+		tag = node.getTag('title')
+		if tag:
+			dic['title'] = tag.getData()
+		tag = node.getTag('instructions')
+		if tag:
+			dic['instructions'] = tag.getData()
+		i = 0
+		for child in node.getChildren():
+			if child.getName() != 'field':
+				continue
+			var = child.getAttr('var')
+			ctype = child.getAttr('type')
+			label = child.getAttr('label')
+			if not var and ctype != 'fixed': # We must have var if type != fixed
+				continue
+			dic[i] = {}
+			if var:
+				dic[i]['var'] = var
+			if ctype:
+				dic[i]['type'] = ctype
+			if label:
+				dic[i]['label'] = label
+			tags = child.getTags('value')
+			if len(tags):
+				dic[i]['values'] = []
+				for tag in tags:
+					data = tag.getData()
+					if ctype == 'boolean':
+						if data in ('yes', 'true', 'assent', '1'):
+							data = True
+						else:
+							data = False
+					dic[i]['values'].append(data)
+			tag = child.getTag('desc')
+			if tag:
+				dic[i]['desc'] = tag.getData()
+			option_tags = child.getTags('option')
+			if len(option_tags):
+				dic[i]['options'] = {}
+				j = 0
+				for option_tag in option_tags:
+					dic[i]['options'][j] = {}
+					label = option_tag.getAttr('label')
+					tags = option_tag.getTags('value')
+					dic[i]['options'][j]['values'] = []
+					for tag in tags:
+						dic[i]['options'][j]['values'].append(tag.getData())
+					if not label:
+						label = dic[i]['options'][j]['values'][0]
+					dic[i]['options'][j]['label'] = label
+					j += 1
+				if not dic[i].has_key('values'):
+					dic[i]['values'] = [dic[i]['options'][0]['values'][0]]
+			i += 1
+		return dic
+	
+	def store_metacontacts(self, tags):
+		''' fake empty method '''
+		# serverside metacontacts  are not supported with zeroconf 
+		# (there is no server)
+		pass
+	def remove_transfers_for_contact(self, contact):
+		''' stop all active transfer for contact '''
+		'''for file_props in self.files_props.values():
+			if self.is_transfer_stoped(file_props):
+				continue
+			receiver_jid = unicode(file_props['receiver']).split('/')[0]
+			if contact.jid == receiver_jid:
+				file_props['error'] = -5
+				self.remove_transfer(file_props)
+				self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
+			sender_jid = unicode(file_props['sender']).split('/')[0]
+			if contact.jid == sender_jid:
+				file_props['error'] = -3
+				self.remove_transfer(file_props)
+		'''
+		pass
+	
+	def remove_all_transfers(self):
+		''' stops and removes all active connections from the socks5 pool '''
+		'''
+		for file_props in self.files_props.values():
+			self.remove_transfer(file_props, remove_from_list = False)
+		del(self.files_props)
+		self.files_props = {}
+		'''
+		pass
+	
+	def remove_transfer(self, file_props, remove_from_list = True):
+		'''
+		if file_props is None:
+			return
+		self.disconnect_transfer(file_props)
+		sid = file_props['sid']
+		gajim.socks5queue.remove_file_props(self.name, sid)
+
+		if remove_from_list:
+			if self.files_props.has_key('sid'):
+				del(self.files_props['sid'])
+		'''
+		pass
+			
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5dfd5090e71d68a96195522465a8bbe1d50e167
--- /dev/null
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -0,0 +1,500 @@
+##	common/zeroconf/connection_zeroconf.py
+##
+## Contributors for this file:
+##	- Yann Le Boulanger <asterix@lagaule.org>
+##	- Nikos Kouremenos <nkour@jabber.org>
+##	- Dimitur Kirov <dkirov@gmail.com>
+##	- Travis Shirk <travis@pobox.com>
+##  - Stefan Bethge <stefan@lanpartei.de>
+##
+## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
+##                         Vincent Hanquez <tab@snarc.org>
+## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
+##                    Vincent Hanquez <tab@snarc.org>
+##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Dimitur Kirov <dkirov@gmail.com>
+##                    Travis Shirk <travis@pobox.com>
+##                    Norman Rasmussen <norman@rasmussen.co.za>
+##                    Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+
+import os
+import random
+random.seed()
+
+import signal
+if os.name != 'nt':
+	signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+import getpass
+import gobject
+import notify
+
+from common import helpers
+from common import gajim
+from common import GnuPG
+from common.zeroconf import connection_handlers_zeroconf
+from common.zeroconf import client_zeroconf
+from connection_handlers_zeroconf import *
+
+USE_GPG = GnuPG.USE_GPG
+
+class ConnectionZeroconf(ConnectionHandlersZeroconf):
+	'''Connection class'''
+	def __init__(self, name):
+		ConnectionHandlersZeroconf.__init__(self)
+		# system username
+		self.username = None
+		self.name = name
+		self.connected = 0 # offline
+		self.connection = None
+		self.gpg = None
+		self.is_zeroconf = True
+		self.privacy_rules_supported = False
+		self.status = ''
+		self.old_show = ''
+		self.priority = 0
+	
+		self.call_resolve_timeout = False
+		
+		#self.time_to_reconnect = None
+		#self.new_account_info = None
+		self.bookmarks = []
+
+		#we don't need a password, but must be non-empty
+		self.password = 'zeroconf'
+
+		self.autoconnect = False
+		self.sync_with_global_status = True
+		self.no_log_for = False
+
+		# Do we continue connection when we get roster (send presence,get vcard...)
+		self.continue_connect_info = None
+		if USE_GPG:
+			self.gpg = GnuPG.GnuPG()
+			gajim.config.set('usegpg', True)
+		else:
+			gajim.config.set('usegpg', False)
+		
+		self.get_config_values_or_default()
+		
+		self.muc_jid = {} # jid of muc server for each transport type
+		self.vcard_supported = False
+
+	def get_config_values_or_default(self):
+		''' get name, host, port from config, or 
+		create zeroconf account with default values'''
+		
+		if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
+			gajim.log.debug('Creating zeroconf account')
+			gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect', True)
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', '')
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', 'zeroconf')
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status', True)
+
+			#XXX make sure host is US-ASCII
+			self.host = unicode(socket.gethostname())
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', self.host)
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', 5298)
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'is_zeroconf', True)
+		self.host = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname')
+		self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
+		self.autoconnect = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect')
+		self.sync_with_global_status = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
+		self.no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for')
+		self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
+		self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
+		self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
+		self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
+
+		if not self.username:
+			self.username = unicode(getpass.getuser())
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', self.username)
+		else:
+			self.username = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name')
+	# END __init__
+
+	def dispatch(self, event, data):
+		if gajim.handlers.has_key(event):
+			gajim.handlers[event](self.name, data)
+
+	def _reconnect(self):
+		gajim.log.debug('reconnect')
+
+		signed = self.get_signed_msg(self.status)
+		self.reconnect()
+
+	def quit(self, kill_core):
+		if kill_core and self.connected > 1:
+			self.disconnect()
+	
+	def disable_account(self):
+		self.disconnect()
+
+	def test_gpg_passphrase(self, password):
+		self.gpg.passphrase = password
+		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+		signed = self.gpg.sign('test', keyID)
+		self.gpg.password = None
+		return signed != 'BAD_PASSPHRASE'
+
+	def get_signed_msg(self, msg):
+		signed = ''
+		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+		if keyID and USE_GPG:
+			use_gpg_agent = gajim.config.get('use_gpg_agent')
+			if self.connected < 2 and self.gpg.passphrase is None and \
+				not use_gpg_agent:
+				# We didn't set a passphrase
+				self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
+					#%s is the account name here
+					_('You will be connected to %s without OpenPGP.') % self.name))
+			elif self.gpg.passphrase is not None or use_gpg_agent:
+				signed = self.gpg.sign(msg, keyID)
+				if signed == 'BAD_PASSPHRASE':
+					signed = ''
+					if self.connected < 2:
+						self.dispatch('BAD_PASSPHRASE', ())
+		return signed
+
+	def _on_resolve_timeout(self):
+		if self.connected:
+			self.connection.resolve_all()
+			diffs = self.roster.getDiffs()
+			for key in diffs:
+				self.roster.setItem(key)
+				self.dispatch('ROSTER_INFO', (key, self.roster.getName(key),
+							'both', 'no', self.roster.getGroups(key)))
+				self.dispatch('NOTIFY', (key, self.roster.getStatus(key),
+							self.roster.getMessage(key), 'local', 0, None, 0))
+				#XXX open chat windows don't get refreshed (full name), add that
+		return self.call_resolve_timeout
+
+	# callbacks called from zeroconf	
+	def _on_new_service(self,jid):
+		self.roster.setItem(jid)
+		self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
+		self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
+	
+	def _on_remove_service(self, jid):
+		self.roster.delItem(jid)
+		# 'NOTIFY' (account, (jid, status, status message, resource, priority,
+		# keyID, timestamp))
+		self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0))
+
+	def _on_disconnected(self):
+		self.disconnect()
+		self.dispatch('STATUS', 'offline')
+		self.dispatch('CONNECTION_LOST',
+			(_('Connection with account "%s" has been lost') % self.name,
+			_('To continue sending and receiving messages, you will need to reconnect.')))
+		self.status = 'offline'
+		self.disconnect()
+
+	def _on_name_conflictCB(self, alt_name):
+		self.disconnect()
+		self.dispatch('STATUS', 'offline')
+		self.dispatch('ZC_NAME_CONFLICT', alt_name)
+
+	def _on_error(self, message):
+		self.dispatch('ERROR', (_('Avahi error'), _("%s\nLink-local messaging might not work properly.") % message))
+
+	def connect(self, show = 'online', msg = ''):
+		self.get_config_values_or_default()
+		if not self.connection:
+			self.connection = client_zeroconf.ClientZeroconf(self)
+			if not self.connection.test_avahi():
+				self.dispatch('STATUS', 'offline')
+				self.status = 'offline'
+				self.dispatch('CONNECTION_LOST',
+					(_('Could not connect to "%s"') % self.name,
+					_('Please check if Avahi is installed.')))
+				self.disconnect()
+				return
+			result = self.connection.connect(show, msg)
+			if not result:
+				self.dispatch('STATUS', 'offline')
+				self.status = 'offline'
+				if result is False:
+					self.dispatch('CONNECTION_LOST',
+						(_('Could not start local service'),
+						_('Unable to bind to port %d.' % self.port)))
+				else: # result is None
+					self.dispatch('CONNECTION_LOST',
+					(_('Could not start local service'),
+					_('Please check if avahi-daemon is running.')))
+				self.disconnect()
+				return
+		else:
+			self.connection.announce()
+		self.roster = self.connection.getRoster()
+		self.dispatch('ROSTER', self.roster)
+
+		#display contacts already detected and resolved
+		for jid in self.roster.keys():
+			self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
+			self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
+
+		self.connected = STATUS_LIST.index(show)
+
+		# refresh all contacts data every five seconds
+		self.call_resolve_timeout = True
+		gobject.timeout_add(5000, self._on_resolve_timeout)
+		return True
+
+	def disconnect(self, on_purpose = False):
+		self.connected = 0
+		self.time_to_reconnect = None
+		if self.connection:
+			self.connection.disconnect()
+			self.connection = None
+			# stop calling the timeout
+			self.call_resolve_timeout = False
+
+	def reannounce(self):
+		if self.connected:
+			txt = {}
+			txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
+			txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
+			txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
+			txt['email'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
+			self.connection.reannounce(txt)
+
+	def update_details(self):
+		if self.connection:
+			port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
+			if port != self.port:
+				self.port = port
+				last_msg = self.connection.last_msg
+				self.disconnect()
+				if not self.connect(self.status, last_msg):
+					return
+				if self.status != 'invisible':
+					self.connection.announce()
+			else:
+				self.reannounce()
+
+	def change_status(self, show, msg, sync = False, auto = False):
+		if not show in STATUS_LIST:
+			return -1
+		self.status = show
+
+		check = True		#to check for errors from zeroconf
+		# 'connect'
+		if show != 'offline' and not self.connected:
+			if not self.connect(show, msg):
+				return
+			if show != 'invisible':
+				check = self.connection.announce()
+			else:
+				self.connected = STATUS_LIST.index(show)
+
+		# 'disconnect'
+		elif show == 'offline' and self.connected:
+			self.disconnect()
+			
+		# update status
+		elif show != 'offline' and self.connected:
+			was_invisible = self.connected == STATUS_LIST.index('invisible')
+			self.connected = STATUS_LIST.index(show)
+			if show == 'invisible':
+				check = check and self.connection.remove_announce()
+			elif was_invisible:
+				if not self.connected:
+					check = check and self.connect(show, msg)
+				check = check and self.connection.announce()
+			if self.connection and not show == 'invisible':
+				check = check and self.connection.set_show_msg(show, msg)
+
+		#stay offline when zeroconf does something wrong
+		if check:
+			self.dispatch('STATUS', show)
+		else:
+			# show notification that avahi or system bus is down	
+			self.dispatch('STATUS', 'offline')
+			self.status = 'offline'
+			self.dispatch('CONNECTION_LOST',
+				(_('Could not change status of account "%s"') % self.name,
+				_('Please check if avahi-daemon is running.')))
+			
+	def get_status(self):
+		return STATUS_LIST[self.connected]
+
+	def send_message(self, jid, msg, keyID, type = 'chat', subject='',
+	chatstate = None, msg_id = None, composing_jep = None, resource = None, 
+	user_nick = None):
+		fjid = jid
+		
+		if not self.connection:
+			return
+		if not msg and chatstate is None:
+			return
+		
+		if self.status in ('invisible', 'offline'):
+			self.dispatch('MSGERROR', [unicode(jid), '-1', _('You are not connected or not visible to others. Your message could not be sent.'), None, None])
+			return
+
+		msgtxt = msg
+		msgenc = ''
+		if keyID and USE_GPG:
+			#encrypt
+			msgenc = self.gpg.encrypt(msg, [keyID])
+			if msgenc:
+				msgtxt = '[This message is encrypted]'
+				lang = os.getenv('LANG')
+				if lang is not None or lang != 'en': # we're not english
+					msgtxt = _('[This message is encrypted]') +\
+						' ([This message is encrypted])' # one  in locale and one en
+
+		
+		if type == 'chat':
+			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
+			
+		else:
+			if subject:
+				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
+					typ = 'normal', subject = subject)
+			else:
+				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
+					typ = 'normal')
+
+		if msgenc:
+			msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
+		
+		# chatstates - if peer supports jep85 or jep22, send chatstates
+		# please note that the only valid tag inside a message containing a <body>
+		# tag is the active event
+		if chatstate is not None:
+			if composing_jep == 'JEP-0085' or not composing_jep:
+				# JEP-0085
+				msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
+			if composing_jep == 'JEP-0022' or not composing_jep:
+				# JEP-0022
+				chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
+				if not msgtxt: # when no <body>, add <id>
+					if not msg_id: # avoid putting 'None' in <id> tag
+						msg_id = ''
+					chatstate_node.setTagData('id', msg_id)
+				# when msgtxt, requests JEP-0022 composing notification
+				if chatstate is 'composing' or msgtxt: 
+					chatstate_node.addChild(name = 'composing') 
+		
+		if not self.connection.send(msg_iq, msg != None):
+			return
+
+		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
+		ji = gajim.get_jid_without_resource(jid)
+		if self.name not in no_log_for and ji not in no_log_for:
+			log_msg = msg
+			if subject:
+				log_msg = _('Subject: %s\n%s') % (subject, msg)
+			if log_msg:
+				if type == 'chat':
+					kind = 'chat_msg_sent'
+				else:
+					kind = 'single_msg_sent'
+				gajim.logger.write(kind, jid, log_msg)
+		
+		self.dispatch('MSGSENT', (jid, msg, keyID))
+		
+	def send_stanza(self, stanza):
+		# send a stanza untouched
+		if not self.connection:
+			return
+		self.connection.send(stanza)
+	
+	def ack_subscribed(self, jid):
+		gajim.log.debug('This should not happen (ack_subscribed)')
+
+	def ack_unsubscribed(self, jid):
+		gajim.log.debug('This should not happen (ack_unsubscribed)')
+
+	def request_subscription(self, jid, msg = '', name = '', groups = [],
+	auto_auth = False):
+		gajim.log.debug('This should not happen (request_subscription)')
+
+	def send_authorization(self, jid):
+		gajim.log.debug('This should not happen (send_authorization)')
+
+	def refuse_authorization(self, jid):
+		gajim.log.debug('This should not happen (refuse_authorization)')
+
+	def unsubscribe(self, jid, remove_auth = True):
+		gajim.log.debug('This should not happen (unsubscribe)')
+
+	def unsubscribe_agent(self, agent):
+		gajim.log.debug('This should not happen (unsubscribe_agent)')
+
+	def update_contact(self, jid, name, groups):	
+		if self.connection:
+			self.connection.getRoster().setItem(jid = jid, name = name,
+				groups = groups)
+	
+	def new_account(self, name, config, sync = False):
+		gajim.log.debug('This should not happen (new_account)')
+
+	def _on_new_account(self, con = None, con_type = None):
+		gajim.log.debug('This should not happen (_on_new_account)')
+
+	def account_changed(self, new_name):
+		self.name = new_name
+
+	def request_last_status_time(self, jid, resource):
+		gajim.log.debug('This should not happen (request_last_status_time)')
+
+	def request_os_info(self, jid, resource):
+		gajim.log.debug('This should not happen (request_os_info)')
+
+	def get_settings(self):
+		gajim.log.debug('This should not happen (get_settings)')
+
+	def get_bookmarks(self):
+		gajim.log.debug('This should not happen (get_bookmarks)')
+		
+	def store_bookmarks(self):
+		gajim.log.debug('This should not happen (store_bookmarks)')
+		
+	def get_metacontacts(self):
+		gajim.log.debug('This should not happen (get_metacontacts)')
+		
+	def send_agent_status(self, agent, ptype):
+		gajim.log.debug('This should not happen (send_agent_status)')
+
+	def gpg_passphrase(self, passphrase):
+		if USE_GPG:
+			use_gpg_agent = gajim.config.get('use_gpg_agent')
+			if use_gpg_agent:
+				self.gpg.passphrase = None
+			else:
+				self.gpg.passphrase = passphrase
+
+	def ask_gpg_keys(self):
+		if USE_GPG:
+			keys = self.gpg.get_keys()
+			return keys
+		return None
+
+	def ask_gpg_secrete_keys(self):
+		if USE_GPG:
+			keys = self.gpg.get_secret_keys()
+			return keys
+		return None
+
+	def _event_dispatcher(self, realm, event, data):
+		if realm == '':
+			if event == common.xmpp.transports.DATA_RECEIVED:
+				self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
+			elif event == common.xmpp.transports.DATA_SENT:
+				self.dispatch('STANZA_SENT', unicode(data))
+
+# END ConnectionZeroconf
diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..269d3dbb2355e156b525143a68da0126c5142de4
--- /dev/null
+++ b/src/common/zeroconf/roster_zeroconf.py
@@ -0,0 +1,156 @@
+##      common/zeroconf/roster_zeroconf.py
+##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+
+from common.zeroconf import zeroconf
+
+class Roster:
+	def __init__(self, zeroconf):
+		self._data = None
+		self.zeroconf = zeroconf 	  	 # our zeroconf instance
+
+	def update_roster(self):
+		for val in self.zeroconf.contacts.values():
+			self.setItem(val[zeroconf.C_NAME])
+
+	def getRoster(self):
+		#print 'roster_zeroconf.py: getRoster'
+		if self._data is None:
+			self._data = {}
+			self.update_roster()
+		return self
+
+	def getDiffs(self):
+		'''	update the roster with new data and return dict with
+		jid -> new status pairs to do notifications and stuff '''
+
+		diffs = {}
+		old_data = self._data.copy()
+		self.update_roster()
+		for key in old_data.keys():
+			if self._data.has_key(key):
+				if old_data[key] != self._data[key]:
+					diffs[key] = self._data[key]['status']
+		#print 'roster_zeroconf.py: diffs:' + str(diffs)
+		return diffs
+		
+	def setItem(self, jid, name = '', groups = ''):
+		#print 'roster_zeroconf.py: setItem %s' % jid
+		contact = self.zeroconf.get_contact(jid)
+		if not contact:
+			return
+
+		(service_jid, domain, interface, protocol, host, address, port, bare_jid, txt)  \
+			= contact
+
+		self._data[jid]={}
+		self._data[jid]['ask'] = 'no'  #?
+		self._data[jid]['subscription'] = 'both'
+		self._data[jid]['groups'] = []
+		self._data[jid]['resources'] = {}
+		self._data[jid]['address'] = address
+		self._data[jid]['host'] = host
+		self._data[jid]['port'] = port
+		txt_dict = self.zeroconf.txt_array_to_dict(txt)
+		if txt_dict.has_key('status'):
+			status = txt_dict['status']
+		else:
+			status = ''
+		nm = ''
+		if txt_dict.has_key('1st'):
+			nm = txt_dict['1st']
+		if txt_dict.has_key('last'):
+			if nm != '':
+				nm += ' '
+			nm += txt_dict['last']
+		if nm:
+			self._data[jid]['name'] = nm
+		else:
+			self._data[jid]['name'] = jid
+		if status == 'avail': 
+			status = 'online'
+		self._data[jid]['txt_dict'] = txt_dict
+		if not self._data[jid]['txt_dict'].has_key('msg'):
+			self._data[jid]['txt_dict']['msg'] = ''
+		self._data[jid]['status'] = status
+		self._data[jid]['show'] = status
+
+	def delItem(self, jid):
+		#print 'roster_zeroconf.py: delItem %s' % jid
+		if self._data.has_key(jid):
+			del self._data[jid]
+		
+	def getItem(self, jid):
+		#print 'roster_zeroconf.py: getItem: %s' % jid
+		if self._data.has_key(jid):
+			return self._data[jid]
+
+	def __getitem__(self,jid):
+		#print 'roster_zeroconf.py: __getitem__'
+		return self._data[jid]
+	
+	def getItems(self):
+		#print 'roster_zeroconf.py: getItems'
+		# Return list of all [bare] JIDs that the roster currently tracks.
+		return self._data.keys()
+	
+	def keys(self):
+		#print 'roster_zeroconf.py: keys'
+		return self._data.keys()
+	
+	def getRaw(self):
+		#print 'roster_zeroconf.py: getRaw'
+		return self._data
+
+	def getResources(self, jid):
+		#print 'roster_zeroconf.py: getResources(%s)' % jid
+		return {}
+		
+	def getGroups(self, jid):
+		return self._data[jid]['groups']
+
+	def getName(self, jid):
+		if self._data.has_key(jid):
+			return self._data[jid]['name']
+
+	def getStatus(self, jid):
+		if self._data.has_key(jid):
+			return self._data[jid]['status']
+
+	def getMessage(self, jid):
+		if self._data.has_key(jid):
+			return self._data[jid]['txt_dict']['msg']
+
+	def getShow(self, jid):
+		#print 'roster_zeroconf.py: getShow'
+		return getStatus(jid)
+
+	def getPriority(jid):
+		return 5
+
+	def getSubscription(self,jid):
+		#print 'roster_zeroconf.py: getSubscription'
+		return 'both'
+
+	def Subscribe(self,jid):
+		pass
+		
+	def Unsubscribe(self,jid):
+		pass
+	
+	def Authorize(self,jid):
+		pass
+
+	def Unauthorize(self,jid):
+		pass
diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py
new file mode 100755
index 0000000000000000000000000000000000000000..fbaadfd1d83fc4487454f9708a625444a46f1eca
--- /dev/null
+++ b/src/common/zeroconf/zeroconf.py
@@ -0,0 +1,421 @@
+##      common/zeroconf/zeroconf.py
+##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+import os
+import sys
+import socket
+from common import gajim
+from common import xmpp
+
+try:
+	import dbus.glib
+except ImportError, e:
+	pass
+
+
+C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \
+C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9)
+
+class Zeroconf:
+	def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, 
+		disconnected_CB, error_CB, name, host, port):
+		self.avahi = None
+		self.domain = None   # specific domain to browse
+		self.stype = '_presence._tcp'	
+		self.port = port  # listening port that gets announced	
+		self.username = name
+		self.host = host
+		self.txt = {}		# service data
+		
+		#XXX these CBs should be set to None when we destroy the object 
+		# (go offline), because they create a circular reference 
+		self.new_serviceCB = new_serviceCB
+		self.remove_serviceCB = remove_serviceCB
+		self.name_conflictCB = name_conflictCB
+		self.disconnected_CB = disconnected_CB
+		self.error_CB = error_CB
+		
+		self.service_browser = None
+		self.domain_browser = None
+		self.bus = None
+		self.server = None
+		self.contacts = {}    # all current local contacts with data
+		self.entrygroup = None
+		self.connected = False
+		self.announced = False
+		self.invalid_self_contact = {}
+
+
+	## handlers for dbus callbacks
+	def entrygroup_commit_error_CB(self, err):
+		# left blank for possible later usage
+		pass
+	
+	def error_callback1(self, err):
+		gajim.log.debug('Error while resolving: ' + str(err))
+	
+	def error_callback(self, err):
+		gajim.log.debug(str(err))
+		# timeouts are non-critical
+		if str(err) != 'Timeout reached':
+			self.disconnect()
+			self.disconnected_CB()
+
+	def new_service_callback(self, interface, protocol, name, stype, domain, flags):
+		gajim.log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, interface, protocol))
+		if not self.connected:
+		 	return
+		
+		# synchronous resolving
+		self.server.ResolveService( int(interface), int(protocol), name, stype, \
+					domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), \
+					reply_handler=self.service_resolved_callback, error_handler=self.error_callback1)
+
+	def remove_service_callback(self, interface, protocol, name, stype, domain, flags):
+		gajim.log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, domain, interface, protocol))
+		if not self.connected:
+		 	return
+		if name != self.name:
+			for key in self.contacts.keys():
+				if self.contacts[key][C_BARE_NAME] == name:
+					del self.contacts[key]
+					self.remove_serviceCB(key)
+					return
+
+	def new_service_type(self, interface, protocol, stype, domain, flags):
+		# Are we already browsing this domain for this type? 
+		if self.service_browser:
+			return
+
+		object_path = self.server.ServiceBrowserNew(interface, protocol, \
+				stype, domain, dbus.UInt32(0))
+		
+		self.service_browser = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \
+			object_path) , self.avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+		self.service_browser.connect_to_signal('ItemNew', self.new_service_callback)
+		self.service_browser.connect_to_signal('ItemRemove', self.remove_service_callback)
+		self.service_browser.connect_to_signal('Failure', self.error_callback)
+
+	def new_domain_callback(self,interface, protocol, domain, flags):
+		if domain != "local":
+			self.browse_domain(interface, protocol, domain)
+	
+	def txt_array_to_dict(self, txt_array):
+		txt_dict = {}
+		for els in txt_array:
+			key, val = '', None
+			for c in els:
+					#FIXME: remove when outdated, this is for avahi < 0.6.14
+					if c < 0 or c > 255:
+						c = '.'
+					else:
+						c = chr(c)
+					if val is None:
+						if c == '=':
+							val = ''
+						else:
+							key += c
+					else:
+						val += c
+			if val is None: # missing '='
+				val = ''
+			txt_dict[key] = val.decode('utf-8')
+		return txt_dict
+	
+	def service_resolved_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):	
+		gajim.log.debug('Service data for service %s in domain %s on %i.%i:'
+				% (name, domain, interface, protocol))
+		gajim.log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, port,
+			self.txt_array_to_dict(txt)))
+		if not self.connected:
+			return
+		bare_name = name
+		if name.find('@') == -1:
+			name = name + '@' + name
+		
+		# we don't want to see ourselves in the list
+		if name != self.name:
+			self.contacts[name] = (name, domain, interface, protocol, host, address, port, 
+					bare_name, txt)
+			self.new_serviceCB(name)
+		else:
+			# remember data
+			# In case this is not our own record but of another
+			# gajim instance on the same machine,
+			# it will be used when we get a new name.
+			self.invalid_self_contact[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt)
+
+
+	# different handler when resolving all contacts
+	def service_resolved_all_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+		if not self.connected:
+			return
+		bare_name = name
+		if name.find('@') == -1:
+			name = name + '@' + name
+		self.contacts[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt)
+
+	def service_added_callback(self):
+		gajim.log.debug('Service successfully added')
+
+	def service_committed_callback(self):
+		gajim.log.debug('Service successfully committed')
+
+	def service_updated_callback(self):
+		gajim.log.debug('Service successfully updated')
+
+	def service_add_fail_callback(self, err):
+		gajim.log.debug('Error while adding service. %s' % str(err))
+		if str(err) == 'Local name collision':
+			alternative_name = self.server.GetAlternativeServiceName(self.username)
+			self.name_conflictCB(alternative_name)
+			return
+		self.error_CB(_('Error while adding service. %s') % str(err))
+		self.disconnect()
+
+	def server_state_changed_callback(self, state, error):
+		if state == self.avahi.SERVER_RUNNING:
+			self.create_service()
+		elif state == self.avahi.SERVER_COLLISION:
+			self.entrygroup.Reset()
+		elif state == self.avahi.CLIENT_FAILURE:
+			# does it ever go here?
+			gajim.log.debug('CLIENT FAILURE')
+
+	def entrygroup_state_changed_callback(self, state, error):
+		# the name is already present, so recreate
+		if state == self.avahi.ENTRY_GROUP_COLLISION:
+			self.service_add_fail_callback('Local name collision')
+		elif state == self.avahi.ENTRY_GROUP_FAILURE:
+			gajim.log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that'
+				' should not happen)')
+
+	# make zeroconf-valid names
+	def replace_show(self, show):
+		if show in ['chat', 'online', '']:
+			return 'avail'
+		elif show == 'xa':
+			return 'away'
+		return show
+
+	def avahi_txt(self):
+		utf8_dict = {}
+		for key in self.txt:
+			val = self.txt[key]
+			if isinstance(val, unicode):
+				utf8_dict[key] = val.encode('utf-8')
+			else:
+				utf8_dict[key] = val
+		return self.avahi.dict_to_txt_array(utf8_dict)
+
+	def create_service(self):
+		try:
+			if not self.entrygroup:
+				# create an EntryGroup for publishing
+				self.entrygroup = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, self.server.EntryGroupNew()), self.avahi.DBUS_INTERFACE_ENTRY_GROUP)
+				self.entrygroup.connect_to_signal('StateChanged', self.entrygroup_state_changed_callback)
+			
+			txt = {}
+			
+			#remove empty keys
+			for key,val in self.txt.iteritems():
+				if val:
+					txt[key] = val
+			
+			txt['port.p2pj'] = self.port
+			txt['version'] = 1
+			txt['txtvers'] = 1
+
+			# replace gajim's show messages with compatible ones
+			if self.txt.has_key('status'):
+				txt['status'] = self.replace_show(self.txt['status'])
+			else:
+				txt['status'] = 'avail'
+
+			self.txt = txt
+			gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype))
+			self.entrygroup.AddService(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', '', self.port, self.avahi_txt(), reply_handler=self.service_added_callback, error_handler=self.service_add_fail_callback)
+			
+			self.entrygroup.Commit(reply_handler=self.service_committed_callback, 
+				error_handler=self.entrygroup_commit_error_CB)
+
+			return True
+		
+		except dbus.dbus_bindings.DBusException, e:
+			gajim.log.debug(str(e))
+			return False
+			
+	def announce(self):
+		if not self.connected:
+			return False
+
+		state = self.server.GetState()
+		if state == self.avahi.SERVER_RUNNING:
+			self.create_service()
+			self.announced = True
+			return True
+
+	def remove_announce(self):
+		if self.announced == False:
+			return False
+		try:
+			if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE:
+				self.entrygroup.Reset()
+				self.entrygroup.Free()
+				# .Free() has mem leaks
+				obj = self.entrygroup._obj
+				obj._bus = None
+				self.entrygroup._obj = None
+				self.entrygroup = None
+				self.announced = False
+				
+				return True
+			else:
+				return False
+		except dbus.dbus_bindings.DBusException, e:
+			gajim.log.debug("Can't remove service. That should not happen")
+
+	def browse_domain(self, interface, protocol, domain):
+		self.new_service_type(interface, protocol, self.stype, domain, '')
+
+	def avahi_dbus_connect_cb(self, a, connect, disconnect):
+		if connect != "":
+			gajim.log.debug('Lost connection to avahi-daemon')
+			self.disconnect()
+			if self.disconnected_CB:
+				self.disconnected_CB()
+		else:
+			gajim.log.debug('We are connected to avahi-daemon')
+
+	# connect to dbus
+	def connect_dbus(self):
+		try:
+			import dbus
+		except ImportError:
+			gajim.log.debug('Error: python-dbus needs to be installed. No zeroconf support.')
+			return False
+		if self.bus:
+			return True
+		try:
+			self.bus = dbus.SystemBus()
+			self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, 
+				"NameOwnerChanged", "org.freedesktop.DBus", 
+				arg0="org.freedesktop.Avahi")
+		except Exception, e:
+			# System bus is not present
+			self.bus = None
+			gajim.log.debug(str(e))
+			return False
+		else:
+			return True
+
+	# connect to avahi
+	def connect_avahi(self):
+		if not self.connect_dbus():
+			return False
+		try:
+			import avahi
+			self.avahi = avahi
+		except ImportError:
+			gajim.log.debug('Error: python-avahi needs to be installed. No zeroconf support.')
+			return False
+
+		if self.server:
+			return True
+		try:
+			self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \
+			self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER)
+			self.server.connect_to_signal('StateChanged', 
+				self.server_state_changed_callback)
+		except Exception, e:
+			# Avahi service is not present
+			self.server = None
+			gajim.log.debug(str(e))
+			return False
+		else:
+			return True
+
+	def connect(self):
+		self.name = self.username + '@' + self.host # service name
+		if not self.connect_avahi():
+			return False
+		
+		self.connected = True
+		# start browsing
+		if self.domain is None:
+			# Explicitly browse .local
+			self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, "local")
+
+			# Browse for other browsable domains
+			self.domain_browser = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \
+					self.server.DomainBrowserNew(self.avahi.IF_UNSPEC, \
+					self.avahi.PROTO_UNSPEC, '', self.avahi.DOMAIN_BROWSER_BROWSE,\
+					dbus.UInt32(0))), self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
+			self.domain_browser.connect_to_signal('ItemNew', self.new_domain_callback)
+			self.domain_browser.connect_to_signal('Failure', self.error_callback)
+		else:
+			self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, domain)
+		
+		return True
+
+	def disconnect(self):
+		if self.connected:
+			self.connected = False
+			if self.service_browser:
+				self.service_browser.Free()
+				self.service_browser._obj._bus = None
+				self.service_browser._obj = None
+			if self.domain_browser:
+				self.domain_browser.Free()
+				self.domain_browser._obj._bus = None
+				self.domain_browser._obj = None
+			self.remove_announce()
+			self.server._obj._bus = None
+			self.server._obj = None
+		self.server = None
+		self.service_browser = None
+		self.domain_browser = None
+
+	# refresh txt data of all contacts manually (no callback available)
+	def resolve_all(self):
+		if not self.connected:
+			return
+		for val in self.contacts.values():
+			self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), val[C_BARE_NAME], \
+				self.stype, val[C_DOMAIN], self.avahi.PROTO_UNSPEC, dbus.UInt32(0),\
+				reply_handler=self.service_resolved_all_callback, error_handler=self.error_callback)
+
+	def get_contacts(self):
+		if not jid in self.contacts:
+			return None
+		return self.contacts
+
+	def get_contact(self, jid):
+		if not jid in self.contacts:
+			return None
+		return self.contacts[jid]
+		
+	def update_txt(self, show = None):
+		if show:
+			self.txt['status'] = self.replace_show(show)
+
+		txt = self.avahi_txt()
+		if self.connected and self.entrygroup:
+			self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype,'', txt, reply_handler=self.service_updated_callback, error_handler=self.error_callback)
+			return True
+		else:
+			return False
+
+
+# END Zeroconf
diff --git a/src/config.py b/src/config.py
index dd8e7bce3871c2be7f419c244c73d9c84433eb5d..080858aa3f1babf1386cee4c56c0f8dd95a0fdbf 100644
--- a/src/config.py
+++ b/src/config.py
@@ -1,9 +1,10 @@
 ##	config.py
 ##
 ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
 ## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org>
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -23,7 +24,6 @@ import common.sleepy
 
 import gtkgui_helpers
 import dialogs
-import vcard
 import cell_renderer_image
 import message_control
 import chat_control
@@ -37,6 +37,11 @@ except:
 from common import helpers
 from common import gajim
 from common import connection
+from common import passwords
+from common import zeroconf
+from common import dbus_support
+
+from common.exceptions import GajimGeneralException
 
 #---------- PreferencesWindow class -------------#
 class PreferencesWindow:
@@ -116,6 +121,9 @@ class PreferencesWindow:
 		emoticons_combobox.set_model(model)
 		l = []
 		for dir in emoticons_list:
+			if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'emoticons', dir)) \
+			and not os.path.isdir(os.path.join(gajim.MY_EMOTS_PATH, dir)) :
+				continue
 			if dir != '.svn':
 				l.append(dir)
 		l.append(_('Disabled'))
@@ -140,6 +148,8 @@ class PreferencesWindow:
 		self.iconset_combobox.set_model(model)
 		l = []
 		for dir in iconsets_list:
+			if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', dir)):
+				continue
 			if dir != '.svn' and dir != 'transports':
 				l.append(dir)
 		if l.count == 0:
@@ -147,8 +157,10 @@ class PreferencesWindow:
 		for i in xrange(len(l)):
 			preview = gtk.Image()
 			files = []
-			files.append(os.path.join(gajim.DATA_DIR, 'iconsets', l[i], '16x16', 'online.png'))
-			files.append(os.path.join(gajim.DATA_DIR, 'iconsets', l[i], '16x16', 'online.gif'))
+			files.append(os.path.join(gajim.DATA_DIR, 'iconsets', l[i], '16x16',
+				'online.png'))
+			files.append(os.path.join(gajim.DATA_DIR, 'iconsets', l[i], '16x16',
+				'online.gif'))
 			for file in files:
 				if os.path.exists(file):
 					preview.set_from_file(file)
@@ -181,11 +193,10 @@ class PreferencesWindow:
 			theme = config_theme.replace('_', ' ')
 			model.append([theme])
 			if gajim.config.get('roster_theme') == config_theme:
-			         theme_combobox.set_active(i)
+				theme_combobox.set_active(i)
 			i += 1
-		self.on_theme_combobox_changed(theme_combobox)
 
-		#use speller
+		# use speller
 		if os.name == 'nt':
 			self.xml.get_widget('speller_checkbutton').set_no_show_all(True)
 		else:
@@ -195,7 +206,11 @@ class PreferencesWindow:
 			else:
 				self.xml.get_widget('speller_checkbutton').set_sensitive(False)
 
-		#Print time
+		# Ignore XHTML
+		st = gajim.config.get('ignore_incoming_xhtml')
+		self.xml.get_widget('xhtml_checkbutton').set_active(st)
+
+		# Print time
 		st = gajim.config.get('print_ichat_every_foo_minutes')
 		text = _('Every %s _minutes') % st
 		self.xml.get_widget('time_sometimes_radiobutton').set_label(text)
@@ -211,19 +226,23 @@ class PreferencesWindow:
 
 		#before time
 		st = gajim.config.get('before_time')
-		self.xml.get_widget('before_time_entry').set_text(st)
+		st = helpers.from_one_line(st)
+		self.xml.get_widget('before_time_textview').get_buffer().set_text(st)
 
 		#after time
 		st = gajim.config.get('after_time')
-		self.xml.get_widget('after_time_entry').set_text(st)
+		st = helpers.from_one_line(st)
+		self.xml.get_widget('after_time_textview').get_buffer().set_text(st)
 
 		#before nickname
 		st = gajim.config.get('before_nickname')
-		self.xml.get_widget('before_nickname_entry').set_text(st)
+		st = helpers.from_one_line(st)
+		self.xml.get_widget('before_nickname_textview').get_buffer().set_text(st)
 
 		#after nickanme
 		st = gajim.config.get('after_nickname')
-		self.xml.get_widget('after_nickname_entry').set_text(st)
+		st = helpers.from_one_line(st)
+		self.xml.get_widget('after_nickname_textview').get_buffer().set_text(st)
 
 		#Color for incomming messages
 		colSt = gajim.config.get('inmsgcolor')
@@ -294,7 +313,8 @@ class PreferencesWindow:
 			combo.set_active(2)
 
 		#sounds
-		if os.name == 'nt': # if windows, player must not become visible on show_all
+		if os.name == 'nt':
+			# if windows, player must not become visible on show_all
 			soundplayer_hbox = self.xml.get_widget('soundplayer_hbox')
 			soundplayer_hbox.set_no_show_all(True)
 		if gajim.config.get('sounds_on'):
@@ -429,15 +449,16 @@ class PreferencesWindow:
 			self.xml.get_widget('custom_apps_frame').set_no_show_all(True)
 			if gajim.config.get('autodetect_browser_mailer'):
 				self.applications_combobox.set_active(0)
-				gtkgui_helpers.autodetect_browser_mailer()
-			# autodetect_browser_mailer is now False.
-			# so user has 'Always Use GNOME/KDE' or Custom
+			# else autodetect_browser_mailer is False.
+			# so user has 'Always Use GNOME/KDE/XFCE4' or Custom
 			elif gajim.config.get('openwith') == 'gnome-open':
 				self.applications_combobox.set_active(1)
 			elif gajim.config.get('openwith') == 'kfmclient exec':
 				self.applications_combobox.set_active(2)
+			elif gajim.config.get('openwith') == 'exo-open':
+				self.applications_combobox.set_active(3)				
 			elif gajim.config.get('openwith') == 'custom':
-				self.applications_combobox.set_active(3)
+				self.applications_combobox.set_active(4)
 				self.xml.get_widget('custom_apps_frame').show()
 				
 			self.xml.get_widget('custom_browser_entry').set_text(
@@ -454,12 +475,25 @@ class PreferencesWindow:
 		# send os info
 		st = gajim.config.get('send_os_info')
 		self.xml.get_widget('send_os_info_checkbutton').set_active(st)
+
+		# set status msg from currently playing music track
+		widget = self.xml.get_widget(
+			'set_status_msg_from_current_music_track_checkbutton')
+		if os.name == 'nt':
+			widget.set_no_show_all(True)
+			widget.hide()
+		elif dbus_support.supported:
+			st = gajim.config.get('set_status_msg_from_current_music_track')
+			widget.set_active(st)
+		else:
+			widget.set_sensitive(False)
 		
 		# Notify user of new gmail e-mail messages,
 		# only show checkbox if user has a gtalk account
 		frame_gmail = self.xml.get_widget('frame_gmail')
 		notify_gmail_checkbutton = self.xml.get_widget('notify_gmail_checkbutton')
-		notify_gmail_extra_checkbutton = self.xml.get_widget('notify_gmail_extra_checkbutton')
+		notify_gmail_extra_checkbutton = self.xml.get_widget(
+			'notify_gmail_extra_checkbutton')
 		frame_gmail.set_no_show_all(True)
 		
 		for account in gajim.config.get_per('accounts'):
@@ -511,8 +545,11 @@ class PreferencesWindow:
 			gajim.interface.systray.change_status(show)
 		else:
 			gajim.config.set('trayicon', False)
+			if not gajim.interface.roster.window.get_property('visible'):
+				gajim.interface.roster.window.present()
 			gajim.interface.hide_systray()
-			gajim.config.set('show_roster_on_startup', True) # no tray, show roster!
+			# no tray, show roster!
+			gajim.config.set('show_roster_on_startup', True)
 		gajim.interface.roster.draw_roster()
 		gajim.interface.save_config()
 
@@ -543,7 +580,7 @@ class PreferencesWindow:
 		else:
 			gajim.config.set('emoticons_theme', emot_theme)
 
-		gajim.interface.init_emoticons()
+		gajim.interface.init_emoticons(need_reload = True)
 		gajim.interface.make_regexps()
 		self.toggle_emoticons()
 
@@ -640,11 +677,14 @@ class PreferencesWindow:
 		else:
 			self.remove_speller()
 
+	def on_xhtml_checkbutton_toggled(self, widget):
+		self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
+		
 	def _set_sensitivity_for_before_after_time_widgets(self, sensitive):
 		self.xml.get_widget('before_time_label').set_sensitive(sensitive)
-		self.xml.get_widget('before_time_entry').set_sensitive(sensitive)
+		self.xml.get_widget('before_time_textview').set_sensitive(sensitive)
 		self.xml.get_widget('after_time_label').set_sensitive(sensitive)
-		self.xml.get_widget('after_time_entry').set_sensitive(sensitive)
+		self.xml.get_widget('after_time_textview').set_sensitive(sensitive)
 	
 	def on_time_never_radiobutton_toggled(self, widget):
 		if widget.get_active():
@@ -664,20 +704,33 @@ class PreferencesWindow:
 		self._set_sensitivity_for_before_after_time_widgets(True)
 		gajim.interface.save_config()
 
-	def on_before_time_entry_focus_out_event(self, widget, event):
-		gajim.config.set('before_time', widget.get_text().decode('utf-8'))
+	def _get_textview_text(self, tv):
+		buffer = tv.get_buffer()
+		begin, end = buffer.get_bounds()
+		return buffer.get_text(begin, end).decode('utf-8')
+
+	def on_before_time_textview_focus_out_event(self, widget, event):
+		text = self._get_textview_text(widget)
+		text = helpers.to_one_line(text)
+		gajim.config.set('before_time', text)
 		gajim.interface.save_config()
 
-	def on_after_time_entry_focus_out_event(self, widget, event):
-		gajim.config.set('after_time', widget.get_text().decode('utf-8'))
+	def on_after_time_textview_focus_out_event(self, widget, event):
+		text = self._get_textview_text(widget)
+		text = helpers.to_one_line(text)
+		gajim.config.set('after_time', text)
 		gajim.interface.save_config()
 
-	def on_before_nickname_entry_focus_out_event(self, widget, event):
-		gajim.config.set('before_nickname', widget.get_text().decode('utf-8'))
+	def on_before_nickname_textview_focus_out_event(self, widget, event):
+		text = self._get_textview_text(widget)
+		text = helpers.to_one_line(text)
+		gajim.config.set('before_nickname', text)
 		gajim.interface.save_config()
 
-	def on_after_nickname_entry_focus_out_event(self, widget, event):
-		gajim.config.set('after_nickname', widget.get_text().decode('utf-8'))
+	def on_after_nickname_textview_focus_out_event(self, widget, event):
+		text = self._get_textview_text(widget)
+		text = helpers.to_one_line(text)
+		gajim.config.set('after_nickname', text)
 		gajim.interface.save_config()
 
 	def update_text_tags(self):
@@ -886,7 +939,7 @@ class PreferencesWindow:
 
 	def on_applications_combobox_changed(self, widget):
 		gajim.config.set('autodetect_browser_mailer', False)
-		if widget.get_active() == 3:
+		if widget.get_active() == 4:
 			self.xml.get_widget('custom_apps_frame').show()
 			gajim.config.set('openwith', 'custom')
 		else:
@@ -896,6 +949,8 @@ class PreferencesWindow:
 				gajim.config.set('openwith', 'gnome-open')
 			elif widget.get_active() == 2:
 				gajim.config.set('openwith', 'kfmclient exec')
+			elif widget.get_active() == 3:
+				gajim.config.set('openwith', 'exo-open')
 			self.xml.get_widget('custom_apps_frame').hide()
 		gajim.interface.save_config()
 
@@ -1063,13 +1118,21 @@ class PreferencesWindow:
 			gajim.interface.instances['advanced_config'] = \
 				dialogs.AdvancedConfigurationWindow()
 
+	def set_status_msg_from_current_music_track_checkbutton_toggled(self,
+		widget):
+		self.on_checkbutton_toggled(widget,
+			'set_status_msg_from_current_music_track')
+		gajim.interface.roster.enable_syncing_status_msg_from_current_music_track(
+			widget.get_active())
+
 #---------- AccountModificationWindow class -------------#
 class AccountModificationWindow:
 	'''Class for account informations'''
 	def on_account_modification_window_destroy(self, widget):
 		'''close window'''
 		if gajim.interface.instances.has_key(self.account):
-			if gajim.interface.instances[self.account].has_key('account_modification'):
+			if gajim.interface.instances[self.account].has_key(
+			'account_modification'):
 				del gajim.interface.instances[self.account]['account_modification']
 				return
 		if gajim.interface.instances.has_key('account_modification'):
@@ -1147,14 +1210,16 @@ class AccountModificationWindow:
 		self.xml.get_widget('save_password_checkbutton').set_active(
 			gajim.config.get_per('accounts', self.account, 'savepass'))
 		if gajim.config.get_per('accounts', self.account, 'savepass'):
-			passstr = gajim.config.get_per('accounts',
-						self.account, 'password')
+			passstr = passwords.get_password(self.account)
 			password_entry = self.xml.get_widget('password_entry')
 			password_entry.set_sensitive(True)
 			password_entry.set_text(passstr)
 
 		self.xml.get_widget('resource_entry').set_text(gajim.config.get_per(
 			'accounts', self.account, 'resource'))
+		self.xml.get_widget('adjust_priority_with_status_checkbutton').set_active(
+			gajim.config.get_per('accounts', self.account,
+			'adjust_priority_with_status'))
 		self.xml.get_widget('priority_spinbutton').set_value(gajim.config.\
 			get_per('accounts', self.account, 'priority'))
 
@@ -1219,6 +1284,10 @@ class AccountModificationWindow:
 				return True
 		return False
 
+	def on_adjust_priority_with_status_checkbutton_toggled(self, widget):
+		self.xml.get_widget('priority_spinbutton').set_sensitive(
+			not widget.get_active())
+
 	def on_save_button_clicked(self, widget):
 		'''When save button is clicked: Save information in config file'''
 		config = {}
@@ -1237,8 +1306,8 @@ class AccountModificationWindow:
 					return
 				if name in gajim.connections:
 					dialogs.ErrorDialog(_('Account Name Already Used'),
-						_('This name is already used by another of your accounts. Please choose '
-						'another name.'))
+						_('This name is already used by another of your accounts. '
+						'Please choose another name.'))
 					return
 		if (name == ''):
 			dialogs.ErrorDialog(_('Invalid account name'),
@@ -1265,7 +1334,8 @@ class AccountModificationWindow:
 			dialogs.ErrorDialog(pritext, sectext)
 			return
 
-		resource = self.xml.get_widget('resource_entry').get_text().decode('utf-8')
+		resource = self.xml.get_widget('resource_entry').get_text().decode(
+			'utf-8')
 		try:
 			resource = helpers.parse_resource(resource)
 		except helpers.InvalidFormat, s:
@@ -1278,16 +1348,18 @@ class AccountModificationWindow:
 		config['password'] = self.xml.get_widget('password_entry').get_text().\
 			decode('utf-8')
 		config['resource'] = resource
+		config['adjust_priority_with_status'] = self.xml.get_widget(
+			'adjust_priority_with_status_checkbutton').get_active()
 		config['priority'] = self.xml.get_widget('priority_spinbutton').\
-																			get_value_as_int()
+			get_value_as_int()
 		config['autoconnect'] = self.xml.get_widget('autoconnect_checkbutton').\
-																					get_active()
-		config['autoreconnect'] = self.xml.get_widget('autoreconnect_checkbutton').\
-																					get_active()
+			get_active()
+		config['autoreconnect'] = self.xml.get_widget(
+			'autoreconnect_checkbutton').get_active()
 
 		if self.account:
-			list_no_log_for = gajim.config.get_per('accounts',
-					self.account, 'no_log_for').split()
+			list_no_log_for = gajim.config.get_per('accounts', self.account,
+				'no_log_for').split()
 		else:
 			list_no_log_for = []
 		if self.account in list_no_log_for:
@@ -1297,7 +1369,7 @@ class AccountModificationWindow:
 		config['no_log_for'] = ' '.join(list_no_log_for)
 
 		config['sync_with_global_status'] = self.xml.get_widget(
-				'sync_with_global_status_checkbutton').get_active()
+			'sync_with_global_status_checkbutton').get_active()
 		config['use_ft_proxies'] = self.xml.get_widget(
 			'use_ft_proxies_checkbutton').get_active()
 
@@ -1324,21 +1396,27 @@ class AccountModificationWindow:
 		config['custom_host'] = self.xml.get_widget(
 			'custom_host_entry').get_text().decode('utf-8')
 
-		config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().decode('utf-8')
+		# update in case the name was changed to local account's name
+		config['is_zeroconf'] = False
+
+		config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().\
+			decode('utf-8')
 		if config['keyname'] == '': #no key selected
 			config['keyid'] = ''
 			config['savegpgpass'] = False
 			config['gpgpassword'] = ''
 		else:
-			config['keyid'] = self.xml.get_widget('gpg_key_label').get_text().decode('utf-8')
+			config['keyid'] = self.xml.get_widget('gpg_key_label').get_text().\
+				decode('utf-8')
 			config['savegpgpass'] = self.xml.get_widget(
-					'gpg_save_password_checkbutton').get_active()
+				'gpg_save_password_checkbutton').get_active()
 			config['gpgpassword'] = self.xml.get_widget('gpg_password_entry'
 				).get_text().decode('utf-8')
 		#if we modify the name of the account
 		if name != self.account:
 			#update variables
-			gajim.interface.instances[name] = gajim.interface.instances[self.account]
+			gajim.interface.instances[name] = gajim.interface.instances[
+				self.account]
 			gajim.nicks[name] = gajim.nicks[self.account]
 			gajim.block_signed_in_notifications[name] = \
 				gajim.block_signed_in_notifications[self.account]
@@ -1358,8 +1436,7 @@ class AccountModificationWindow:
 			gajim.events.change_account_name(self.account, name)
 
 			# change account variable for chat / gc controls
-			for ctrl in gajim.interface.msg_win_mgr.get_controls():
-				ctrl.account = name
+			gajim.interface.msg_win_mgr.change_account_name(self.account, name)
 			# upgrade account variable in opened windows
 			for kind in ('infos', 'disco', 'gc_config'):
 				for j in gajim.interface.instances[name][kind]:
@@ -1394,27 +1471,28 @@ class AccountModificationWindow:
 			# check if relogin is needed
 			relogin_needed = False
 			if self.options_changed_need_relogin(config,
-				('resource', 'proxy', 'usessl', 'keyname',
-				'use_custom_host', 'custom_host')):
+			('resource', 'proxy', 'usessl', 'keyname',
+			'use_custom_host', 'custom_host')):
 				relogin_needed = True
 
 			elif config['use_custom_host'] and (self.option_changed(config,
-				'custom_host') or self.option_changed(config, 'custom_port')):
+			'custom_host') or self.option_changed(config, 'custom_port')):
 				relogin_needed = True
 
 			if self.option_changed(config, 'use_ft_proxies') and \
 			config['use_ft_proxies']:
 				gajim.connections[self.account].discover_ft_proxies()
 
-			if self.option_changed(config, 'priority'):
+			if self.option_changed(config, 'priority') or self.option_changed(
+			config, 'adjust_priority_with_status'):
 				resend_presence = True
 
 		for opt in config:
 			gajim.config.set_per('accounts', name, opt, config[opt])
 		if config['savepass']:
-			gajim.connections[name].password = config['password']
+			passwords.save_password(name, config['password'])
 		else:
-			gajim.connections[name].password = None
+			passwords.save_password(name, None)
 		# refresh accounts window
 		if gajim.interface.instances.has_key('accounts'):
 			gajim.interface.instances['accounts'].init_accounts()
@@ -1463,7 +1541,7 @@ class AccountModificationWindow:
 	def on_change_password_button_clicked(self, widget):
 		try:
 			dialog = dialogs.ChangePasswordDialog(self.account)
-		except RuntimeError:
+		except GajimGeneralException:
 			#if we showed ErrorDialog, there will not be dialog instance
 			return
 
@@ -1476,7 +1554,8 @@ class AccountModificationWindow:
 	def on_edit_details_button_clicked(self, widget):
 		if not gajim.interface.instances.has_key(self.account):
 			dialogs.ErrorDialog(_('No such account available'),
-				_('You must create your account before editing your personal information.'))
+				_('You must create your account before editing your personal '
+				'information.'))
 			return
 
 		# show error dialog if account is newly created (not in gajim.connections)
@@ -1639,7 +1718,8 @@ class ManageProxiesWindow:
 		self.xml.get_widget('proxypass_entry').set_sensitive(act)
 
 	def on_proxies_treeview_cursor_changed(self, widget):
-		#FIXME: check if off proxy settings are correct (see http://trac.gajim.org/changeset/1921#file2 line 1221
+		#FIXME: check if off proxy settings are correct (see
+		# http://trac.gajim.org/changeset/1921#file2 line 1221
 		(model, iter) = widget.get_selection().get_selected()
 		if not iter:
 			return
@@ -1758,6 +1838,23 @@ class AccountsWindow:
 		st = gajim.config.get('mergeaccounts')
 		self.xml.get_widget('merge_checkbutton').set_active(st)
 
+		import os
+
+		avahi_error = False
+		try:
+			import avahi
+		except ImportError:
+			avahi_error = True
+
+		# enable zeroconf
+		st = gajim.config.get('enable_zeroconf')
+		w = self.xml.get_widget('enable_zeroconf_checkbutton')
+		w.set_active(st)
+		if os.name == 'nt' or (avahi_error and not w.get_active()):
+			w.set_sensitive(False)
+		self.zeroconf_toggled_id = w.connect('toggled',
+			self.on_enable_zeroconf_checkbutton_toggled)
+
 	def on_accounts_window_key_press_event(self, widget, event):
 		if event.keyval == gtk.keysyms.Escape:
 			self.window.destroy()
@@ -1770,7 +1867,8 @@ class AccountsWindow:
 		model.clear()
 		for account in gajim.connections:
 			iter = model.append()
-			model.set(iter, 0, account, 1, gajim.get_hostname_from_account(account))
+			model.set(iter, 0, account, 1, gajim.get_hostname_from_account(
+				account))
 
 	def on_accounts_treeview_cursor_changed(self, widget):
 		'''Activate delete and modify buttons when a row is selected'''
@@ -1797,11 +1895,47 @@ class AccountsWindow:
 			dialogs.ErrorDialog(_('Unread events'),
 				_('Read all pending events before removing this account.'))
 			return
-		if gajim.interface.instances[account].has_key('remove_account'):
-			gajim.interface.instances[account]['remove_account'].window.present()
+
+		if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+			w = self.xml.get_widget('enable_zeroconf_checkbutton')
+			w.set_active(False)
+			return
 		else:
-			gajim.interface.instances[account]['remove_account'] = \
-				RemoveAccountWindow(account)
+			if gajim.interface.instances[account].has_key('remove_account'):
+				gajim.interface.instances[account]['remove_account'].window.present(
+					)
+			else:
+				gajim.interface.instances[account]['remove_account'] = \
+					RemoveAccountWindow(account)
+
+		win_opened = False
+		if gajim.interface.msg_win_mgr.get_controls(acct = account):
+			win_opened = True
+		else:
+			for key in gajim.interface.instances[account]:
+				if gajim.interface.instances[account][key] and key != \
+				'remove_account':
+					win_opened = True
+					break
+		# Detect if we have opened windows for this account
+		self.dialog = None
+		def remove(widget, account):
+			if self.dialog:
+				self.dialog.destroy()
+			if gajim.interface.instances[account].has_key('remove_account'):
+				gajim.interface.instances[account]['remove_account'].window.\
+					present()
+			else:
+				gajim.interface.instances[account]['remove_account'] = \
+					RemoveAccountWindow(account)
+		if win_opened:
+			self.dialog = dialogs.ConfirmationDialog(
+				_('You have opened chat in account %s') % account,
+				_('All chat and groupchat windows will be closed. Do you want to '
+				'continue?'),
+				on_response_ok = (remove, account))
+		else:
+			remove(widget, account)
 
 	def on_modify_button_clicked(self, widget):
 		'''When modify button is clicked:
@@ -1819,21 +1953,117 @@ class AccountsWindow:
 		self.show_modification_window(account)
 
 	def show_modification_window(self, account):
-		if gajim.interface.instances[account].has_key('account_modification'):
-			gajim.interface.instances[account]['account_modification'].window.present()
+		if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+			if gajim.interface.instances.has_key('zeroconf_properties'):
+				gajim.interface.instances['zeroconf_properties'].window.present()
+			else:
+				gajim.interface.instances['zeroconf_properties'] = \
+					ZeroconfPropertiesWindow()
 		else:
-			gajim.interface.instances[account]['account_modification'] = \
-				AccountModificationWindow(account)
+			if gajim.interface.instances[account].has_key('account_modification'):
+				gajim.interface.instances[account]['account_modification'].window.\
+					present()
+			else:
+				gajim.interface.instances[account]['account_modification'] = \
+					AccountModificationWindow(account)
 
-	def on_merge_checkbutton_toggled(self, widget):
-		gajim.config.set('mergeaccounts', widget.get_active())
+	def on_checkbutton_toggled(self, widget, config_name,
+		change_sensitivity_widgets = None):
+		gajim.config.set(config_name, widget.get_active())
+		if change_sensitivity_widgets:
+			for w in change_sensitivity_widgets:
+				w.set_sensitive(widget.get_active())
 		gajim.interface.save_config()
+
+	def on_merge_checkbutton_toggled(self, widget):
+		self.on_checkbutton_toggled(widget, 'mergeaccounts')
 		if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
 			gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
 		else:
 			gajim.interface.roster.regroup = False
 		gajim.interface.roster.draw_roster()
 
+	
+	def on_enable_zeroconf_checkbutton_toggled(self, widget):
+		# don't do anything if there is an account with the local name but is a 
+		# normal account
+		if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME) and not \
+		gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf:
+			gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR',
+				(_('Account Local already exists.'),
+				_('Please rename or remove it before enabling link-local messaging'
+				'.')))
+			widget.disconnect(self.zeroconf_toggled_id)
+			widget.set_active(False)
+			self.zeroconf_toggled_id = widget.connect('toggled',
+				self.on_enable_zeroconf_checkbutton_toggled)
+			return
+
+		if gajim.config.get('enable_zeroconf'):
+			#disable
+			gajim.interface.roster.close_all(gajim.ZEROCONF_ACC_NAME)
+			gajim.connections[gajim.ZEROCONF_ACC_NAME].disable_account()
+			del gajim.connections[gajim.ZEROCONF_ACC_NAME]
+			gajim.interface.save_config()
+			del gajim.interface.instances[gajim.ZEROCONF_ACC_NAME]
+			del gajim.nicks[gajim.ZEROCONF_ACC_NAME]
+			del gajim.block_signed_in_notifications[gajim.ZEROCONF_ACC_NAME]
+			del gajim.groups[gajim.ZEROCONF_ACC_NAME]
+			gajim.contacts.remove_account(gajim.ZEROCONF_ACC_NAME)
+			del gajim.gc_connected[gajim.ZEROCONF_ACC_NAME]
+			del gajim.automatic_rooms[gajim.ZEROCONF_ACC_NAME]
+			del gajim.to_be_removed[gajim.ZEROCONF_ACC_NAME]
+			del gajim.newly_added[gajim.ZEROCONF_ACC_NAME]
+			del gajim.sleeper_state[gajim.ZEROCONF_ACC_NAME]
+			del gajim.encrypted_chats[gajim.ZEROCONF_ACC_NAME]
+			del gajim.last_message_time[gajim.ZEROCONF_ACC_NAME]
+			del gajim.status_before_autoaway[gajim.ZEROCONF_ACC_NAME]
+			if len(gajim.connections) >= 2:
+				# Do not merge accounts if only one exists
+				gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') 
+			else: 
+				gajim.interface.roster.regroup = False
+			gajim.interface.roster.draw_roster()
+			gajim.interface.roster.actions_menu_needs_rebuild = True
+			if gajim.interface.instances.has_key('accounts'):
+				gajim.interface.instances['accounts'].init_accounts()
+			
+		else:
+			# enable (will create new account if not present)
+			gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.\
+				connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
+			# update variables
+			gajim.interface.instances[gajim.ZEROCONF_ACC_NAME] = {'infos': {},
+				'disco': {}, 'gc_config': {}}
+			gajim.connections[gajim.ZEROCONF_ACC_NAME].connected = 0
+			gajim.groups[gajim.ZEROCONF_ACC_NAME] = {}
+			gajim.contacts.add_account(gajim.ZEROCONF_ACC_NAME)
+			gajim.gc_connected[gajim.ZEROCONF_ACC_NAME] = {}
+			gajim.automatic_rooms[gajim.ZEROCONF_ACC_NAME] = {}
+			gajim.newly_added[gajim.ZEROCONF_ACC_NAME] = []
+			gajim.to_be_removed[gajim.ZEROCONF_ACC_NAME] = []
+			gajim.nicks[gajim.ZEROCONF_ACC_NAME] = gajim.ZEROCONF_ACC_NAME
+			gajim.block_signed_in_notifications[gajim.ZEROCONF_ACC_NAME] = True
+			gajim.sleeper_state[gajim.ZEROCONF_ACC_NAME] = 'off'
+			gajim.encrypted_chats[gajim.ZEROCONF_ACC_NAME] = []
+			gajim.last_message_time[gajim.ZEROCONF_ACC_NAME] = {}
+			gajim.status_before_autoaway[gajim.ZEROCONF_ACC_NAME] = ''
+			# refresh accounts window
+			if gajim.interface.instances.has_key('accounts'):
+				gajim.interface.instances['accounts'].init_accounts()
+			# refresh roster
+			if len(gajim.connections) >= 2:
+				# Do not merge accounts if only one exists
+				gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') 
+			else: 
+				gajim.interface.roster.regroup = False
+			gajim.interface.roster.draw_roster()
+			gajim.interface.roster.actions_menu_needs_rebuild = True
+			gajim.interface.save_config()
+			gajim.connections[gajim.ZEROCONF_ACC_NAME].change_status('online', '')
+
+		self.on_checkbutton_toggled(widget, 'enable_zeroconf')
+		
 class DataFormWindow:
 	def __init__(self, account, config):
 		self.account = account
@@ -1988,7 +2218,8 @@ class ServiceRegistrationWindow(DataFormWindow):
 		if self.is_form:
 			DataFormWindow.__init__(self, account, infos)
 		else:
-			self.xml = gtkgui_helpers.get_glade('service_registration_window.glade')
+			self.xml = gtkgui_helpers.get_glade(
+				'service_registration_window.glade')
 			self.window = self.xml.get_widget('service_registration_window')
 			self.window.set_transient_for(gajim.interface.roster.window)
 			if infos.has_key('registered'):
@@ -2038,8 +2269,8 @@ class ServiceRegistrationWindow(DataFormWindow):
 			else:
 				gajim.interface.roster.add_transport_to_roster(self.account,
 					self.service)
-			gajim.connections[self.account].register_agent(self.service, self.infos,
-				True) # True is for is_form
+			gajim.connections[self.account].register_agent(self.service,
+				self.infos, True) # True is for is_form
 		else:
 			# we pressed OK of service_registration_window
 			# send registration info to the core
@@ -2052,7 +2283,8 @@ class ServiceRegistrationWindow(DataFormWindow):
 			else:
 				gajim.interface.roster.add_transport_to_roster(self.account,
 					self.service)
-			gajim.connections[self.account].register_agent(self.service, self.infos)
+			gajim.connections[self.account].register_agent(self.service,
+				self.infos)
 		
 		self.window.destroy()
 
@@ -2281,7 +2513,8 @@ class RemoveAccountWindow:
 						# We don't remove account cause we canceled pw window
 						return
 					gajim.connections[self.account].password = passphrase
-				gajim.connections[self.account].unregister_account(self._on_remove_success)
+				gajim.connections[self.account].unregister_account(
+					self._on_remove_success)
 			else:
 				self._on_remove_success(True)
 
@@ -2300,7 +2533,7 @@ class RemoveAccountWindow:
 		if not res:
 			return
 		# Close all opened windows
-		gajim.interface.roster.close_all(self.account)
+		gajim.interface.roster.close_all(self.account, force = True)
 		gajim.connections[self.account].disconnect(on_purpose = True)
 		del gajim.connections[self.account]
 		gajim.config.del_per('accounts', self.account)
@@ -2335,15 +2568,17 @@ class ManageBookmarksWindow:
 		self.window = self.xml.get_widget('manage_bookmarks_window')
 		self.window.set_transient_for(gajim.interface.roster.window)
 
-		#Account-JID, RoomName, Room-JID, Autojoin, Passowrd, Nick, Show_Status
+		# Account-JID, RoomName, Room-JID, Autojoin, Passowrd, Nick, Show_Status
 		self.treestore = gtk.TreeStore(str, str, str, bool, str, str, str)
 
-		#Store bookmarks in treeview.
+		# Store bookmarks in treeview.
 		for account in gajim.connections:
 			if gajim.connections[account].connected <= 1:
 				continue
+			if gajim.connections[account].is_zeroconf:
+				continue
 			iter = self.treestore.append(None, [None, account,None,
-							    None, None, None, None])
+				None, None, None, None])
 
 			for bookmark in gajim.connections[account].bookmarks:
 				if bookmark['name'] == '':
@@ -2356,10 +2591,9 @@ class ManageBookmarksWindow:
 				autojoin = helpers.from_xs_boolean_to_python_boolean(
 					bookmark['autojoin'])
 
-				if bookmark.has_key('print_status'):
-					print_status = bookmark['print_status']
-				if not print_status:
-					print_status = gajim.config.get('print_status_in_muc')
+				print_status = bookmark.get('print_status', '')
+				if print_status not in ('', 'all', 'in_and_out', 'none'):
+					print_status = ''
 				self.treestore.append( iter, [
 						account,
 						bookmark['name'],
@@ -2372,7 +2606,7 @@ class ManageBookmarksWindow:
 		self.print_status_combobox = self.xml.get_widget('print_status_combobox')
 		model = gtk.ListStore(str, str)
 
-		self.option_list = {'all': _('All'),
+		self.option_list = {'': _('Default'), 'all': _('All'),
 			'in_and_out': _('Enter and leave only'), 'none': _('None')}
 		opts = self.option_list.keys()
 		opts.sort()
@@ -2443,8 +2677,8 @@ class ManageBookmarksWindow:
 
 		account = model[add_to][1].decode('utf-8')
 		nick = gajim.nicks[account]
-		self.treestore.append(add_to, [account, _('New Room'), '', False, '',
-			nick, 'in_and_out'])
+		self.treestore.append(add_to, [account, _('New Group Chat'), '', False,
+			'', nick, 'in_and_out'])
 
 		self.view.expand_row(model.get_path(add_to), True)
 
@@ -2473,9 +2707,11 @@ class ManageBookmarksWindow:
 			#Account data can't be changed
 			return
 
-		if self.server_entry.get_text().decode('utf-8') == '' or self.room_entry.get_text().decode('utf-8') == '':
+		if self.server_entry.get_text().decode('utf-8') == '' or \
+		self.room_entry.get_text().decode('utf-8') == '':
 			dialogs.ErrorDialog(_('This bookmark has invalid data'),
-_('Please be sure to fill out server and room fields or remove this bookmark.'))
+				_('Please be sure to fill out server and room fields or remove this'
+				' bookmark.'))
 			return False
 
 		return True
@@ -2627,7 +2863,8 @@ _('Please be sure to fill out server and room fields or remove this bookmark.'))
 
 class AccountCreationWizardWindow:
 	def __init__(self):
-		self.xml = gtkgui_helpers.get_glade('account_creation_wizard_window.glade')
+		self.xml = gtkgui_helpers.get_glade(
+			'account_creation_wizard_window.glade')
 		self.window = self.xml.get_widget('account_creation_wizard_window')
 
 		# Connect events from comboboxentry.child
@@ -2736,7 +2973,8 @@ class AccountCreationWizardWindow:
 			username = widgets['username_entry'].get_text().decode('utf-8')
 			if not username:
 				pritext = _('Invalid username')
-				sectext = _('You must provide a username to configure this account.')
+				sectext = _('You must provide a username to configure this account'
+				'.')
 				dialogs.ErrorDialog(pritext, sectext)
 				return
 			server = widgets['server_comboboxentry'].child.get_text()
@@ -2781,7 +3019,7 @@ class AccountCreationWizardWindow:
 				self.account = server + str(i)
 				i += 1
 
-			username, server = gajim.get_room_name_and_server_from_room_jid(jid)
+			username, server = gajim.get_name_and_server_from_jid(jid)
 			self.save_account(username, server, savepass, password)
 			self.cancel_button.hide()
 			self.back_button.hide()
@@ -2789,7 +3027,9 @@ class AccountCreationWizardWindow:
 			if self.modify:
 				finish_text = '<big><b>%s</b></big>\n\n%s' % (
 					_('Account has been added successfully'),
-_('You can set advanced account options by pressing Advanced button, or later by clicking in Accounts menuitem under Edit menu from the main window.'))
+					_('You can set advanced account options by pressing Advanced '
+					'button, or later by clicking in Accounts menuitem under Edit '
+					'menu from the main window.'))
 				self.finish_label.set_markup(finish_text)
 				self.finish_button.show()
 				self.finish_button.set_property('has-default', True)
@@ -2822,7 +3062,9 @@ _('You can set advanced account options by pressing Advanced button, or later by
 
 		finish_text = '<big><b>%s</b></big>\n\n%s' % (
 			_('Your new account has been created successfully'),
-_('You can set advanced account options by pressing Advanced button, or later by clicking in Accounts menuitem under Edit menu from the main window.'))
+			_('You can set advanced account options by pressing Advanced button, '
+			'or later by clicking in Accounts menuitem under Edit menu from the '
+			'main window.'))
 		self.finish_label.set_markup(finish_text)
 		self.notebook.set_current_page(3) # show finish page
 
@@ -2837,7 +3079,8 @@ _('You can set advanced account options by pressing Advanced button, or later by
 		self.show_vcard_checkbutton.hide()
 		img = self.xml.get_widget('finish_image')
 		img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
-		finish_text = '<big><b>%s</b></big>\n\n%s' % (_('An error occured during account creation') , reason)
+		finish_text = '<big><b>%s</b></big>\n\n%s' % (_('An error occured during '
+			'account creation') , reason)
 		self.finish_label.set_markup(finish_text)
 		self.notebook.set_current_page(3) # show finish page
 
@@ -2900,9 +3143,6 @@ _('You can set advanced account options by pressing Advanced button, or later by
 		con = connection.Connection(self.account)
 		con.password = password
 
-		if not savepass:
-			password = ""
-
 		config = {}
 		config['name'] = login
 		config['hostname'] = server
@@ -2931,6 +3171,10 @@ _('You can set advanced account options by pressing Advanced button, or later by
 
 	def create_vars(self, config):
 		gajim.config.add_per('accounts', self.account)
+
+		if not config['savepass']:
+			config['password'] = ''
+
 		for opt in config:
 			gajim.config.set_per('accounts', self.account, opt, config[opt])
 
@@ -2961,3 +3205,210 @@ _('You can set advanced account options by pressing Advanced button, or later by
 		gajim.interface.roster.draw_roster()
 		gajim.interface.roster.actions_menu_needs_rebuild = True
 		gajim.interface.save_config()
+
+#---------- ZeroconfPropertiesWindow class -------------#
+class ZeroconfPropertiesWindow:
+	def __init__(self):
+		self.xml = gtkgui_helpers.get_glade('zeroconf_properties_window.glade')
+		self.window = self.xml.get_widget('zeroconf_properties_window')
+		self.window.set_transient_for(gajim.interface.roster.window)
+		self.xml.signal_autoconnect(self)
+
+		self.init_account()
+		self.init_account_gpg()
+
+		self.xml.get_widget('save_button').grab_focus()
+		self.window.show_all()
+	
+	def init_account(self):
+		st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'autoconnect')
+		if st:
+			self.xml.get_widget('autoconnect_checkbutton').set_active(st)
+		
+		list_no_log_for = gajim.config.get_per('accounts',
+			gajim.ZEROCONF_ACC_NAME,'no_log_for').split()
+		if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
+			self.xml.get_widget('log_history_checkbutton').set_active(0)
+		else:
+			self.xml.get_widget('log_history_checkbutton').set_active(1)
+
+
+		st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'sync_with_global_status')
+		if st:
+			self.xml.get_widget('sync_with_global_status_checkbutton').set_active(
+				st)
+
+		for opt in ('first_name', 'last_name', 'jabber_id', 'email'):
+			st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+				'zeroconf_' + opt)
+			if st:
+				self.xml.get_widget(opt + '_entry').set_text(st)
+
+		st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'custom_port')
+		if st:
+			self.xml.get_widget('custom_port_entry').set_text(str(st))
+		
+		st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'use_custom_host')
+		if st:
+			self.xml.get_widget('custom_port_checkbutton').set_active(st)
+	
+		self.xml.get_widget('custom_port_entry').set_sensitive(bool(st))
+
+		if not st:
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+				'custom_port', '5298')
+
+	def init_account_gpg(self):
+		keyid = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'keyid')
+		keyname = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'keyname')
+		savegpgpass = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'savegpgpass')
+
+		if not keyid or not gajim.config.get('usegpg'):
+			return
+
+		self.xml.get_widget('gpg_key_label').set_text(keyid)
+		self.xml.get_widget('gpg_name_label').set_text(keyname)
+		gpg_save_password_checkbutton = \
+			self.xml.get_widget('gpg_save_password_checkbutton')
+		gpg_save_password_checkbutton.set_sensitive(True)
+		gpg_save_password_checkbutton.set_active(savegpgpass)
+
+		if savegpgpass:
+			entry = self.xml.get_widget('gpg_password_entry')
+			entry.set_sensitive(True)
+			gpgpassword = gajim.config.get_per('accounts',
+						gajim.ZEROCONF_ACC_NAME, 'gpgpassword')
+			entry.set_text(gpgpassword)
+
+	def on_zeroconf_properties_window_destroy(self, widget):
+		# close window
+		if gajim.interface.instances.has_key('zeroconf_properties'):
+			del gajim.interface.instances['zeroconf_properties']
+	
+	def on_custom_port_checkbutton_toggled(self, widget):
+		st = self.xml.get_widget('custom_port_checkbutton').get_active()
+		self.xml.get_widget('custom_port_entry').set_sensitive(bool(st))
+	
+	def on_cancel_button_clicked(self, widget):
+		self.window.destroy()
+	
+	def on_save_button_clicked(self, widget):
+		config = {}
+
+		st = self.xml.get_widget('autoconnect_checkbutton').get_active()
+		config['autoconnect'] = st
+		list_no_log_for = gajim.config.get_per('accounts',
+				gajim.ZEROCONF_ACC_NAME, 'no_log_for').split()
+		if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
+			list_no_log_for.remove(gajim.ZEROCONF_ACC_NAME)
+		if not self.xml.get_widget('log_history_checkbutton').get_active():
+			list_no_log_for.append(gajim.ZEROCONF_ACC_NAME)
+		config['no_log_for'] =  ' '.join(list_no_log_for)
+		
+		st = self.xml.get_widget('sync_with_global_status_checkbutton').\
+			get_active()
+		config['sync_with_global_status'] = st
+
+		st = self.xml.get_widget('first_name_entry').get_text()
+		config['zeroconf_first_name'] = st.decode('utf-8')
+		
+		st = self.xml.get_widget('last_name_entry').get_text()
+		config['zeroconf_last_name'] = st.decode('utf-8')
+
+		st = self.xml.get_widget('jabber_id_entry').get_text()
+		config['zeroconf_jabber_id'] = st.decode('utf-8')
+
+		st = self.xml.get_widget('email_entry').get_text()
+		config['zeroconf_email'] = st.decode('utf-8')
+
+		use_custom_port = self.xml.get_widget('custom_port_checkbutton').\
+			get_active()
+		config['use_custom_host'] = use_custom_port
+
+		old_port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+			'custom_port')
+		if use_custom_port:
+			port = self.xml.get_widget('custom_port_entry').get_text()
+		else:
+			port = 5298
+			
+		config['custom_port'] = port
+	
+		config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().\
+			decode('utf-8')
+		if config['keyname'] == '': #no key selected
+			config['keyid'] = ''
+			config['savegpgpass'] = False
+			config['gpgpassword'] = ''
+		else:
+			config['keyid'] = self.xml.get_widget('gpg_key_label').get_text().\
+				decode('utf-8')
+			config['savegpgpass'] = self.xml.get_widget(
+					'gpg_save_password_checkbutton').get_active()
+			config['gpgpassword'] = self.xml.get_widget('gpg_password_entry'
+				).get_text().decode('utf-8')
+
+		reconnect = False
+		for opt in ('zeroconf_first_name','zeroconf_last_name',
+			'zeroconf_jabber_id', 'zeroconf_email', 'custom_port'):
+			if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, opt) != \
+			config[opt]:
+				reconnect = True
+
+		for opt	in config:
+			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, opt,
+				config[opt])
+
+		if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME):
+			if port != old_port or reconnect:
+				gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details()
+
+		self.window.destroy()
+
+	def on_gpg_choose_button_clicked(self, widget, data = None):
+		if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME):
+			secret_keys = gajim.connections[gajim.ZEROCONF_ACC_NAME].\
+				ask_gpg_secrete_keys()
+
+		# self.account is None and/or gajim.connections is {}
+		else:
+			from common import GnuPG
+			if GnuPG.USE_GPG:
+				secret_keys = GnuPG.GnuPG().get_secret_keys()
+			else:
+				secret_keys = []
+		if not secret_keys:
+			dialogs.ErrorDialog(_('Failed to get secret keys'),
+				_('There was a problem retrieving your OpenPGP secret keys.'))
+			return
+		secret_keys[_('None')] = _('None')
+		instance = dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
+			_('Choose your OpenPGP key'), secret_keys)
+		keyID = instance.run()
+		if keyID is None:
+			return
+		checkbutton = self.xml.get_widget('gpg_save_password_checkbutton')
+		gpg_key_label = self.xml.get_widget('gpg_key_label')
+		gpg_name_label = self.xml.get_widget('gpg_name_label')
+		if keyID[0] == _('None'):
+			gpg_key_label.set_text(_('No key selected'))
+			gpg_name_label.set_text('')
+			checkbutton.set_sensitive(False)
+			self.xml.get_widget('gpg_password_entry').set_sensitive(False)
+		else:
+			gpg_key_label.set_text(keyID[0])
+			gpg_name_label.set_text(keyID[1])
+			checkbutton.set_sensitive(True)
+		checkbutton.set_active(False)
+		self.xml.get_widget('gpg_password_entry').set_text('')
+
+	def on_gpg_save_password_checkbutton_toggled(self, widget):
+		st = widget.get_active()
+		w = self.xml.get_widget('gpg_password_entry')
+		w.set_sensitive(bool(st))
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
index 888c9d2b367d72f7f37cc12181ac6e7fe3dc4880..834ec6acd62bb87e2b568720b635d37ef724cc73 100644
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -1,17 +1,8 @@
 ##	conversation_textview.py
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -27,7 +18,6 @@ import gtk
 import pango
 import gobject
 import time
-import sys
 import os
 import tooltips
 import dialogs
@@ -39,12 +29,20 @@ from common import helpers
 from calendar import timegm
 from common.fuzzyclock import FuzzyClock
 
+from htmltextview import HtmlTextView
+from common.exceptions import GajimGeneralException
+
 class ConversationTextview:
 	'''Class for the conversation textview (where user reads already said messages)
 	for chat/groupchat windows'''
-	def __init__(self, account):
-		# no need to inherit TextView, use it as property is safer
-		self.tv = gtk.TextView()
+	def __init__(self, account, used_in_history_window = False):
+		'''if used_in_history_window is True, then we do not show
+		Clear menuitem in context menu'''
+		self.used_in_history_window = used_in_history_window
+		
+		# no need to inherit TextView, use it as atrribute is safer
+		self.tv = HtmlTextView()
+		self.tv.html_hyperlink_handler = self.html_hyperlink_handler
 
 		# set properties
 		self.tv.set_border_width(1)
@@ -57,11 +55,13 @@ class ConversationTextview:
 		self.handlers = {}
 
 		# connect signals
-		id = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event)
+		id = self.tv.connect('motion_notify_event',
+			self.on_textview_motion_notify_event)
 		self.handlers[id] = self.tv
 		id = self.tv.connect('populate_popup', self.on_textview_populate_popup)
 		self.handlers[id] = self.tv
-		id = self.tv.connect('button_press_event', self.on_textview_button_press_event)
+		id = self.tv.connect('button_press_event',
+			self.on_textview_button_press_event)
 		self.handlers[id] = self.tv
 
 		self.account = account
@@ -98,7 +98,7 @@ class ConversationTextview:
 		tag.set_property('weight', pango.WEIGHT_BOLD)
 
 		tag = buffer.create_tag('time_sometimes')
-		tag.set_property('foreground', 'grey')
+		tag.set_property('foreground', 'darkgrey')
 		tag.set_property('scale', pango.SCALE_SMALL)
 		tag.set_property('justification', gtk.JUSTIFY_CENTER)
 
@@ -138,6 +138,11 @@ class ConversationTextview:
 		self.focus_out_end_iter_offset = None
 
 		self.line_tooltip = tooltips.BaseTooltip()
+		
+		path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
+		self.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
+		# use it for hr too
+		self.tv.focus_out_line_pixbuf = self.focus_out_line_pixbuf
 
 	def del_handlers(self):
 		for i in self.handlers.keys():
@@ -145,7 +150,7 @@ class ConversationTextview:
 				self.handlers[i].disconnect(i)
 		del self.handlers
 		self.tv.destroy()
-		#TODO
+		#FIXME:
 		# self.line_tooltip.destroy()
 	
 	def update_tags(self):
@@ -230,19 +235,14 @@ class ConversationTextview:
 					end_iter_for_previous_line)
 
 			# add the new focus out line
-			# FIXME: Why is this loaded from disk everytime
-			path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
-			focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
 			end_iter = buffer.get_end_iter()
 			buffer.insert(end_iter, '\n')
-			buffer.insert_pixbuf(end_iter, focus_out_line_pixbuf)
+			buffer.insert_pixbuf(end_iter, self.focus_out_line_pixbuf)
 
 			end_iter = buffer.get_end_iter()
 			before_img_iter = end_iter.copy()
 			before_img_iter.backward_char() # one char back (an image also takes one char)
 			buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
-			#FIXME: remove this workaround when bug is fixed
-			# c http://bugzilla.gnome.org/show_bug.cgi?id=318569
 
 			self.allow_focus_out_line = False
 
@@ -316,19 +316,29 @@ class ConversationTextview:
 
 	def on_textview_populate_popup(self, textview, menu):
 		'''we override the default context menu and we prepend Clear
+		(only if used_in_history_window is False)
 		and if we have sth selected we show a submenu with actions on the phrase
 		(see on_conversation_textview_button_press_event)'''
-		item = gtk.SeparatorMenuItem()
-		menu.prepend(item)
-		item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
-		menu.prepend(item)
-		id = item.connect('activate', self.clear)
-		self.handlers[id] = item
+
+		separator_menuitem_was_added = False
+		if not self.used_in_history_window:
+			item = gtk.SeparatorMenuItem()
+			menu.prepend(item)
+			separator_menuitem_was_added = True
+
+			item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+			menu.prepend(item)
+			id = item.connect('activate', self.clear)
+			self.handlers[id] = item
+
 		if self.selected_phrase:
-			s = self.selected_phrase
-			if len(s) > 25:
-				s = s[:21] + '...'
-			item = gtk.MenuItem(_('Actions for "%s"') % s)
+			if not separator_menuitem_was_added:
+				item = gtk.SeparatorMenuItem()
+				menu.prepend(item)
+
+			self.selected_phrase = helpers.reduce_chars_newlines(
+				self.selected_phrase, 25, 2)
+			item = gtk.MenuItem(_('_Actions for "%s"') % self.selected_phrase)
 			menu.prepend(item)
 			submenu = gtk.Menu()
 			item.set_submenu(submenu)
@@ -360,19 +370,20 @@ class ConversationTextview:
 				self.handlers[id] = item
 			else:
 				if dict_link.find('%s') == -1:
-					#we must have %s in the url if not WIKTIONARY
+					# we must have %s in the url if not WIKTIONARY
 					item = gtk.MenuItem(_('Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
 					item.set_property('sensitive', False)
 				else:
 					link = dict_link % self.selected_phrase
-					id = item.connect('activate', self.visit_url_from_menuitem, link)
+					id = item.connect('activate', self.visit_url_from_menuitem,
+						link)
 					self.handlers[id] = item
 			submenu.append(item)
 
 
 			search_link = gajim.config.get('search_engine')
 			if search_link.find('%s') == -1:
-				#we must have %s in the url
+				# we must have %s in the url
 				item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
 				item.set_property('sensitive', False)
 			else:
@@ -381,14 +392,18 @@ class ConversationTextview:
 				id = item.connect('activate', self.visit_url_from_menuitem, link)
 				self.handlers[id] = item
 			submenu.append(item)
+			
+			item = gtk.MenuItem(_('Open as _Link'))
+			id = item.connect('activate', self.visit_url_from_menuitem, link)
+			self.handlers[id] = item
+			submenu.append(item)
 
 		menu.show_all()
 
 	def on_textview_button_press_event(self, widget, event):
 		# If we clicked on a taged text do NOT open the standard popup menu
 		# if normal text check if we have sth selected
-
-		self.selected_phrase = ''
+		self.selected_phrase = '' # do not move belove event button check!
 
 		if event.button != 3: # if not right click
 			return False
@@ -426,18 +441,16 @@ class ConversationTextview:
 	def on_start_chat_activate(self, widget, jid):
 		gajim.interface.roster.new_chat_from_jid(self.account, jid)
 
-	def on_join_group_chat_menuitem_activate(self, widget, jid):
-		room, server = jid.split('@')
-		if gajim.interface.instances[self.account].has_key('join_gc'):
+	def on_join_group_chat_menuitem_activate(self, widget, room_jid):
+		if 'join_gc' in gajim.interface.instances[self.account]:
 			instance = gajim.interface.instances[self.account]['join_gc']
-			instance.xml.get_widget('server_entry').set_text(server)
-			instance.xml.get_widget('room_entry').set_text(room)
+			instance.xml.get_widget('room_jid_entry').set_text(room_jid)
 			gajim.interface.instances[self.account]['join_gc'].window.present()
 		else:
 			try:
 				gajim.interface.instances[self.account]['join_gc'] = \
-				dialogs.JoinGroupchatWindow(self.account, server, room)
-			except RuntimeError:
+				dialogs.JoinGroupchatWindow(self.account, room_jid)
+			except GajimGeneralException:
 				pass
 
 	def on_add_to_roster_activate(self, widget, jid):
@@ -459,6 +472,7 @@ class ConversationTextview:
 			childs[6].hide() # join group chat
 			childs[7].hide() # add to roster
 		else: # It's a mail or a JID
+			text = text.lower()
 			id = childs[2].connect('activate', self.on_copy_link_activate, text)
 			self.handlers[id] = childs[2]
 			id = childs[3].connect('activate', self.on_open_link_activate, kind, text)
@@ -506,6 +520,15 @@ class ConversationTextview:
 				# we launch the correct application
 				helpers.launch_browser_mailer(kind, word)
 
+	def html_hyperlink_handler(self, texttag, widget, event, iter, kind, href):
+		if event.type == gtk.gdk.BUTTON_PRESS:
+			if event.button == 3: # right click
+				self.make_link_menu(event, kind, href)
+			else:
+				# we launch the correct application
+				helpers.launch_browser_mailer(kind, href)
+
+
 	def detect_and_print_special_text(self, otext, other_tags):
 		'''detects special text (emots & links & formatting)
 		prints normal text before any special text it founts,
@@ -562,6 +585,7 @@ class ConversationTextview:
 			img.show()
 			#add with possible animation
 			self.tv.add_child_at_anchor(img, anchor)
+		#FIXME: one day, somehow sync with regexp in gajim.py
 		elif special_text.startswith('http://') or \
 			special_text.startswith('www.') or \
 			special_text.startswith('ftp://') or \
@@ -638,11 +662,11 @@ class ConversationTextview:
 	def print_empty_line(self):
 		buffer = self.tv.get_buffer()
 		end_iter = buffer.get_end_iter()
-		buffer.insert(end_iter, '\n')
+		buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
 
 	def print_conversation_line(self, text, jid, kind, name, tim,
-			other_tags_for_name = [], other_tags_for_time = [],
-			other_tags_for_text = [], subject = None, old_kind = None):
+	other_tags_for_name = [], other_tags_for_time = [], other_tags_for_text = [],
+	subject = None, old_kind = None, xhtml = None):
 		'''prints 'chat' type messages'''
 		buffer = self.tv.get_buffer()
 		buffer.begin_user_action()
@@ -652,7 +676,7 @@ class ConversationTextview:
 			at_the_end = True
 
 		if buffer.get_char_count() > 0:
-			buffer.insert(end_iter, '\n')
+			buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
 		if kind == 'incoming_queue':
 			kind = 'incoming'
 		if old_kind == 'incoming_queue':
@@ -664,7 +688,9 @@ class ConversationTextview:
 		current_print_time = gajim.config.get('print_time')
 		if current_print_time == 'always' and kind != 'info':
 			before_str = gajim.config.get('before_time')
+			before_str = helpers.from_one_line(before_str)
 			after_str = gajim.config.get('after_time')
+			after_str = helpers.from_one_line(after_str)
 			# get difference in days since epoch (86400 = 24*3600)
 			# number of days since epoch for current time (in GMT) -
 			# number of days since epoch for message (in GMT)
@@ -682,10 +708,10 @@ class ConversationTextview:
 				format += day_str + ' '
 			format += '%X' + after_str
 			tim_format = time.strftime(format, tim)
-			# if tim_format comes as unicode because of day_str.
-			# we convert it to the encoding that we want (and that is utf-8)
-			tim_format = helpers.ensure_utf8_string(tim_format)
-			tim_format = tim_format.encode('utf-8')
+			if locale.getpreferredencoding() == 'UTF-8':
+				# if tim_format comes as unicode because of day_str.
+				# we convert it to the encoding that we want (and that is utf-8)
+				tim_format = helpers.ensure_utf8_string(tim_format)
 			buffer.insert_with_tags_by_name(end_iter, tim_format + ' ',
 				*other_tags_for_time)
 		elif current_print_time == 'sometimes' and kind != 'info':
@@ -725,7 +751,7 @@ class ConversationTextview:
 			else:
 				self.print_name(name, kind, other_tags_for_name)
 		self.print_subject(subject)
-		self.print_real_text(text, text_tags, name)
+		self.print_real_text(text, text_tags, name, xhtml)
 
 		# scroll to the end of the textview
 		if at_the_end or kind == 'outgoing':
@@ -748,7 +774,9 @@ class ConversationTextview:
 			name_tags = other_tags_for_name[:] # create a new list
 			name_tags.append(kind)
 			before_str = gajim.config.get('before_nickname')
+			before_str = helpers.from_one_line(before_str)
 			after_str = gajim.config.get('after_nickname')
+			after_str = helpers.from_one_line(after_str)
 			format = before_str + name + after_str + ' '
 			buffer.insert_with_tags_by_name(end_iter, format, *name_tags)
 
@@ -760,8 +788,18 @@ class ConversationTextview:
 			buffer.insert(end_iter, subject)
 			self.print_empty_line()
 
-	def print_real_text(self, text, text_tags = [], name = None):
+	def print_real_text(self, text, text_tags = [], name = None, xhtml = None):
 		'''this adds normal and special text. call this to add text'''
+		if xhtml:
+			try:
+				if name and (text.startswith('/me ') or text.startswith('/me\n')):
+					xhtml = xhtml.replace('/me', '<dfn>%s</dfn>'% (name,), 1)
+				self.tv.display_html(xhtml.encode('utf-8'))
+				return
+			except Exception, e:
+				gajim.log.debug(str("Error processing xhtml")+str(e))
+				gajim.log.debug(str("with |"+xhtml+"|"))
+
 		buffer = self.tv.get_buffer()
 		# /me is replaced by name if name is given
 		if name and (text.startswith('/me ') or text.startswith('/me\n')):
diff --git a/src/dialogs.py b/src/dialogs.py
index 6a8ec5de3e8e71e9351486a72f43d9d4d559622d..afb53b83a7dc9e1e36f277e3dc47d24b081020a9 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -3,7 +3,7 @@
 ##
 ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
 ## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
 ## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>
 ## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
@@ -25,6 +25,7 @@ import os
 import gtkgui_helpers
 import vcard
 import conversation_textview
+import message_control
 
 try:
 	import gtkspell
@@ -40,6 +41,7 @@ from advanced import AdvancedConfigurationWindow
 
 from common import gajim
 from common import helpers
+from common.exceptions import GajimGeneralException
 
 class EditGroupsDialog:
 	'''Class for the edit group dialog window'''
@@ -139,6 +141,9 @@ class EditGroupsDialog:
 		group = self.xml.get_widget('group_entry').get_text().decode('utf-8')
 		if not group:
 			return
+		# Do not allow special groups
+		if group in helpers.special_groups:
+			return
 		# check if it already exists
 		model = self.list.get_model()
 		iter = model.get_iter_root()
@@ -180,14 +185,16 @@ class EditGroupsDialog:
 			if account not in accounts:
 				accounts.append(account)
 				for g in gajim.groups[account].keys():
-					if g in helpers.special_groups:
-						continue
 					if g in groups:
 						continue
 					groups[g] = 0
 			for g in contact.groups:
 				groups[g] += 1
-		group_list = groups.keys()
+		group_list = []
+		# Remove special groups if they are empty
+		for group in groups:
+			if group not in helpers.special_groups or groups[group] > 0:
+				group_list.append(group)
 		group_list.sort()			
 		for group in group_list:
 			iter = store.append()
@@ -264,6 +271,7 @@ class ChooseGPGKeyDialog:
 		renderer = gtk.CellRendererText()
 		self.keys_treeview.insert_column_with_attributes(-1, _('Contact name'),
 			renderer, text = 1)
+		self.keys_treeview.set_search_column(1)
 		self.fill_tree(secret_keys, selected)
 		self.window.show_all()
 
@@ -405,12 +413,12 @@ class ChangeStatusMessageDialog:
 
 class AddNewContactWindow:
 	'''Class for AddNewContactWindow'''
-	uid_labels = {'jabber': _('Jabber ID'),
-		'aim': _('AIM Address'),
-		'gadu-gadu': _('GG Number'),
-		'icq': _('ICQ Number'),
-		'msn': _('MSN Address'),
-		'yahoo': _('Yahoo! Address')}
+	uid_labels = {'jabber': _('Jabber ID:'),
+		'aim': _('AIM Address:'),
+		'gadu-gadu': _('GG Number:'),
+		'icq': _('ICQ Number:'),
+		'msn': _('MSN Address:'),
+		'yahoo': _('Yahoo! Address:')}
 	def __init__(self, account = None, jid = None, user_nick = None,
 	group = None):
 		self.account = account
@@ -441,7 +449,8 @@ class AddNewContactWindow:
 		'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox',
 		'protocol_hbox', 'nickname_entry', 'message_scrolledwindow',
 		'register_hbox', 'subscription_table', 'add_button',
-		'message_textview', 'connected_label', 'group_comboboxentry'):
+		'message_textview', 'connected_label', 'group_comboboxentry',
+		'auto_authorize_checkbutton'):
 			self.__dict__[w] = self.xml.get_widget(w)
 		if account and len(gajim.connections) >= 2:
 			prompt_text =\
@@ -486,8 +495,10 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 				liststore.append([type_, type_])
 		self.protocol_combobox.set_model(liststore)
 		self.protocol_combobox.set_active(0)
-		self.protocol_jid_combobox.set_sensitive(False)
+		self.protocol_jid_combobox.set_no_show_all(True)
+		self.protocol_jid_combobox.hide()
 		self.subscription_table.set_no_show_all(True)
+		self.auto_authorize_checkbutton.show()
 		self.message_scrolledwindow.set_no_show_all(True)
 		self.register_hbox.set_no_show_all(True)
 		self.register_hbox.hide()
@@ -497,11 +508,13 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 		self.protocol_jid_combobox.set_model(liststore)
 		self.xml.signal_autoconnect(self)
 		if jid:
-			type_ = gajim.get_transport_name_from_jid(jid) or 'jabber'
+			type_ = gajim.get_transport_name_from_jid(jid)
+			if not type_:
+				type_ = 'jabber'
 			if type_ == 'jabber':
 				self.uid_entry.set_text(jid)
 			else:
-				uid, transport = gajim.get_room_name_and_server_from_room_jid(jid)
+				uid, transport = gajim.get_name_and_server_from_jid(jid)
 				self.uid_entry.set_text(uid.replace('%', '@', 1))
 			#set protocol_combobox
 			model = self.protocol_combobox.get_model()
@@ -515,13 +528,13 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 				i += 1
 
 			# set protocol_jid_combobox
-			self.protocol_combobox.set_active(0)
+			self.protocol_jid_combobox.set_active(0)
 			model = self.protocol_jid_combobox.get_model()
 			iter = model.get_iter_first()
 			i = 0
 			while iter:
 				if model[iter][0] == transport:
-					self.protocol_combobox.set_active(i)
+					self.protocol_jid_combobox.set_active(i)
 					break
 				iter = model.iter_next(iter)
 				i += 1
@@ -626,7 +639,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 		else:
 			message= ''
 		group = self.group_comboboxentry.child.get_text().decode('utf-8')
-		auto_auth = self.xml.get_widget('auto_authorize_checkbutton').get_active()
+		auto_auth = self.auto_authorize_checkbutton.get_active()
 		gajim.interface.roster.req_sub(self, jid, message, self.account,
 			group = group, pseudo = nickname, auto_auth = auto_auth)
 		self.window.destroy()
@@ -641,13 +654,15 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 			for jid_ in self.agents[type_]:
 				model.append([jid_])
 			self.protocol_jid_combobox.set_active(0)
-			self.protocol_jid_combobox.set_sensitive(True)
+		if len(self.agents[type_]) > 1:
+			self.protocol_jid_combobox.set_no_show_all(False)
+			self.protocol_jid_combobox.show_all()
 		else:
-			self.protocol_jid_combobox.set_sensitive(False)
+			self.protocol_jid_combobox.hide()
 		if type_ in self.uid_labels:
 			self.uid_label.set_text(self.uid_labels[type_])
 		else:
-			self.uid_label.set_text(_('User ID'))
+			self.uid_label.set_text(_('User ID:'))
 		if type_ == 'jabber':
 			self.message_scrolledwindow.show()
 		else:
@@ -655,6 +670,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 		if type_ in self.available_types:
 			self.register_hbox.set_no_show_all(False)
 			self.register_hbox.show_all()
+			self.auto_authorize_checkbutton.hide()
 			self.connected_label.hide()
 			self.subscription_table.hide()
 			self.add_button.set_sensitive(False)
@@ -668,9 +684,11 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
 					self.subscription_table.hide()
 					self.connected_label.show()
 					self.add_button.set_sensitive(False)
+					self.auto_authorize_checkbutton.hide()
 					return
 			self.subscription_table.set_no_show_all(False)
 			self.subscription_table.show_all()
+			self.auto_authorize_checkbutton.show()
 			self.connected_label.hide()
 			self.add_button.set_sensitive(True)
 
@@ -697,8 +715,14 @@ class AboutDialog:
 		dlg.set_version(gajim.version)
 		s = u'Copyright © 2003-2006 Gajim Team'
 		dlg.set_copyright(s)
-		text = open('../COPYING').read()
-		dlg.set_license(text)
+		copying_file_path = None
+		if os.path.isfile(os.path.join(gajim.defs.docdir, 'COPYING')):
+			copying_file_path = os.path.join(gajim.defs.docdir, 'COPYING')
+		elif os.path.isfile('../COPYING'):
+			copying_file_path = '../COPYING'
+		if copying_file_path:
+			text = open(copying_file_path).read()
+			dlg.set_license(text)
 		
 		dlg.set_comments('%s\n%s %s\n%s %s' 
 			% (_('A GTK+ jabber client'), \
@@ -706,28 +730,40 @@ class AboutDialog:
 			_('PyGTK Version:'), self.tuple2str(gtk.pygtk_version)))
 		dlg.set_website('http://www.gajim.org/')
 
-		authors = []
-		authors_file = open('../AUTHORS').read()
-		authors_file = authors_file.split('\n')
-		for author in authors_file:
-			if author == 'CURRENT DEVELOPERS:':
-				authors.append(_('Current Developers:'))
-			elif author == 'PAST DEVELOPERS:':
-				authors.append('\n' + _('Past Developers:'))
-			elif author != '': # Real author line
-				authors.append(author)
-				
-		authors.append('\n' + _('THANKS:'))
+		authors_file_path = None
+		if os.path.isfile(os.path.join(gajim.defs.docdir, 'AUTHORS')):
+			authors_file_path = os.path.join(gajim.defs.docdir, 'AUTHORS')
+		elif os.path.isfile('../AUTHORS'):
+			authors_file_path = '../AUTHORS'
+		if authors_file_path:
+			authors = []
+			authors_file = open(authors_file_path).read()
+			authors_file = authors_file.split('\n')
+			for author in authors_file:
+				if author == 'CURRENT DEVELOPERS:':
+					authors.append(_('Current Developers:'))
+				elif author == 'PAST DEVELOPERS:':
+					authors.append('\n' + _('Past Developers:'))
+				elif author != '': # Real author line
+					authors.append(author)
+	
+			thanks_file_path = None
+			if os.path.isfile(os.path.join(gajim.defs.docdir, 'THANKS')):
+				thanks_file_path = os.path.join(gajim.defs.docdir, 'THANKS')
+			elif os.path.isfile('../THANKS'):
+				thanks_file_path = '../THANKS'
+			if thanks_file_path:
+				authors.append('\n' + _('THANKS:'))
 				
-		text = open('../THANKS').read()
-		text_splitted = text.split('\n')
-		text = '\n'.join(text_splitted[:-2]) # remove one english sentence
-		# and add it manually as translatable
-		text += '\n%s\n' % _('Last but not least, we would like to thank all '
-			'the package maintainers.')
-		authors.append(text)
+				text = open(thanks_file_path).read()
+				text_splitted = text.split('\n')
+				text = '\n'.join(text_splitted[:-2]) # remove one english sentence
+				# and add it manually as translatable
+				text += '\n%s\n' % _('Last but not least, we would like to thank all '
+					'the package maintainers.')
+				authors.append(text)
 		
-		dlg.set_authors(authors)
+			dlg.set_authors(authors)
 		
 		if gtk.pygtk_version >= (2, 8, 0) and gtk.gtk_version >= (2, 8, 0):
 			dlg.props.wrap_license = True
@@ -837,27 +873,31 @@ class FileChooserDialog(gtk.FileChooserDialog):
 			self.set_current_folder(current_folder)
 		else:
 			self.set_current_folder(helpers.get_documents_path())
-
-		buttons = self.action_area.get_children()
-		possible_responses = {gtk.STOCK_OPEN: on_response_ok,
-			gtk.STOCK_SAVE: on_response_ok,
-			gtk.STOCK_CANCEL: on_response_cancel}
-		for b in buttons:
-			for response in possible_responses:
-				if b.get_label() == response:
-					if not possible_responses[response]:
-						b.connect('clicked', self.just_destroy)
-					elif isinstance(possible_responses[response], tuple):
-						if len(possible_responses[response]) == 1:
-							b.connect('clicked', possible_responses[response][0])
-						else:
-							b.connect('clicked', *possible_responses[response])
-					else:
-						b.connect('clicked', possible_responses[response])
-					break
-
+		self.response_ok, self.response_cancel = \
+			on_response_ok, on_response_cancel
+		# in gtk+-2.10 clicked signal on some of the buttons in a dialog
+		# is emitted twice, so we cannot rely on 'clicked' signal
+		self.connect('response', self.on_dialog_response)
 		self.show_all()
 
+	def on_dialog_response(self, dialog, response):
+		if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE):
+			if self.response_cancel:
+				if isinstance(self.response_cancel, tuple):
+					self.response_cancel[0](dialog, *self.response_cancel[1:])
+				else:
+					self.response_cancel(dialog)
+			else:
+				self.just_destroy(dialog)
+		elif response == gtk.RESPONSE_OK:
+			if self.response_ok:
+				if isinstance(self.response_ok, tuple):
+					self.response_ok[0](dialog, *self.response_ok[1:])
+				else:
+					self.response_ok(dialog)
+			else:
+				self.just_destroy(dialog)
+			
 	def just_destroy(self, widget):
 		self.destroy()
 
@@ -1015,6 +1055,12 @@ class SubscriptionRequestWindow:
 		xml.signal_autoconnect(self)
 		self.window.show_all()
 
+	def prepare_popup_menu(self):
+		xml = gtkgui_helpers.get_glade('subscription_request_popup_menu.glade')
+		menu = xml.get_widget('subscription_request_popup_menu')
+		xml.signal_autoconnect(self)
+		return menu
+
 	def on_close_button_clicked(self, widget):
 		self.window.destroy()
 		
@@ -1025,7 +1071,7 @@ class SubscriptionRequestWindow:
 		if self.jid not in gajim.contacts.get_jid_list(self.account):
 			AddNewContactWindow(self.account, self.jid, self.user_nick)
 
-	def on_contact_info_button_clicked(self, widget):
+	def on_contact_info_activate(self, widget):
 		'''ask vcard'''
 		if gajim.interface.instances[self.account]['infos'].has_key(self.jid):
 			gajim.interface.instances[self.account]['infos'][self.jid].window.present()
@@ -1039,38 +1085,50 @@ class SubscriptionRequestWindow:
 			gajim.interface.instances[self.account]['infos'][self.jid].xml.\
 				get_widget('information_notebook').remove_page(0)
 	
+	def on_start_chat_activate(self, widget):
+		'''open chat'''
+		gajim.interface.roster.new_chat_from_jid(self.account, self.jid)
+
 	def on_deny_button_clicked(self, widget):
 		'''refuse the request'''
 		gajim.connections[self.account].refuse_authorization(self.jid)
 		self.window.destroy()
 
+	def on_actions_button_clicked(self, widget):
+		'''popup action menu'''
+		menu = self.prepare_popup_menu()
+		menu.show_all()
+		gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window)
+
+
 class JoinGroupchatWindow:
-	def __init__(self, account, server = '', room = '', nick = '',
-	automatic = False):
+	def __init__(self, account, room_jid = '', nick = '', automatic = False):
 		'''automatic is a dict like {'invities': []}
 		If automatic is not empty, this means room must be automaticaly configured
 		and when done, invities must be automatically invited'''
-		if server and room:
-			jid = room + '@' + server
-			if jid in gajim.gc_connected[account] and gajim.gc_connected[account][jid]:
-				ErrorDialog(_('You are already in room %s') % jid)
-				raise RuntimeError, 'You are already in this room'
+		if room_jid != '':
+			if room_jid in gajim.gc_connected[account] and\
+			gajim.gc_connected[account][room_jid]:
+				ErrorDialog(_('You are already in group chat %s') % room_jid)
+				raise GajimGeneralException, 'You are already in this group chat'
 		self.account = account
 		self.automatic = automatic
 		if nick == '':
 			nick = gajim.nicks[self.account]
 		if gajim.connections[account].connected < 2:
 			ErrorDialog(_('You are not connected to the server'),
-_('You can not join a group chat unless you are connected.'))
-			raise RuntimeError, 'You must be connected to join a groupchat'
+				_('You can not join a group chat unless you are connected.'))
+			raise GajimGeneralException, 'You must be connected to join a groupchat'
 
 		self._empty_required_widgets = []
 
 		self.xml = gtkgui_helpers.get_glade('join_groupchat_window.glade')
 		self.window = self.xml.get_widget('join_groupchat_window')
-		self.xml.get_widget('server_entry').set_text(server)
-		self.xml.get_widget('room_entry').set_text(room)
-		self.xml.get_widget('nickname_entry').set_text(nick)
+		self._room_jid_entry = self.xml.get_widget('room_jid_entry')
+		self._nickname_entry = self.xml.get_widget('nickname_entry')
+		
+		self._room_jid_entry.set_text(room_jid)
+		self._nickname_entry.set_text(nick)
 		self.xml.signal_autoconnect(self)
 		gajim.interface.instances[account]['join_gc'] = self #now add us to open windows
 		if len(gajim.connections) > 1:
@@ -1090,19 +1148,14 @@ _('You can not join a group chat unless you are connected.'))
 			self.recently_combobox.append_text(g)
 		if len(self.recently_groupchat) == 0:
 			self.recently_combobox.set_sensitive(False)
-		elif server == '' and room == '':
+		elif room_jid == '':
 			self.recently_combobox.set_active(0)
-			self.xml.get_widget('room_entry').select_region(0, -1)
-		elif room and server:
+			self._room_jid_entry.select_region(0, -1)
+		elif room_jid != '':
 			self.xml.get_widget('join_button').grab_focus()
 
-		self._server_entry = self.xml.get_widget('server_entry')
-		self._room_entry = self.xml.get_widget('room_entry')
-		self._nickname_entry = self.xml.get_widget('nickname_entry')
-		if not self._server_entry.get_text():
-			self._empty_required_widgets.append(self._server_entry)
-		if not self._room_entry.get_text():
-			self._empty_required_widgets.append(self._room_entry)
+		if not self._room_jid_entry.get_text():
+			self._empty_required_widgets.append(self._room_jid_entry)
 		if not self._nickname_entry.get_text():
 			self._empty_required_widgets.append(self._nickname_entry)
 		if len(self._empty_required_widgets):
@@ -1129,27 +1182,11 @@ _('You can not join a group chat unless you are connected.'))
 				if len(self._empty_required_widgets) == 0:
 					self.xml.get_widget('join_button').set_sensitive(True)
 
-	def on_room_entry_key_press_event(self, widget, event):
-		# Check for pressed @ and jump to server_entry if found
-		if event.keyval == gtk.keysyms.at:
-			self.xml.get_widget('server_entry').grab_focus()
-			return True
-
-	def on_server_entry_key_press_event(self, widget, event):
-		# If backspace is pressed in empty server_entry, return to the room entry
-		backspace = event.keyval == gtk.keysyms.BackSpace
-		server_entry = self.xml.get_widget('server_entry')
-		empty = len(server_entry.get_text()) == 0
-		if backspace and empty:
-			self.xml.get_widget('room_entry').grab_focus()
-			return True
-
 	def on_recently_combobox_changed(self, widget):
 		model = widget.get_model()
-		iter = widget.get_active_iter()
-		gid = model[iter][0].decode('utf-8')
-		self.xml.get_widget('room_entry').set_text(gid.split('@')[0])
-		self.xml.get_widget('server_entry').set_text(gid.split('@')[1])
+		iter_ = widget.get_active_iter()
+		room_jid = model[iter_][0].decode('utf-8')
+		self._room_jid_entry.set_text(room_jid)
 
 	def on_cancel_button_clicked(self, widget):
 		'''When Cancel button is clicked'''
@@ -1157,30 +1194,35 @@ _('You can not join a group chat unless you are connected.'))
 
 	def on_join_button_clicked(self, widget):
 		'''When Join button is clicked'''
-		nickname = self.xml.get_widget('nickname_entry').get_text().decode(
-			'utf-8')
-		room = self.xml.get_widget('room_entry').get_text().decode('utf-8')
-		server = self.xml.get_widget('server_entry').get_text().decode('utf-8')
+		nickname = self._nickname_entry.get_text().decode('utf-8')
+		room_jid = self._room_jid_entry.get_text().decode('utf-8')
 		password = self.xml.get_widget('password_entry').get_text().decode(
 			'utf-8')
-		jid = '%s@%s' % (room, server)
 		try:
-			jid = helpers.parse_jid(jid)
+			room_jid = helpers.parse_jid(room_jid)
 		except:
-			ErrorDialog(_('Invalid room or server name'),
-				_('The room name or server name has not allowed characters.'))
+			ErrorDialog(_('Invalid group chat Jabber ID'),
+				_('The group chat Jabber ID has not allowed characters.'))
 			return
 
-		if jid in self.recently_groupchat:
-			self.recently_groupchat.remove(jid)
-		self.recently_groupchat.insert(0, jid)
+		if gajim.interface.msg_win_mgr.has_window(room_jid, self.account):
+			ctrl = gajim.interface.msg_win_mgr.get_control(room_jid, self.account)
+			if ctrl.type_id != message_control.TYPE_GC:
+				ErrorDialog(_('This is not a group chat'),
+					_('%s is not the name of a group chat.') % room_jid)
+				return
+		if room_jid in self.recently_groupchat:
+			self.recently_groupchat.remove(room_jid)
+		self.recently_groupchat.insert(0, room_jid)
 		if len(self.recently_groupchat) > 10:
 			self.recently_groupchat = self.recently_groupchat[0:10]
-		gajim.config.set('recently_groupchat', ' '.join(self.recently_groupchat))
+		gajim.config.set('recently_groupchat',
+			' '.join(self.recently_groupchat))
 
 		if self.automatic:
-			gajim.automatic_rooms[self.account][jid] = self.automatic
-		gajim.interface.roster.join_gc_room(self.account, jid, nickname, password)
+			gajim.automatic_rooms[self.account][room_jid] = self.automatic
+		gajim.interface.roster.join_gc_room(self.account, room_jid, nickname,
+			password)
 
 		self.window.destroy()
 
@@ -1192,7 +1234,7 @@ class NewChatDialog(InputDialog):
 			title = _('Start Chat with account %s') % account
 		else:
 			title = _('Start Chat')
-		prompt_text = _('Fill in the jid, or nick of the contact you would like\nto send a chat message to:')
+		prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:')
 		InputDialog.__init__(self, title, prompt_text, is_modal = False)
 		
 		self.completion_dict = {}
@@ -1240,7 +1282,7 @@ class ChangePasswordDialog:
 		if not account or gajim.connections[account].connected < 2:
 			ErrorDialog(_('You are not connected to the server'),
 				_('Without a connection, you can not change your password.'))
-			raise RuntimeError, 'You are not connected to the server'
+			raise GajimGeneralException, 'You are not connected to the server'
 		self.account = account
 		self.xml = gtkgui_helpers.get_glade('change_password_dialog.glade')
 		self.dialog = self.xml.get_widget('change_password_dialog')
@@ -1302,7 +1344,8 @@ class PopupNotificationWindow:
 		# default image
 		if not path_to_image:
 			path_to_image = os.path.abspath(
-				os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'chat_msg_recv.png')) # img to display
+				os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
+					'chat_msg_recv.png')) # img to display
 
 		if event_type == _('Contact Signed In'):
 			bg_color = 'limegreen'
@@ -1322,7 +1365,7 @@ class PopupNotificationWindow:
 			bg_color = 'tan1'
 		elif event_type == _('Contact Changed Status'):			
 			bg_color = 'thistle2'
-		else: # Unknown event ! Shouldn't happen but deal with it
+		else: # Unknown event! Shouldn't happen but deal with it
 			bg_color = 'white'
 		popup_bg_color = gtk.gdk.color_parse(bg_color)
 		close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
@@ -1421,8 +1464,12 @@ class SingleMessageWindow:
 		self.cancel_button = self.xml.get_widget('cancel_button')
 		self.close_button = self.xml.get_widget('close_button')
 		self.message_tv_buffer.connect('changed', self.update_char_counter)
-		
-		self.to_entry.set_text(to)
+		if type(to) == type([]):
+			jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to])
+			self.to_entry.set_text(jid)
+			self.to_entry.set_sensitive(False)
+		else:
+			self.to_entry.set_text(to)
 		
 		if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send':
 			try:
@@ -1574,22 +1621,27 @@ class SingleMessageWindow:
 			ErrorDialog(_('Connection not available'),
 		_('Please make sure you are connected with "%s".') % self.account)
 			return
-		to_whom_jid = self.to_entry.get_text().decode('utf-8')
-		if self.completion_dict.has_key(to_whom_jid):
-			to_whom_jid = self.completion_dict[to_whom_jid].jid
-		subject = self.subject_entry.get_text().decode('utf-8')
-		begin, end = self.message_tv_buffer.get_bounds()
-		message = self.message_tv_buffer.get_text(begin, end).decode('utf-8')
-
-		if to_whom_jid.find('/announce/') != -1:
-			gajim.connections[self.account].send_motd(to_whom_jid, subject,
-				message)
-			return
+		if type(self.to) == type([]):
+			sender_list = [i[0].jid + '/' + i[0].resource for i in self.to]
+		else:
+			sender_list = [self.to_entry.get_text().decode('utf-8')]
+ 
+		for to_whom_jid in sender_list:
+			if self.completion_dict.has_key(to_whom_jid):
+				to_whom_jid = self.completion_dict[to_whom_jid].jid
+			subject = self.subject_entry.get_text().decode('utf-8')
+			begin, end = self.message_tv_buffer.get_bounds()
+			message = self.message_tv_buffer.get_text(begin, end).decode('utf-8')
+
+			if to_whom_jid.find('/announce/') != -1:
+				gajim.connections[self.account].send_motd(to_whom_jid, subject,
+					message)
+				return
 
-		# FIXME: allow GPG message some day
-		gajim.connections[self.account].send_message(to_whom_jid, message,
-			keyID = None, type = 'normal', subject=subject)
-		
+			# FIXME: allow GPG message some day
+			gajim.connections[self.account].send_message(to_whom_jid, message,
+				keyID = None, type = 'normal', subject=subject)
+			
 		self.subject_entry.set_text('') # we sent ok, clear the subject
 		self.message_tv_buffer.set_text('') # we sent ok, clear the textview
 
@@ -1725,10 +1777,13 @@ class XMLConsoleWindow:
 			self.input_textview.grab_focus()
 
 class PrivacyListWindow:
-	def __init__(self, account, privacy_list, list_type):
-		'''list_type can be 0 if list is created or 1 if it id edited'''
+	'''Window that is used for creating NEW or EDITING already there privacy
+	lists'''
+	def __init__(self, account, privacy_list_name, action):
+		'''action is 'EDIT' or 'NEW' depending on if we create a new priv list
+		or edit an already existing one'''
 		self.account = account
-		self.privacy_list = privacy_list
+		self.privacy_list_name = privacy_list_name
 
 		# Dicts and Default Values
 		self.active_rule = ''
@@ -1740,7 +1795,7 @@ class PrivacyListWindow:
 		self.allow_deny = 'allow'
 
 		# Connect to glade
-		self.xml = gtkgui_helpers.get_glade('privacy_list_edit_window.glade')
+		self.xml = gtkgui_helpers.get_glade('privacy_list_window.glade')
 		self.window = self.xml.get_widget('privacy_list_edit_window')
 
 		# Add Widgets
@@ -1762,10 +1817,9 @@ class PrivacyListWindow:
 		'privacy_list_default_checkbutton']:
 			self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add)
 
-		# Send translations
 		self.privacy_lists_title_label.set_label(
 			_('Privacy List <b><i>%s</i></b>') % \
-			gtkgui_helpers.escape_for_pango_markup(self.privacy_list))
+			gtkgui_helpers.escape_for_pango_markup(self.privacy_list_name))
 
 		if len(gajim.connections) > 1:
 			title = _('Privacy List for %s') % self.account
@@ -1776,9 +1830,9 @@ class PrivacyListWindow:
 		self.open_rule_button.set_sensitive(False)
 		self.privacy_list_active_checkbutton.set_sensitive(False)
 		self.privacy_list_default_checkbutton.set_sensitive(False)
+		self.list_of_rules_combobox.set_sensitive(False)
 
-		# Check if list is created (0) or edited (1)
-		if list_type == 1:
+		if action == 'EDIT':
 			self.refresh_rules()
 
 		count = 0
@@ -1793,22 +1847,20 @@ class PrivacyListWindow:
 		self.add_edit_vbox.set_no_show_all(True)
 		self.window.show_all()
 		self.add_edit_vbox.hide()
-		
+
 		self.xml.signal_autoconnect(self)
 
 	def on_privacy_list_edit_window_destroy(self, widget):
-		'''close window'''
-		if gajim.interface.instances[self.account].has_key('privacy_list_%s' % \
-		self.privacy_list):
-			del gajim.interface.instances[self.account]['privacy_list_%s' % \
-				self.privacy_list]
+		key_name = 'privacy_list_%s' % self.privacy_list_name
+		if key_name in gajim.interface.instances[self.account]:
+			del gajim.interface.instances[self.account][key_name]
 
 	def check_active_default(self, a_d_dict):
-		if a_d_dict['active'] == self.privacy_list:
+		if a_d_dict['active'] == self.privacy_list_name:
 			self.privacy_list_active_checkbutton.set_active(True)
 		else:
 			self.privacy_list_active_checkbutton.set_active(False)
-		if a_d_dict['default'] == self.privacy_list:
+		if a_d_dict['default'] == self.privacy_list_name:
 			self.privacy_list_default_checkbutton.set_active(True)
 		else:
 			self.privacy_list_default_checkbutton.set_active(False)		
@@ -1818,11 +1870,10 @@ class PrivacyListWindow:
 		self.global_rules = {}
 		for rule in rules:
 			if rule.has_key('type'):
-				text_item = 'Order: %s, action: %s, type: %s, value: %s' % \
-					(rule['order'], rule['action'], rule['type'],
-					rule['value'])
+				text_item = _('Order: %s, action: %s, type: %s, value: %s') % \
+					(rule['order'], rule['action'], rule['type'], rule['value'])
 			else:
-				text_item = 'Order: %s, action: %s' % (rule['order'],
+				text_item = _('Order: %s, action: %s') % (rule['order'],
 					rule['action'])
 			self.global_rules[text_item] = rule
 			self.list_of_rules_combobox.append_text(text_item)
@@ -1845,22 +1896,26 @@ class PrivacyListWindow:
 		gajim.connections[self.account].get_active_default_lists()
 
 	def refresh_rules(self):
-		gajim.connections[self.account].get_privacy_list(self.privacy_list)
+		gajim.connections[self.account].get_privacy_list(self.privacy_list_name)
 
 	def on_delete_rule_button_clicked(self, widget):
 		tags = []
 		for rule in self.global_rules:
-			if rule != \
-				self.list_of_rules_combobox.get_active_text().decode('utf-8'):
+			if rule != self.list_of_rules_combobox.get_active_text():
 				tags.append(self.global_rules[rule])
 		gajim.connections[self.account].set_privacy_list(
-			self.privacy_list, tags)
+			self.privacy_list_name, tags)
 		self.privacy_list_received(tags)
 		self.add_edit_vbox.hide()
+		if not tags: # we removed latest rule
+			if 'privacy_lists' in gajim.interface.instances[self.account]:
+				win = gajim.interface.instances[self.account]['privacy_lists']
+				win.remove_privacy_list_from_combobox(self.privacy_list_name)
+				win.draw_widgets()
 
 	def on_open_rule_button_clicked(self, widget):
 		self.add_edit_rule_label.set_label(
-		_('<b>Edit a rule</b>'))
+			_('<b>Edit a rule</b>'))
 		active_num = self.list_of_rules_combobox.get_active()
 		if active_num == -1:
 			self.active_rule = ''
@@ -1909,29 +1964,31 @@ class PrivacyListWindow:
 					self.edit_queries_send_checkbutton.set_active(True)
 				elif child == 'message':
 					self.edit_send_messages_checkbutton.set_active(True)
-		
+
 			if rule_info['action'] == 'allow':
-					self.edit_allow_radiobutton.set_active(True)
+				self.edit_allow_radiobutton.set_active(True)
 			else:
-					self.edit_deny_radiobutton.set_active(True)
+				self.edit_deny_radiobutton.set_active(True)
 		self.add_edit_vbox.show()
-	
+
 	def on_privacy_list_active_checkbutton_toggled(self, widget):
 		if widget.get_active():
-			gajim.connections[self.account].set_active_list(self.privacy_list)
+			gajim.connections[self.account].set_active_list(
+				self.privacy_list_name)
 		else:
 			gajim.connections[self.account].set_active_list(None)
 
 	def on_privacy_list_default_checkbutton_toggled(self, widget):
 		if widget.get_active():
-			gajim.connections[self.account].set_default_list(self.privacy_list)
+			gajim.connections[self.account].set_default_list(
+				self.privacy_list_name)
 		else:
 			gajim.connections[self.account].set_default_list(None)
 
 	def on_new_rule_button_clicked(self, widget):
 		self.reset_fields()
 		self.add_edit_vbox.show()
-	
+
 	def reset_fields(self):
 		self.edit_type_jabberid_entry.set_text('')
 		self.edit_allow_radiobutton.set_active(True)
@@ -1950,12 +2007,10 @@ class PrivacyListWindow:
 	def get_current_tags(self):
 		if self.edit_type_jabberid_radiobutton.get_active():
 			edit_type = 'jid'
-			edit_value = \
-				self.edit_type_jabberid_entry.get_text().decode('utf-8')
+			edit_value = self.edit_type_jabberid_entry.get_text()
 		elif self.edit_type_group_radiobutton.get_active():
 			edit_type = 'group'
-			edit_value = \
-				self.edit_type_group_combobox.get_active_text().decode('utf-8')
+			edit_value = self.edit_type_group_combobox.get_active_text()
 		elif self.edit_type_subscription_radiobutton.get_active():
 			edit_type = 'subscription'
 			subs = ['none', 'both', 'from', 'to']
@@ -1994,9 +2049,14 @@ class PrivacyListWindow:
 			else:
 				tags.append(current_tags)
 
-		gajim.connections[self.account].set_privacy_list(self.privacy_list, tags)
+		gajim.connections[self.account].set_privacy_list(
+			self.privacy_list_name, tags)
 		self.privacy_list_received(tags)
 		self.add_edit_vbox.hide()
+		if 'privacy_lists' in gajim.interface.instances[self.account]:
+			win = gajim.interface.instances[self.account]['privacy_lists']
+			win.add_privacy_list_to_combobox(self.privacy_list_name)
+			win.draw_widgets()
 
 	def on_list_of_rules_combobox_changed(self, widget):
 		self.add_edit_vbox.hide()
@@ -2011,32 +2071,28 @@ class PrivacyListWindow:
 		if active_bool:
 			self.allow_deny = radiobutton
 
-	def on_privacy_list_close_button_clicked(self, widget):
+	def on_close_button_clicked(self, widget):
 		self.window.destroy()
-	
-	def on_privacy_list_refresh_button_clicked(self, widget):
-		self.refresh_rules()
-		self.add_edit_vbox.hide()
 
 class PrivacyListsWindow:
-# To do: UTF-8 ???????
+	'''Window that is the main window for Privacy Lists;
+	we can list there the privacy lists and ask to create a new one
+	or edit an already there one'''
 	def __init__(self, account):
 		self.account = account
-
-		self.privacy_lists = []
-
 		self.privacy_lists_save = []		
 
-		self.xml = gtkgui_helpers.get_glade('privacy_lists_first_window.glade')
+		self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade')
 
 		self.window = self.xml.get_widget('privacy_lists_first_window')
 		for widget_to_add in ['list_of_privacy_lists_combobox',
-			'delete_privacy_list_button', 'open_privacy_list_button',
-			'new_privacy_list_button', 'new_privacy_list_entry', 'buttons_hbox',
-			'privacy_lists_refresh_button', 'close_privacy_lists_window_button']:
-			self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add)		
+		'delete_privacy_list_button', 'open_privacy_list_button',
+		'new_privacy_list_button', 'new_privacy_list_entry',
+		'privacy_lists_refresh_button', 'close_privacy_lists_window_button']:
+			self.__dict__[widget_to_add] = self.xml.get_widget(
+				widget_to_add)		
 
-		self.draw_privacy_lists_in_combobox()
+		self.draw_privacy_lists_in_combobox([])
 		self.privacy_lists_refresh()
 
 		self.enabled = True
@@ -2053,31 +2109,40 @@ class PrivacyListsWindow:
 		self.xml.signal_autoconnect(self)
 
 	def on_privacy_lists_first_window_destroy(self, widget):
-		'''close window'''
-		if gajim.interface.instances[self.account].has_key('privacy_lists'):
+		if 'privacy_lists' in gajim.interface.instances[self.account]:
 			del gajim.interface.instances[self.account]['privacy_lists']
 
-	def draw_privacy_lists_in_combobox(self):
+	def remove_privacy_list_from_combobox(self, privacy_list):
+		if privacy_list not in self.privacy_lists_save:
+			return
+		privacy_list_index = self.privacy_lists_save.index(privacy_list)
+		self.list_of_privacy_lists_combobox.remove_text(privacy_list_index)
+		self.privacy_lists_save.remove(privacy_list)
+
+	def add_privacy_list_to_combobox(self, privacy_list):
+		if privacy_list in self.privacy_lists_save:
+			return
+		self.list_of_privacy_lists_combobox.append_text(privacy_list)
+		self.privacy_lists_save.append(privacy_list)
+
+	def draw_privacy_lists_in_combobox(self, privacy_lists):
 		self.list_of_privacy_lists_combobox.set_active(-1)
 		self.list_of_privacy_lists_combobox.get_model().clear()
-		self.privacy_lists_save = self.privacy_lists
-		for add_item in self.privacy_lists:
-			self.list_of_privacy_lists_combobox.append_text(add_item)
-		if len(self.privacy_lists) == 0:
-			self.list_of_privacy_lists_combobox.set_sensitive(False)
-			self.buttons_hbox.set_sensitive(False)
-		elif len(self.privacy_lists) == 1:
-			self.list_of_privacy_lists_combobox.set_active(0)
+		self.privacy_lists_save = []
+		for add_item in privacy_lists:
+			self.add_privacy_list_to_combobox(add_item)
+		self.draw_widgets()
+
+	def draw_widgets(self):
+		if len(self.privacy_lists_save) == 0:
 			self.list_of_privacy_lists_combobox.set_sensitive(False)
-			self.buttons_hbox.set_sensitive(True)	
+			self.open_privacy_list_button.set_sensitive(False)
+			self.delete_privacy_list_button.set_sensitive(False)
 		else:
 			self.list_of_privacy_lists_combobox.set_sensitive(True)
-			self.buttons_hbox.set_sensitive(True)
 			self.list_of_privacy_lists_combobox.set_active(0)
-		self.privacy_lists = []
-
-	def on_privacy_lists_refresh_button_clicked(self, widget):
-		self.privacy_lists_refresh()
+			self.open_privacy_list_button.set_sensitive(True)
+			self.delete_privacy_list_button.set_sensitive(True)
 
 	def on_close_button_clicked(self, widget):
 		self.window.destroy()
@@ -2087,27 +2152,31 @@ class PrivacyListsWindow:
 			self.list_of_privacy_lists_combobox.get_active()]
 		gajim.connections[self.account].del_privacy_list(active_list)
 		self.privacy_lists_save.remove(active_list)
-		self.privacy_lists_received({'lists':self.privacy_lists_save})
+		self.privacy_lists_received({'lists': self.privacy_lists_save})
 
 	def privacy_lists_received(self, lists):
 		if not lists:
 			return
+		privacy_lists = []
 		for privacy_list in lists['lists']:
-			self.privacy_lists += [privacy_list]
-		self.draw_privacy_lists_in_combobox()
+			privacy_lists.append(privacy_list)
+		self.draw_privacy_lists_in_combobox(privacy_lists)
 
 	def privacy_lists_refresh(self):
 		gajim.connections[self.account].get_privacy_lists()
 
 	def on_new_privacy_list_button_clicked(self, widget):
-		name = self.new_privacy_list_entry.get_text().decode('utf-8')
-		if gajim.interface.instances[self.account].has_key(
-		'privacy_list_%s' % name):
-			gajim.interface.instances[self.account]['privacy_list_%s' % name].\
-				window.present()
+		name = self.new_privacy_list_entry.get_text()
+		if not name:
+			ErrorDialog(_('Invalid List Name'),
+				_('You must enter a name to create a privacy list.'))
+			return
+		key_name = 'privacy_list_%s' % name
+		if gajim.interface.instances[self.account].has_key(key_name):
+			gajim.interface.instances[self.account][key_name].window.present()
 		else:
-			gajim.interface.instances[self.account]['privacy_list_%s' % name] = \
-				PrivacyListWindow(self.account, name, 0)
+			gajim.interface.instances[self.account][key_name] = \
+				PrivacyListWindow(self.account, name, 'NEW')
 		self.new_privacy_list_entry.set_text('')
 
 	def on_privacy_lists_refresh_button_clicked(self, widget):
@@ -2116,16 +2185,17 @@ class PrivacyListsWindow:
 	def on_open_privacy_list_button_clicked(self, widget):
 		name = self.privacy_lists_save[
 			self.list_of_privacy_lists_combobox.get_active()]
+		key_name = 'privacy_list_%s' % name
 		if gajim.interface.instances[self.account].has_key(
-		'privacy_list_%s' % name):
-			gajim.interface.instances[self.account]['privacy_list_%s' % name].\
-				window.present()
+		key_name):
+			gajim.interface.instances[self.account][key_name].window.present()
 		else:
-			gajim.interface.instances[self.account]['privacy_list_%s' % name] = \
-				PrivacyListWindow(self.account, name, 1)
+			gajim.interface.instances[self.account][key_name] = \
+				PrivacyListWindow(self.account, name, 'EDIT')
 
 class InvitationReceivedDialog:
-	def __init__(self, account, room_jid, contact_jid, password = None, comment = None):
+	def __init__(self, account, room_jid, contact_jid, password = None,
+	comment = None):
 
 		self.room_jid = room_jid
 		self.account = account
@@ -2133,8 +2203,8 @@ class InvitationReceivedDialog:
 		self.dialog = xml.get_widget('invitation_received_dialog')
 
 		#FIXME: use nickname instead of contact_jid
-		pritext = _('%(contact_jid)s has invited you to %(room_jid)s room') % {
-			'room_jid': room_jid, 'contact_jid': contact_jid }
+		pritext = _('%(contact_jid)s has invited you to group chat %(room_jid)s')\
+			% {'room_jid': room_jid, 'contact_jid': contact_jid }
 		
 		label_text = '<big><b>%s</b></big>' % pritext
 
@@ -2155,10 +2225,9 @@ class InvitationReceivedDialog:
 	
 	def on_accept_button_clicked(self, widget):
 		self.dialog.destroy()
-		room, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid)
 		try:
-			JoinGroupchatWindow(self.account, server = server, room = room)
-		except RuntimeError:
+			JoinGroupchatWindow(self.account, self.room_jid)
+		except GajimGeneralException:
 			pass
 			
 class ProgressDialog:
@@ -2291,6 +2360,18 @@ class ImageChooserDialog(FileChooserDialog):
 			return
 		widget.get_preview_widget().set_from_pixbuf(pixbuf)
 
+class AvatarChooserDialog(ImageChooserDialog):
+	def __init__(self, path_to_file = '', on_response_ok = None,
+	on_response_cancel = None, on_response_clear = None):
+		ImageChooserDialog.__init__(self, path_to_file, on_response_ok,
+			on_response_cancel)
+		button = gtk.Button(None, gtk.STOCK_CLEAR)
+		if on_response_clear:
+			button.connect('clicked', on_response_clear)
+		button.show_all()
+		self.action_area.pack_start(button)
+		self.action_area.reorder_child(button, 0)
+
 class AddSpecialNotificationDialog:
 	def __init__(self, jid):
 		'''jid is the jid for which we want to add special notification
diff --git a/src/disco.py b/src/disco.py
index 1a23fcc467ad0f5714b24c2f6e758c72a30f70ee..7e94ef74e0857d383699f88c8622ab3cdadfd09b 100644
--- a/src/disco.py
+++ b/src/disco.py
@@ -1,19 +1,9 @@
 # -*- coding: utf-8 -*-
 ##	config.py
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##	- Stéphan Kochen <stephan@kochen.nl>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Stéphan Kochen <stephan@kochen.nl>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -36,10 +26,10 @@
 # - def update_actions(self)
 # - def default_action(self)
 # - def _find_item(self, jid, node)
-# - def _add_item(self, model, jid, node, item, force)
-# - def _update_item(self, model, iter, jid, node, item)
-# - def _update_info(self, model, iter, jid, node, identities, features, data)
-# - def _update_error(self, model, iter, jid, node)
+# - def _add_item(self, jid, node, item, force)
+# - def _update_item(self, iter, jid, node, item)
+# - def _update_info(self, iter, jid, node, identities, features, data)
+# - def _update_error(self, iter, jid, node)
 #
 # * Should call the super class for this method.
 # All others do not have to call back to the super class. (but can if they want
@@ -60,6 +50,7 @@ import groups
 
 from common import gajim
 from common import xmpp
+from common.exceptions import GajimGeneralException
 
 # Dictionary mapping category, type pairs to browser class, image pairs.
 # This is a function, so we can call it after the classes are declared.
@@ -133,6 +124,13 @@ class CacheDictionary:
 		def __call__(self):
 			return self.value
 
+	def cleanup(self):
+		for key in self.cache.keys():
+			item = self.cache[key]
+			if item.source:
+				gobject.source_remove(item.source)
+			del self.cache[key]
+
 	def _expire_timeout(self, key):
 		'''The timeout has expired, remove the object.'''
 		if key in self.cache:
@@ -144,8 +142,9 @@ class CacheDictionary:
 		item = self.cache[key]
 		if item.source:
 			gobject.source_remove(item.source)
-		source = gobject.timeout_add(self.lifetime, self._expire_timeout, key)
-		item.source = source
+		if self.lifetime:
+			source = gobject.timeout_add(self.lifetime, self._expire_timeout, key)
+			item.source = source
 
 	def __getitem__(self, key):
 		item = self.cache[key]
@@ -217,11 +216,15 @@ class ServicesCache:
 	ServiceCache instance.'''
 	def __init__(self, account):
 		self.account = account
-		self._items = CacheDictionary(15, getrefresh = False)
-		self._info = CacheDictionary(15, getrefresh = False)
+		self._items = CacheDictionary(0, getrefresh = False)
+		self._info = CacheDictionary(0, getrefresh = False)
 		self._subscriptions = CacheDictionary(5, getrefresh=False)
 		self._cbs = {}
 
+	def cleanup(self):
+		self._items.cleanup()
+		self._info.cleanup()
+
 	def _clean_closure(self, cb, type, addr):
 		# A closure died, clean up
 		cbkey = (type, addr)
@@ -393,12 +396,12 @@ class ServicesCache:
 			if self._cbs.has_key(cbkey):
 				del self._cbs[cbkey]
 
-# object is needed so that property() works
+# object is needed so that @property works
 class ServiceDiscoveryWindow(object):
 	'''Class that represents the Services Discovery window.'''
 	def __init__(self, account, jid = '', node = '',
 			address_entry = False, parent = None):
-		self._account = account
+		self.account = account
 		self.parent = parent
 		if not jid:
 			jid = gajim.config.get_per('accounts', account, 'hostname')
@@ -425,6 +428,7 @@ _('Without a connection, you can not browse available services'))
 		self.xml = gtkgui_helpers.get_glade('service_discovery_window.glade')
 		self.window = self.xml.get_widget('service_discovery_window')
 		self.services_treeview = self.xml.get_widget('services_treeview')
+		self.model = None
 		# This is more reliable than the cursor-changed signal.
 		selection = self.services_treeview.get_selection()
 		selection.connect_after('changed',
@@ -455,7 +459,6 @@ _('Without a connection, you can not browse available services'))
 
 			liststore = gtk.ListStore(str)
 			self.address_comboboxentry.set_model(liststore)
-			self.address_comboboxentry.set_text_column(0)
 			self.latest_addresses = gajim.config.get(
 				'latest_disco_addresses').split()
 			if jid in self.latest_addresses:
@@ -476,30 +479,35 @@ _('Without a connection, you can not browse available services'))
 		self.travel(jid, node)
 		self.window.show_all()
 
+	@property
 	def _get_account(self):
-		return self._account
+		return self.account
 
+	@property
 	def _set_account(self, value):
-		self._account = value
+		self.account = value
 		self.cache.account = value
 		if self.browser:
 			self.browser.account = value
-	account = property(_get_account, _set_account)
 
 	def _initial_state(self):
 		'''Set some initial state on the window. Separated in a method because
 		it's handy to use within browser's cleanup method.'''
 		self.progressbar.hide()
-		self.window.set_title(_('Service Discovery using account %s') % self.account)
+		title_text = _('Service Discovery using account %s') % self.account
+		self.window.set_title(title_text)
 		self._set_window_banner_text(_('Service Discovery'))
-		# FIXME: use self.banner_icon.clear() when we switch to GTK 2.8
-		self.banner_icon.set_from_file(None)
-		self.banner_icon.hide()		# Just clearing it doesn't work
+		if gtk.gtk_version >= (2, 8, 0) and gtk.pygtk_version >= (2, 8, 0):
+			self.banner_icon.clear()
+		else:
+			self.banner_icon.set_from_file(None)
+		self.banner_icon.hide() # Just clearing it doesn't work
 
 	def _set_window_banner_text(self, text, text_after = None):
 		theme = gajim.config.get('roster_theme')
 		bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
-		bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
+		bannerfontattrs = gajim.config.get_per('themes', theme,
+			'bannerfontattrs')
 		
 		if bannerfont:
 			font = pango.FontDescription(bannerfont)
@@ -592,6 +600,7 @@ _('Without a connection, you can not browse available services'))
 			self.browser = None
 		self.window.destroy()
 
+		self.cache.cleanup()
 		for child in self.children[:]:
 			child.parent = None
 			if chain:
@@ -724,9 +733,9 @@ class AgentBrowser:
 		note that the first two columns should ALWAYS be of type string and
 		contain the JID and node of the item respectively.'''
 		# JID, node, name, address
-		model = gtk.ListStore(str, str, str, str)
-		model.set_sort_column_id(3, gtk.SORT_ASCENDING)
-		self.window.services_treeview.set_model(model)
+		self.model = gtk.ListStore(str, str, str, str)
+		self.model.set_sort_column_id(3, gtk.SORT_ASCENDING)
+		self.window.services_treeview.set_model(self.model)
 		# Name column
 		col = gtk.TreeViewColumn(_('Name'))
 		renderer = gtk.CellRendererText()
@@ -744,7 +753,7 @@ class AgentBrowser:
 		self.window.services_treeview.set_headers_visible(True)
 
 	def _clean_treemodel(self):
-		self.window.services_treeview.get_model().clear()
+		self.model.clear()
 		for col in self.window.services_treeview.get_columns():
 			self.window.services_treeview.remove_column(col)
 		self.window.services_treeview.set_headers_visible(False)
@@ -876,8 +885,7 @@ class AgentBrowser:
 
 	def browse(self, force = False):
 		'''Fill the treeview with agents, fetching the info if necessary.'''
-		model = self.window.services_treeview.get_model()
-		model.clear()
+		self.model.clear()
 		self._total_items = self._progress = 0
 		self.window.progressbar.show()
 		self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb)
@@ -894,21 +902,21 @@ class AgentBrowser:
 	def _find_item(self, jid, node):
 		'''Check if an item is already in the treeview. Return an iter to it
 		if so, None otherwise.'''
-		model = self.window.services_treeview.get_model()
-		iter = model.get_iter_root()
+		iter = self.model.get_iter_root()
 		while iter:
-			cjid = model.get_value(iter, 0).decode('utf-8')
-			cnode = model.get_value(iter, 1).decode('utf-8')
+			cjid = self.model.get_value(iter, 0).decode('utf-8')
+			cnode = self.model.get_value(iter, 1).decode('utf-8')
 			if jid == cjid and node == cnode:
 				break
-			iter = model.iter_next(iter)
+			iter = self.model.iter_next(iter)
 		if iter:
 			return iter
 		return None
 
 	def _agent_items(self, jid, node, items, force):
 		'''Callback for when we receive a list of agent items.'''
-		model = self.window.services_treeview.get_model()
+		self.model.clear()
+		self._total_items = 0
 		gobject.source_remove(self._pulse_timeout)
 		self.window.progressbar.hide()
 		# The server returned an error
@@ -920,53 +928,48 @@ class AgentBrowser:
 _('This service does not contain any items to browse.'))
 			return
 		# We got a list of items
+		self.window.services_treeview.set_model(None)
 		for item in items:
 			jid = item['jid']
 			node = item.get('node', '')
-			iter = self._find_item(jid, node)
-			if iter:
-				# Already in the treeview
-				self._update_item(model, iter, jid, node, item)
-			else:
-				# Not in the treeview
-				self._total_items += 1
-				self._add_item(model, jid, node, item, force)
+			self._total_items += 1
+			self._add_item(jid, node, item, force)
+		self.window.services_treeview.set_model(self.model)
 
 	def _agent_info(self, jid, node, identities, features, data):
 		'''Callback for when we receive info about an agent's item.'''
 		addr = get_agent_address(jid, node)
-		model = self.window.services_treeview.get_model()
 		iter = self._find_item(jid, node)
 		if not iter:
 			# Not in the treeview, stop
 			return
 		if identities == 0:
 			# The server returned an error
-			self._update_error(model, iter, jid, node)
+			self._update_error(iter, jid, node)
 		else:
 			# We got our info
-			self._update_info(model, iter, jid, node,
+			self._update_info(iter, jid, node,
 				identities, features, data)
 		self.update_actions()
 
-	def _add_item(self, model, jid, node, item, force):
+	def _add_item(self, jid, node, item, force):
 		'''Called when an item should be added to the model. The result of a
 		disco#items query.'''
-		model.append((jid, node, item.get('name', ''),
+		self.model.append((jid, node, item.get('name', ''),
 			get_agent_address(jid, node)))
 
-	def _update_item(self, model, iter, jid, node, item):
+	def _update_item(self, iter, jid, node, item):
 		'''Called when an item should be updated in the model. The result of a
 		disco#items query. (seldom)'''
 		if item.has_key('name'):
-			model[iter][2] = item['name']
+			self.model[iter][2] = item['name']
 
-	def _update_info(self, model, iter, jid, node, identities, features, data):
+	def _update_info(self, iter, jid, node, identities, features, data):
 		'''Called when an item should be updated in the model with further info.
 		The result of a disco#info query.'''
-		model[iter][2] = identities[0].get('name', '')
+		self.model[iter][2] = identities[0].get('name', '')
 
-	def _update_error(self, model, iter, jid, node):
+	def _update_error(self, iter, jid, node):
 		'''Called when a disco#info query failed for an item.'''
 		pass
 
@@ -1050,14 +1053,12 @@ class ToplevelAgentBrowser(AgentBrowser):
 
 	# These are all callbacks to make tooltips work
 	def on_treeview_leave_notify_event(self, widget, event):
-		model = widget.get_model()
 		props = widget.get_path_at_pos(int(event.x), int(event.y))
 		if self.tooltip.timeout > 0:
 			if not props or self.tooltip.id == props[0]:
 				self.tooltip.hide_tooltip()
 
 	def on_treeview_motion_notify_event(self, widget, event):
-		model = widget.get_model()
 		props = widget.get_path_at_pos(int(event.x), int(event.y))
 		if self.tooltip.timeout > 0:
 			if not props or self.tooltip.id != props[0]:
@@ -1066,12 +1067,12 @@ class ToplevelAgentBrowser(AgentBrowser):
 			[row, col, x, y] = props
 			iter = None
 			try:
-				iter = model.get_iter(row)
+				iter = self.model.get_iter(row)
 			except:
 				self.tooltip.hide_tooltip()
 				return
-			jid = model[iter][0]
-			state = model[iter][4]
+			jid = self.model[iter][0]
+			state = self.model[iter][4]
 			# Not a category, and we have something to say about state
 			if jid and state > 0 and \
 					(self.tooltip.timeout == 0 or self.tooltip.id != props[0]):
@@ -1088,10 +1089,10 @@ class ToplevelAgentBrowser(AgentBrowser):
 		# JID, node, icon, description, state
 		# State means 2 when error, 1 when fetching, 0 when succes.
 		view = self.window.services_treeview
-		model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int)
-		model.set_sort_func(4, self._treemodel_sort_func)
-		model.set_sort_column_id(4, gtk.SORT_ASCENDING)
-		view.set_model(model)
+		self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int)
+		self.model.set_sort_func(4, self._treemodel_sort_func)
+		self.model.set_sort_column_id(4, gtk.SORT_ASCENDING)
+		view.set_model(self.model)
 
 		col = gtk.TreeViewColumn()
 		# Icon Renderer
@@ -1195,16 +1196,10 @@ class ToplevelAgentBrowser(AgentBrowser):
 		if not iter:
 			return
 		service = model[iter][0].decode('utf-8')
-		if service.find('@') != -1:
-			services = service.split('@', 1)
-			room = services[0]
-			service = services[1]
-		else:
-			room = ''
 		if not gajim.interface.instances[self.account].has_key('join_gc'):
 			try:
-				dialogs.JoinGroupchatWindow(self.account, service, room)
-			except RuntimeError:
+				dialogs.JoinGroupchatWindow(self.account, service)
+			except GajimGeneralException:
 				pass
 		else:
 			gajim.interface.instances[self.account]['join_gc'].window.present()
@@ -1251,10 +1246,11 @@ class ToplevelAgentBrowser(AgentBrowser):
 			# We can register this agent
 			registered_transports = []
 			jid_list = gajim.contacts.get_jid_list(self.account)
-			for j in jid_list:
-				contact = gajim.contacts.get_first_contact_from_jid(self.account, j)
+			for jid in jid_list:
+				contact = gajim.contacts.get_first_contact_from_jid(
+					self.account, jid)
 				if _('Transports') in contact.groups:
-					registered_transports.append(j)
+					registered_transports.append(jid)
 			if jid in registered_transports:
 				self.register_button.set_label(_('_Edit'))
 			else:
@@ -1333,41 +1329,38 @@ class ToplevelAgentBrowser(AgentBrowser):
 
 	def _create_category(self, cat, type=None):
 		'''Creates a category row.'''
-		model = self.window.services_treeview.get_model()
 		cat, prio = self._friendly_category(cat, type)
-		return model.append(None, ('', '', None, cat, prio))
+		return self.model.append(None, ('', '', None, cat, prio))
 
 	def _find_category(self, cat, type=None):
 		'''Looks up a category row and returns the iterator to it, or None.'''
-		model = self.window.services_treeview.get_model()
 		cat, prio = self._friendly_category(cat, type)
-		iter = model.get_iter_root()
+		iter = self.model.get_iter_root()
 		while iter:
-			if model.get_value(iter, 3).decode('utf-8') == cat:
+			if self.model.get_value(iter, 3).decode('utf-8') == cat:
 				break
-			iter = model.iter_next(iter)
+			iter = self.model.iter_next(iter)
 		if iter:
 			return iter
 		return None
 
 	def _find_item(self, jid, node):
-		model = self.window.services_treeview.get_model()
 		iter = None
-		cat_iter = model.get_iter_root()
+		cat_iter = self.model.get_iter_root()
 		while cat_iter and not iter:
-			iter = model.iter_children(cat_iter)
+			iter = self.model.iter_children(cat_iter)
 			while iter:
-				cjid = model.get_value(iter, 0).decode('utf-8')
-				cnode = model.get_value(iter, 1).decode('utf-8')
+				cjid = self.model.get_value(iter, 0).decode('utf-8')
+				cnode = self.model.get_value(iter, 1).decode('utf-8')
 				if jid == cjid and node == cnode:
 					break
-				iter = model.iter_next(iter)
-			cat_iter = model.iter_next(cat_iter)
+				iter = self.model.iter_next(iter)
+			cat_iter = self.model.iter_next(cat_iter)
 		if iter:
 			return iter
 		return None
 
-	def _add_item(self, model, jid, node, item, force):
+	def _add_item(self, jid, node, item, force):
 		# Row text
 		addr = get_agent_address(jid, node)
 		if item.has_key('name'):
@@ -1391,21 +1384,21 @@ class ToplevelAgentBrowser(AgentBrowser):
 		cat = self._find_category(*cat_args)
 		if not cat:
 			cat = self._create_category(*cat_args)
-		model.append(cat, (item['jid'], item.get('node', ''), pix, descr, 1))
+		self.model.append(cat, (item['jid'], item.get('node', ''), pix, descr, 1))
 		self._expand_all()
 		# Grab info on the service
 		self.cache.get_info(jid, node, self._agent_info, force = force)
 		self._update_progressbar()
 
-	def _update_item(self, model, iter, jid, node, item):
+	def _update_item(self, iter, jid, node, item):
 		addr = get_agent_address(jid, node)
 		if item.has_key('name'):
 			descr = "<b>%s</b>\n%s" % (item['name'], addr)
 		else:
 			descr = "<b>%s</b>" % addr
-		model[iter][3] = descr
+		self.model[iter][3] = descr
 
-	def _update_info(self, model, iter, jid, node, identities, features, data):
+	def _update_info(self, iter, jid, node, identities, features, data):
 		addr = get_agent_address(jid, node)
 		name = identities[0].get('name', '')
 		if name:
@@ -1427,32 +1420,32 @@ class ToplevelAgentBrowser(AgentBrowser):
 			break
 
 		# Check if we have to move categories
-		old_cat_iter = model.iter_parent(iter)
-		old_cat = model.get_value(old_cat_iter, 3).decode('utf-8')
-		if model.get_value(old_cat_iter, 3) == cat:
+		old_cat_iter = self.model.iter_parent(iter)
+		old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8')
+		if self.model.get_value(old_cat_iter, 3) == cat:
 			# Already in the right category, just update
-			model[iter][2] = pix
-			model[iter][3] = descr
-			model[iter][4] = 0
+			self.model[iter][2] = pix
+			self.model[iter][3] = descr
+			self.model[iter][4] = 0
 			return
 		# Not in the right category, move it.
-		model.remove(iter)
+		self.model.remove(iter)
 
 		# Check if the old category is empty
-		if not model.iter_is_valid(old_cat_iter):
+		if not self.model.iter_is_valid(old_cat_iter):
 			old_cat_iter = self._find_category(old_cat)
-		if not model.iter_children(old_cat_iter):
-			model.remove(old_cat_iter)
+		if not self.model.iter_children(old_cat_iter):
+			self.model.remove(old_cat_iter)
 
 		cat_iter = self._find_category(cat, type)
 		if not cat_iter:
 			cat_iter = self._create_category(cat, type)
-		model.append(cat_iter, (jid, node, pix, descr, 0))
+		self.model.append(cat_iter, (jid, node, pix, descr, 0))
 		self._expand_all()
 
-	def _update_error(self, model, iter, jid, node):
+	def _update_error(self, iter, jid, node):
 		addr = get_agent_address(jid, node)
-		model[iter][4] = 2
+		self.model[iter][4] = 2
 		self._progress += 1
 		self._update_progressbar()
 
@@ -1466,11 +1459,13 @@ class MucBrowser(AgentBrowser):
 		# JID, node, name, users, description, fetched
 		# This is rather long, I'd rather not use a data_func here though.
 		# Users is a string, because want to be able to leave it empty.
-		model = gtk.ListStore(str, str, str, str, str, bool)
-		model.set_sort_column_id(2, gtk.SORT_ASCENDING)
-		self.window.services_treeview.set_model(model)
+		self.model = gtk.ListStore(str, str, str, str, str, bool)
+		self.model.set_sort_column_id(2, gtk.SORT_ASCENDING)
+		self.window.services_treeview.set_model(self.model)
 		# Name column
 		col = gtk.TreeViewColumn(_('Name'))
+		col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+		col.set_fixed_width(100)
 		renderer = gtk.CellRendererText()
 		col.pack_start(renderer)
 		col.set_attributes(renderer, text = 2)
@@ -1490,6 +1485,13 @@ class MucBrowser(AgentBrowser):
 		col.set_attributes(renderer, text = 4)
 		self.window.services_treeview.insert_column(col, -1)
 		col.set_resizable(True)
+		# Id column
+		col = gtk.TreeViewColumn(_('Id'))
+		renderer = gtk.CellRendererText()
+		col.pack_start(renderer)
+		col.set_attributes(renderer, text = 0)
+		self.window.services_treeview.insert_column(col, -1)
+		col.set_resizable(True)
 		self.window.services_treeview.set_headers_visible(True)
 		# Source id for idle callback used to start disco#info queries.
 		self._fetch_source = None
@@ -1499,7 +1501,8 @@ class MucBrowser(AgentBrowser):
 		self.vadj = self.window.services_scrollwin.get_property('vadjustment')
 		self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll)
 		# And to size changes
-		self.size_cbid = self.window.services_scrollwin.connect('size-allocate', self.on_scroll)
+		self.size_cbid = self.window.services_scrollwin.connect(
+			'size-allocate', self.on_scroll)
 
 	def _clean_treemodel(self):
 		if self.size_cbid:
@@ -1528,16 +1531,12 @@ class MucBrowser(AgentBrowser):
 		if not iter:
 			return
 		service = model[iter][0].decode('utf-8')
-		if service.find('@') != -1:
-			services = service.split('@', 1)
-			room = services[0]
-			service = services[1]
-		else:
-			room = model[iter][1].decode('utf-8')
-		if not gajim.interface.instances[self.account].has_key('join_gc'):
+		room = model[iter][1].decode('utf-8')
+		if 'join_gc' not in gajim.interface.instances[self.account]:
 			try:
-				dialogs.JoinGroupchatWindow(self.account, service, room)
-			except RuntimeError:
+				room_jid = '%s@%s' % (service, room)
+				dialogs.JoinGroupchatWindow(self.account, service)
+			except GajimGeneralException:
 				pass
 		else:
 			gajim.interface.instances[self.account]['join_gc'].window.present()
@@ -1572,7 +1571,6 @@ class MucBrowser(AgentBrowser):
 			# Prevent a silly warning, try again in a bit.
 			self._fetch_source = gobject.timeout_add(100, self._start_info_query)
 			return
-		model = view.get_model()
 		# We have to do this in a pygtk <2.8 compatible way :/
 		#start, end = self.window.services_treeview.get_visible_range()
 		rect = view.get_visible_rect()
@@ -1581,7 +1579,7 @@ class MucBrowser(AgentBrowser):
 		try:
 			sx, sy = view.tree_to_widget_coords(rect.x, rect.y)
 			spath = view.get_path_at_pos(sx, sy)[0]
-			iter = model.get_iter(spath)
+			iter = self.model.get_iter(spath)
 		except TypeError:
 			self._fetch_source = None
 			return
@@ -1595,14 +1593,14 @@ class MucBrowser(AgentBrowser):
 		except TypeError:
 			# We're at the end of the model, we can leave end=None though.
 			pass
-		while iter and model.get_path(iter) != end:
-			if not model.get_value(iter, 5):
-				jid = model.get_value(iter, 0).decode('utf-8')
-				node = model.get_value(iter, 1).decode('utf-8')
+		while iter and self.model.get_path(iter) != end:
+			if not self.model.get_value(iter, 5):
+				jid = self.model.get_value(iter, 0).decode('utf-8')
+				node = self.model.get_value(iter, 1).decode('utf-8')
 				self.cache.get_info(jid, node, self._agent_info)
 				self._fetch_source = True
 				return
-			iter = model.iter_next(iter)
+			iter = self.model.iter_next(iter)
 		self._fetch_source = None
 
 	def _channel_altinfo(self, jid, node, items, name = None):
@@ -1623,22 +1621,21 @@ class MucBrowser(AgentBrowser):
 				self._fetch_source = None
 				return
 		else:
-			model = self.window.services_treeview.get_model()
 			iter = self._find_item(jid, node)
 			if iter:
 				if name:
-					model[iter][2] = name
-				model[iter][3] = len(items)		# The number of users
-				model[iter][5] = True
+					self.model[iter][2] = name
+				self.model[iter][3] = len(items)		# The number of users
+				self.model[iter][5] = True
 		self._fetch_source = None
 		self._query_visible()
 
-	def _add_item(self, model, jid, node, item, force):
-		model.append((jid, node, item.get('name', ''), '', '', False))
+	def _add_item(self, jid, node, item, force):
+		self.model.append((jid, node, item.get('name', ''), '', '', False))
 		if not self._fetch_source:
 			self._fetch_source = gobject.idle_add(self._start_info_query)
 
-	def _update_info(self, model, iter, jid, node, identities, features, data):
+	def _update_info(self, iter, jid, node, identities, features, data):
 		name = identities[0].get('name', '')
 		for form in data:
 			typefield = form.getField('FORM_TYPE')
@@ -1648,14 +1645,14 @@ class MucBrowser(AgentBrowser):
 				users = form.getField('muc#roominfo_occupants')
 				descr = form.getField('muc#roominfo_description')
 				if users:
-					model[iter][3] = users.getValue()
+					self.model[iter][3] = users.getValue()
 				if descr:
-					model[iter][4] = descr.getValue()
+					self.model[iter][4] = descr.getValue()
 				# Only set these when we find a form with additional info
 				# Some servers don't support forms and put extra info in
 				# the name attribute, so we preserve it in that case.
-				model[iter][2] = name
-				model[iter][5] = True
+				self.model[iter][2] = name
+				self.model[iter][5] = True
 				break
 		else:
 			# We didn't find a form, switch to alternate query mode
@@ -1665,7 +1662,7 @@ class MucBrowser(AgentBrowser):
 		self._fetch_source = None
 		self._query_visible()
 
-	def _update_error(self, model, iter, jid, node):
+	def _update_error(self, iter, jid, node):
 		# switch to alternate query mode
 		self.cache.get_items(jid, node, self._channel_altinfo)
 
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
index fc6a3cf2567dd64e1ac07c3f74181951df1fb3fa..02ecc9cb9aff967bc84a6497612f301f96e2ad34 100644
--- a/src/filetransfers_window.py
+++ b/src/filetransfers_window.py
@@ -213,7 +213,7 @@ class FileTransfersWindow:
 _('Connection with peer cannot be established.'))
 		self.tree.get_selection().unselect_all()
 
-	def show_stopped(self, jid, file_props):
+	def show_stopped(self, jid, file_props, error_msg = ''):
 		self.window.present()
 		self.window.window.focus()
 		if file_props['type'] == 'r':
@@ -222,6 +222,8 @@ _('Connection with peer cannot be established.'))
 			file_name = file_props['name']
 		sectext = '\t' + _('Filename: %s') % file_name
 		sectext += '\n\t' + _('Recipient: %s') % jid
+		if error_msg:
+			sectext += '\n\t' + _('Error message: %s') % error_msg
 		dialogs.ErrorDialog(_('File transfer stopped by the contact of the other side'), \
 			sectext)
 		self.tree.get_selection().unselect_all()
@@ -244,11 +246,16 @@ _('Connection with peer cannot be established.'))
 			gtk.RESPONSE_OK,
 			True, # select multiple true as we can select many files to send
 			gajim.config.get('last_send_dir'),
+			on_response_ok = on_ok,
+			on_response_cancel = lambda e:dialog.destroy()
 			)
 
-		btn = dialog.add_button(_('_Send'), gtk.RESPONSE_OK)
-		btn.set_use_stock(True) # FIXME: add send icon to this button (JUMP_TO)
-		btn.connect('clicked', on_ok)
+		btn = gtk.Button(_('_Send'))
+		btn.set_property('can-default', True)
+		# FIXME: add send icon to this button (JUMP_TO)
+		dialog.add_action_widget(btn, gtk.RESPONSE_OK)
+		dialog.set_default_response(gtk.RESPONSE_OK)
+		btn.show()
 
 	def send_file(self, account, contact, file_path):
 		''' start the real transfer(upload) of the file '''
@@ -448,8 +455,10 @@ _('Connection with peer cannot be established.'))
 			for ev_type in ('file-error', 'file-completed', 'file-request-error',
 			'file-send-error', 'file-stopped'):
 				for event in gajim.events.get_events(account, jid, [ev_type]):
-					if event.parameters[1]['sid'] == file_props['sid']:
+					if event.parameters['sid'] == file_props['sid']:
 						gajim.events.remove_events(account, jid, event)
+						gajim.interface.roster.draw_contact(jid, account)
+						gajim.interface.roster.show_title()
 		del(self.files_props[sid[0]][sid[1:]])
 		del(file_props)
 		
@@ -558,7 +567,7 @@ _('Connection with peer cannot be established.'))
 			(file_path, file_name) = os.path.split(file_props['file-name'])
 		else:
 			file_name = file_props['name']
-		text_props = file_name + '\n'
+		text_props = gtkgui_helpers.escape_for_pango_markup(file_name) + '\n'
 		text_props += contact.get_shown_name()
 		self.model.set(iter, 1, text_labels, 2, text_props, C_SID,
 			file_props['type'] + file_props['sid'])
diff --git a/src/gajim-remote.py b/src/gajim-remote.py
index 563cc121ac284eb3aa70e8a3106c6c8db183b7f8..5548b80aa63e26b5de4043552ad067c6de5998e9 100755
--- a/src/gajim-remote.py
+++ b/src/gajim-remote.py
@@ -1,22 +1,8 @@
-#!/bin/sh
-''':'
-exec python -OOt "$0" ${1+"$@"}
-' '''
-##	scripts/gajim-remote.py
+#!/usr/bin/env python
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##	- Dimitur Kirov <dkirov@gmail.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -28,7 +14,7 @@ exec python -OOt "$0" ${1+"$@"}
 ## GNU General Public License for more details.
 ##
 
-# gajim-remote help will show you the DBUS API of Gajim
+# gajim-remote help will show you the D-BUS API of Gajim
 
 import sys
 import locale
@@ -51,13 +37,11 @@ def send_error(error_message):
 
 try:
 	import dbus
-except:
-	raise exceptions.DbusNotSupported
-
-_version = getattr(dbus, 'version', (0, 20, 0))
-if _version[1] >= 41:
 	import dbus.service
 	import dbus.glib
+except:
+	print str(exceptions.DbusNotSupported())
+	sys.exit(1)
 
 OBJ_PATH = '/org/gajim/dbus/RemoteObject'
 INTERFACE = 'org.gajim.dbus.RemoteInterface'
@@ -90,8 +74,8 @@ class GajimRemote:
 					_('Shows or hides the roster window'),
 					[]
 				], 
-			'show_next_unread': [
-					_('Popups a window with the next unread message'),
+			'show_next_pending_event': [
+					_('Popups a window with the next pending event'),
 					[]
 				],
 			'list_contacts': [
@@ -320,14 +304,8 @@ class GajimRemote:
 		except:
 			raise exceptions.SessionBusNotPresent
 
-		if _version[1] >= 30:
-			obj = self.sbus.get_object(SERVICE, OBJ_PATH)
-			interface = dbus.Interface(obj, INTERFACE)
-		elif _version[1] < 30:
-			self.service = self.sbus.get_service(SERVICE)
-			interface = self.service.get_object(OBJ_PATH, INTERFACE)
-		else:
-			send_error(_('Unknown D-Bus version: %s') % _version[1])
+		obj = self.sbus.get_object(SERVICE, OBJ_PATH)
+		interface = dbus.Interface(obj, INTERFACE)
 
 		# get the function asked
 		self.method = interface.__getattr__(self.command)
@@ -447,10 +425,7 @@ class GajimRemote:
 		''' calls self.method with arguments from sys.argv[2:] '''
 		args = sys.argv[2:]
 		args = [i.decode(PREFERRED_ENCODING) for i in sys.argv[2:]]
-		if _version[1] >= 41:
-			args = [dbus.String(i) for i in args]
-		else:
-			args = [i.encode('UTF-8') for i in sys.argv[2:]]
+		args = [dbus.String(i) for i in args]
 		try:
 			res = self.method(*args)
 			return res
diff --git a/src/gajim.py b/src/gajim.py
index 1720d111a1666d0d829db2d3f372f36e110a7582..a2e66fd5e437736806c587c20632a4fa0d379562 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -1,23 +1,11 @@
-#!/bin/sh
-''':'
-exec python -OOt "$0" ${1+"$@"}
-' '''
+#!/usr/bin/env python
 ##	gajim.py
 ##
-## Contributors for this file:
-## - Yann Le Boulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <kourem@gmail.com>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
 ##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
+## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -41,6 +29,8 @@ from chat_control import ChatControlBase
 from atom_window import AtomWindow
 
 from common import exceptions
+from common.zeroconf import connection_zeroconf
+from common import dbus_support
 
 if os.name == 'posix': # dl module is Unix Only
 	try: # rename the process name to gajim
@@ -82,16 +72,14 @@ except exceptions.PysqliteNotAvailable, e:
 if os.name == 'nt':
 	try:
 		import winsound # windows-only built-in module for playing wav
-		import win32api
-		import win32con
 	except:
 		pritext = _('Gajim needs pywin32 to run')
 		sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018'
 
 if pritext:
 	dlg = gtk.MessageDialog(None, 
-				gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
-				gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
+		gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
+		gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
 
 	dlg.format_secondary_text(sectext)
 	dlg.run()
@@ -144,23 +132,16 @@ for o, a in opts:
 	elif o in ('-p', '--profile'): # gajim --profile name
 		profile = a
 
-pid_filename = os.path.expanduser('~/.gajim/gajim')
-config_filename = os.path.expanduser('~/.gajim/config')
-if os.name == 'nt':
-	try:
-		# Documents and Settings\[User Name]\Application Data\Gajim\logs
-		config_filename = os.environ['appdata'] + '/Gajim/config'
-		pid_filename = os.environ['appdata'] + '/Gajim/gajim'
-	except KeyError:
-		# win9x so ./config
-		config_filename = 'config'
-		pid_filename = 'gajim'
-
-if profile:
-	config_filename += '.%s' % profile
-	pid_filename += '.%s' % profile
-
-pid_filename += '.pid'
+import locale
+profile = unicode(profile, locale.getpreferredencoding())
+
+import common.configpaths
+common.configpaths.init_profile(profile)
+gajimpaths = common.configpaths.gajimpaths
+
+pid_filename = gajimpaths['PID_FILE']
+config_filename = gajimpaths['CONFIG_FILE']
+
 import dialogs
 if os.path.exists(pid_filename):
 	path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
@@ -194,7 +175,6 @@ atexit.register(on_exit)
 parser = optparser.OptionsParser(config_filename)
 
 import roster_window
-import systray
 import profile_window
 import config
 
@@ -217,7 +197,7 @@ class GlibIdleQueue(idlequeue.IdleQueue):
 		Start listening for events from fd
 		'''
 		res = gobject.io_add_watch(fd, flags, self.process_events, 
-										priority=gobject.PRIORITY_LOW)
+			priority=gobject.PRIORITY_LOW)
 		# store the id of the watch, so that we can remove it on unplug
 		self.events[fd] = res
 	
@@ -274,7 +254,7 @@ class Interface:
 			on_response_no = (response, account, data[3], 'no'))
 
 	def handle_event_error_answer(self, account, array):
-		#('ERROR_ANSWER', account, (id, jid_from. errmsg, errcode))
+		#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
 		id, jid_from, errmsg, errcode = array
 		if unicode(errcode) in ('403', '406') and id:
 			# show the error dialog
@@ -286,7 +266,7 @@ class Interface:
 				file_props = ft.files_props['s'][sid]
 				file_props['error'] = -4
 				self.handle_event_file_request_error(account, 
-					(jid_from, file_props))
+					(jid_from, file_props, errmsg))
 				conn = gajim.connections[account]
 				conn.disconnect_transfer(file_props)
 				return
@@ -341,7 +321,8 @@ class Interface:
 		# Inform all controls for this account of the connection state change
 		for ctrl in self.msg_win_mgr.get_controls():
 			if ctrl.account == account:
-				if status == 'offline':
+				if status == 'offline' or (status == 'invisible' and \
+						gajim.connections[account].is_zeroconf):
 					ctrl.got_disconnected()
 				else:
 					# Other code rejoins all GCs, so we don't do it here
@@ -422,12 +403,13 @@ class Interface:
 				elif contact1.show in statuss:
 					old_show = statuss.index(contact1.show)
 				if (resources != [''] and (len(lcontact) != 1 or 
-					lcontact[0].show != 'offline')) and jid.find('@') > 0:
+				lcontact[0].show != 'offline')) and jid.find('@') > 0:
 					old_show = 0
 					contact1 = gajim.contacts.copy_contact(contact1)
 					lcontact.append(contact1)
 				contact1.resource = resource
-			if contact1.jid.find('@') > 0 and len(lcontact) == 1: # It's not an agent
+			if contact1.jid.find('@') > 0 and len(lcontact) == 1:
+				# It's not an agent
 				if old_show == 0 and new_show > 1:
 					if not contact1.jid in gajim.newly_added[account]:
 						gajim.newly_added[account].append(contact1.jid)
@@ -459,6 +441,7 @@ class Interface:
 			if ji in jid_list:
 				# Update existing iter
 				self.roster.draw_contact(ji, account)
+				self.roster.draw_group(_('Transports'), account)
 				# transport just signed in/out, don't show popup notifications
 				# for 30s
 				account_ji = account + '/' + ji
@@ -509,18 +492,23 @@ class Interface:
 
 	def handle_event_msg(self, account, array):
 		# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
-		# chatstate, msg_id, composing_jep, user_nick)) user_nick is JEP-0172
+		# chatstate, msg_id, composing_jep, user_nick, xhtml))
+		# user_nick is JEP-0172
 
 		full_jid_with_resource = array[0]
 		jid = gajim.get_jid_without_resource(full_jid_with_resource)
 		resource = gajim.get_resource_from_jid(full_jid_with_resource)
 
 		message = array[1]
+		encrypted = array[3]
 		msg_type = array[4]
 		subject = array[5]
 		chatstate = array[6]
 		msg_id = array[7]
 		composing_jep = array[8]
+		xhtml = array[10]
+		if gajim.config.get('ignore_incoming_xhtml'):
+			xhtml = None
 		if gajim.jid_is_transport(jid):
 			jid = jid.replace('@', '')
 
@@ -530,6 +518,7 @@ class Interface:
 		message_control.TYPE_GC:
 			# It's a Private message
 			pm = True
+			msg_type = 'pm'
 
 		chat_control = None
 		jid_of_control = full_jid_with_resource
@@ -544,7 +533,8 @@ class Interface:
 			# unknow contact or offline message
 			jid_of_control = jid
 			chat_control = self.msg_win_mgr.get_control(jid, account)
-		elif highest_contact and resource != highest_contact.resource:
+		elif highest_contact and resource != highest_contact.resource and \
+		highest_contact.show != 'offline':
 			jid_of_control = full_jid_with_resource
 			chat_control = None
 		elif not pm:
@@ -575,39 +565,51 @@ class Interface:
 					contact.msg_id = msg_id
 
 		# THIS MUST BE AFTER chatstates handling
-		# AND BEFORE playsound (else we here sounding on chatstates!)
+		# AND BEFORE playsound (else we ear sounding on chatstates!)
 		if not message: # empty message text
 			return
 
 		if gajim.config.get('ignore_unknown_contacts') and \
 			not gajim.contacts.get_contact(account, jid) and not pm:
 			return
-
+		if not contact:
+			# contact is not in the roster, create a fake one to display
+			# notification
+			contact = common.contacts.Contact(jid = jid, resource = resource) 
 		advanced_notif_num = notify.get_advanced_notification('message_received',
 			account, contact)
 
 		# Is it a first or next message received ?
 		first = False
-		if not chat_control and not gajim.events.get_events(account,
-		jid_of_control, ['chat']):
-			# It's a first message and not a Private Message
+		if msg_type == 'normal':
+			if not gajim.events.get_events(account, jid, ['normal']):
+				first = True
+		elif not chat_control and not gajim.events.get_events(account, 
+		jid_of_control, [msg_type]): # msg_type can be chat or pm
 			first = True
 
 		if pm:
 			nickname = resource
-			msg_type = 'pm'
-			groupchat_control.on_private_message(nickname, message, array[2])
+			groupchat_control.on_private_message(nickname, message, array[2],
+				xhtml)
 		else:
 			# array: (jid, msg, time, encrypted, msg_type, subject)
-			self.roster.on_message(jid, message, array[2], account, array[3],
-				msg_type, subject, resource, msg_id, array[9], advanced_notif_num)
+			if encrypted:
+				self.roster.on_message(jid, message, array[2], account, array[3],
+					msg_type, subject, resource, msg_id, array[9],
+					advanced_notif_num)
+			else:
+				# xhtml in last element
+				self.roster.on_message(jid, message, array[2], account, array[3],
+					msg_type, subject, resource, msg_id, array[9],
+					advanced_notif_num, xhtml = xhtml)
 			nickname = gajim.get_name_from_jid(account, jid)
 		# Check and do wanted notifications	
 		msg = message
 		if subject:
 			msg = _('Subject: %s') % subject + '\n' + msg
-		notify.notify('new_message', jid, account, [msg_type, first, nickname,
-			msg], advanced_notif_num)
+		notify.notify('new_message', jid_of_control, account, [msg_type,
+			first, nickname, msg], advanced_notif_num)
 
 		if self.remote_ctrl:
 			self.remote_ctrl.raise_signal('NewMessage', (account, array))
@@ -617,33 +619,35 @@ class Interface:
 		full_jid_with_resource = array[0]
 		jids = full_jid_with_resource.split('/', 1)
 		jid = jids[0]
-		gcs = self.msg_win_mgr.get_controls(message_control.TYPE_GC)
-		for gc_control in gcs:
-			if jid == gc_control.contact.jid:
-				if len(jids) > 1: # it's a pm
-					nick = jids[1]
-					if not self.msg_win_mgr.get_control(full_jid_with_resource, account):
-						tv = gc_control.list_treeview
-						model = tv.get_model()
-						i = gc_control.get_contact_iter(nick)
-						if i:
-							show = model[i][3]
-						else:
-							show = 'offline'
-						gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
-							name = nick, show = show)
-						c = gajim.contacts.contact_from_gc_contact(gc_c)
-						self.roster.new_chat(c, account, private_chat = True)
-					ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
-					ctrl.print_conversation('Error %s: %s' % (array[1], array[2]),
-								'status')
-					return
-
-				gc_control.print_conversation('Error %s: %s' % (array[1], array[2]))
-				if gc_control.parent_win.get_active_jid() == jid:
-					gc_control.set_subject(gc_control.subject)
+		gc_control = self.msg_win_mgr.get_control(jid, account)
+		if gc_control and gc_control.type_id != message_control.TYPE_GC:
+			gc_control = None
+		if gc_control:
+			if len(jids) > 1: # it's a pm
+				nick = jids[1]
+				if not self.msg_win_mgr.get_control(full_jid_with_resource,
+				account):
+					tv = gc_control.list_treeview
+					model = tv.get_model()
+					iter = gc_control.get_contact_iter(nick)
+					if iter:
+						show = model[iter][3]
+					else:
+						show = 'offline'
+					gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
+						name = nick, show = show)
+					c = gajim.contacts.contact_from_gc_contact(gc_c)
+					self.roster.new_chat(c, account, private_chat = True)
+				ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
+				ctrl.print_conversation('Error %s: %s' % (array[1], array[2]),
+							'status')
 				return
 
+			gc_control.print_conversation('Error %s: %s' % (array[1], array[2]))
+			if gc_control.parent_win.get_active_jid() == jid:
+				gc_control.set_subject(gc_control.subject)
+			return
+
 		if gajim.jid_is_transport(jid):
 			jid = jid.replace('@', '')
 		msg = array[2]
@@ -697,8 +701,8 @@ class Interface:
 			self.remote_ctrl.raise_signal('Subscribed', (account, array))
 
 	def handle_event_unsubscribed(self, account, jid):
-		dialogs.InformationDialog(_('Contact "%s" removed subscription from you') % jid,
-				_('You will always see him or her as offline.'))
+		dialogs.InformationDialog(_('Contact "%s" removed subscription from you')\
+			% jid, _('You will always see him or her as offline.'))
 		# FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does not show deny
 		gajim.connections[account].ack_unsubscribed(jid)
 		if self.remote_ctrl:
@@ -741,8 +745,8 @@ class Interface:
 			config.ServiceRegistrationWindow(array[0], array[1], account,
 				array[2])
 		else:
-			dialogs.ErrorDialog(_('Contact with "%s" cannot be established')\
-% array[0], _('Check your connection or try again later.'))
+			dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
+				% array[0], _('Check your connection or try again later.'))
 
 	def handle_event_agent_info_items(self, account, array):
 		#('AGENT_INFO_ITEMS', account, (agent, node, items))
@@ -851,6 +855,7 @@ class Interface:
 			self.remote_ctrl.raise_signal('LastStatusTime', (account, array))
 
 	def handle_event_os_info(self, account, array):
+		#'OS_INFO' (account, (jid, resource, client_info, os_info))
 		win = None
 		if self.instances[account]['infos'].has_key(array[0]):
 			win = self.instances[account]['infos'][array[0]]
@@ -875,8 +880,8 @@ class Interface:
 		# Get the window and control for the updated status, this may be a PrivateChatControl
 		control = self.msg_win_mgr.get_control(room_jid, account)
 		if control:
-			control.chg_contact_status(nick, show, status, array[4], array[5], array[6],
-						array[7], array[8], array[9], array[10])
+			control.chg_contact_status(nick, show, status, array[4], array[5],
+				array[6], array[7], array[8], array[9], array[10])
 
 		# print status in chat window and update status/GPG image
 		if self.msg_win_mgr.has_window(fjid, account):
@@ -892,38 +897,48 @@ class Interface:
 			if self.remote_ctrl:
 				self.remote_ctrl.raise_signal('GCPresence', (account, array))
 
-
 	def handle_event_gc_msg(self, account, array):
-		# ('GC_MSG', account, (jid, msg, time))
+		# ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg))
 		jids = array[0].split('/', 1)
 		room_jid = jids[0]
 		gc_control = self.msg_win_mgr.get_control(room_jid, account)
 		if not gc_control:
 			return
+		xhtml = array[4]
+		if gajim.config.get('ignore_incoming_xhtml'):
+			xhtml = None
 		if len(jids) == 1:
 			# message from server
 			nick = ''
 		else:
 			# message from someone
 			nick = jids[1]
-		gc_control.on_message(nick, array[1], array[2])
+		gc_control.on_message(nick, array[1], array[2], array[3], xhtml)
 		if self.remote_ctrl:
 			self.remote_ctrl.raise_signal('GCMessage', (account, array))
 
 	def handle_event_gc_subject(self, account, array):
-		#('GC_SUBJECT', account, (jid, subject, body))
+		#('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
 		jids = array[0].split('/', 1)
 		jid = jids[0]
 		gc_control = self.msg_win_mgr.get_control(jid, account)
 		if not gc_control:
 			return
 		gc_control.set_subject(array[1])
-		# We can receive a subject with a body that contains "X has set the subject to Y" ...
-		if array[2]:
-			gc_control.print_conversation(array[2])
-		# ... Or the message comes from the occupant who set the subject
-		elif len(jids) > 1:
-			gc_control.print_conversation('%s has set the subject to %s' % (jids[1], array[1]))
+		# Standard way, the message comes from the occupant who set the subject
+		text = None
+		if len(jids) > 1:
+			text = '%s has set the subject to %s' % (jids[1], array[1])
+		# Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be 
+		# deleted one day. We can receive a subject with a body that contains 
+		# "X has set the subject to Y" ...
+		elif array[2]:
+			text = array[2]
+		if text is not None:
+			if array[3]:
+				gc_control.print_old_conversation(text)
+			else:
+				gc_control.print_conversation(text)
 
 	def handle_event_gc_config(self, account, array):
 		#('GC_CONFIG', account, (jid, config))  config is a dict
@@ -959,7 +974,7 @@ class Interface:
 		self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
 			array[3]))
 
-		if helpers.allow_showing_notification(account, 'notify_on_new_message'):
+		if helpers.allow_showing_notification(account):
 			path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
 				'gc_invitation.png')
 			path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
@@ -1010,7 +1025,7 @@ class Interface:
 				return
 			# Add it to roster
 			contact = gajim.contacts.create_contact(jid = jid, name = name,
-			groups = groups, show = 'offline', sub = sub, ask = ask)
+				groups = groups, show = 'offline', sub = sub, ask = ask)
 			gajim.contacts.add_contact(account, contact)
 			self.roster.add_contact_to_roster(jid, account)
 		else:
@@ -1075,13 +1090,17 @@ class Interface:
 		gmail_messages_list = array[2]
 		if gajim.config.get('notify_on_new_gmail_email'):
 			img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
-				'single_msg_recv.png') #FIXME: find a better image
-			title = _('New E-mail on %(gmail_mail_address)s') % \
+				'new_email_recv.png')
+			title = _('New mail on %(gmail_mail_address)s') % \
 				{'gmail_mail_address': jid}
-			text = i18n.ngettext('You have %d new E-mail message', 'You have %d new E-mail messages', gmail_new_messages, gmail_new_messages, gmail_new_messages)
+			text = i18n.ngettext('You have %d new mail conversation',
+				'You have %d new mail conversations', gmail_new_messages,
+				gmail_new_messages, gmail_new_messages)
 			
 			if gajim.config.get('notify_on_new_gmail_email_extra'):
 				for gmessage in gmail_messages_list:
+					#FIXME: emulate Gtalk client popups. find out what they parse and how
+					#they decide what to show
 					# each message has a 'From', 'Subject' and 'Snippet' field
 					text += _('\nFrom: %(from_address)s') % \
 						{'from_address': gmessage['From']}
@@ -1149,8 +1168,15 @@ class Interface:
 
 		self.roster.show_title()
 		if no_queue: # We didn't have a queue: we change icons
+			if not gajim.contacts.get_contact_with_highest_priority(account, jid):
+				# add contact to roster ("Not In The Roster") if he is not
+				self.roster.add_to_not_in_the_roster(account, jid) 
 			self.roster.draw_contact(jid, account)
 
+		# Show contact in roster (if he is invisible for example) and select line
+		path = self.roster.get_path(jid, account)		
+		self.roster.show_and_select_path(path, jid, account)
+
 	def remove_first_event(self, account, jid, type_ = None):
 		event = gajim.events.get_first_event(account, jid, type_)
 		self.remove_event(account, jid, event)
@@ -1161,24 +1187,26 @@ class Interface:
 			return
 		# no other event?
 		if not len(gajim.events.get_events(account, jid)):
-			if not gajim.config.get('showoffline'):
-				contact = gajim.contacts.get_contact_with_highest_priority(account,
-					jid)
-				if contact:	
-					self.roster.really_remove_contact(contact, account)
+			contact = gajim.contacts.get_contact_with_highest_priority(account,
+				jid)
+			show_transport = gajim.config.get('show_transports_group')
+			if contact and (contact.show in ('error', 'offline') and \
+			not gajim.config.get('showoffline') or (
+			gajim.jid_is_transport(jid) and not show_transport)):
+				self.roster.really_remove_contact(contact, account)
 		self.roster.show_title()
 		self.roster.draw_contact(jid, account)
 
 	def handle_event_file_request_error(self, account, array):
-		jid = array[0]
-		file_props = array[1]
+		# ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
+		jid, file_props, errmsg = array
 		ft = self.instances['file_transfers']
 		ft.set_status(file_props['type'], file_props['sid'], 'stop')
 		errno = file_props['error']
 
 		if helpers.allow_popup_window(account):
 			if errno in (-4, -5):
-				ft.show_stopped(jid, file_props)
+				ft.show_stopped(jid, file_props, errmsg)
 			else:
 				ft.show_request_error(file_props)
 			return
@@ -1224,8 +1252,11 @@ class Interface:
 				path_to_image = path, title = event_type, text = txt)
 
 	def handle_event_file_progress(self, account, file_props):
-		self.instances['file_transfers'].set_progress(file_props['type'], 
-			file_props['sid'], file_props['received-len'])
+		if time.time() - self.last_ftwindow_update > 0.5:
+			# update ft window every 500ms
+			self.last_ftwindow_update = time.time()
+			self.instances['file_transfers'].set_progress(file_props['type'], 
+				file_props['sid'], file_props['received-len'])
 
 	def handle_event_file_rcv_completed(self, account, file_props):
 		ft = self.instances['file_transfers']
@@ -1328,7 +1359,9 @@ class Interface:
 			self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing')
 
 	def handle_event_vcard_published(self, account, array):
-		dialogs.InformationDialog(_('vCard publication succeeded'), _('Your personal information has been published successfully.'))
+		if self.instances[account].has_key('profile'):
+			win = self.instances[account]['profile']
+			win.vcard_published()
 		for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC):
 			if gc_control.account == account:
 				show = gajim.SHOW_LIST[gajim.connections[account].connected]
@@ -1337,7 +1370,9 @@ class Interface:
 					gc_control.room_jid, show, status)
 
 	def handle_event_vcard_not_published(self, account, array):
-		dialogs.InformationDialog(_('vCard publication failed'), _('There was an error while publishing your personal information, try again later.'))
+		if self.instances[account].has_key('profile'):
+			win = self.instances[account]['profile']
+			win.vcard_not_published()
 
 	def handle_event_signed_in(self, account, empty):
 		'''SIGNED_IN event is emitted when we sign in, so handle it'''
@@ -1362,12 +1397,11 @@ class Interface:
 			if gajim.gc_connected[account].has_key(room_jid) and\
 					gajim.gc_connected[account][room_jid]:
 				continue
-			room, server = gajim.get_room_name_and_server_from_room_jid(room_jid)
 			nick = gc_control.nick
 			password = ''
 			if gajim.gc_passwords.has_key(room_jid):
 				password = gajim.gc_passwords[room_jid]
-			gajim.connections[account].join_gc(nick, room, server, password)
+			gajim.connections[account].join_gc(nick, room_jid, password)
 
 	def handle_event_metacontacts(self, account, tags_list):
 		gajim.contacts.define_metacontacts(account, tags_list)
@@ -1401,6 +1435,21 @@ class Interface:
 			if win.startswith('privacy_list_'):
 				self.instances[account][win].check_active_default(data)
 
+	def handle_event_zc_name_conflict(self, account, data):
+		dlg = dialogs.InputDialog(_('Username Conflict'),
+			_('Please type a new username for your local account'), 
+			is_modal = True)
+		dlg.input_entry.set_text(data)
+		response = dlg.get_response()
+		if response == gtk.RESPONSE_OK:
+			new_name = dlg.input_entry.get_text()
+			gajim.config.set_per('accounts', account, 'name', new_name)
+			status = gajim.connections[account].status
+			gajim.connections[account].username = new_name
+			gajim.connections[account].change_status(status, '')
+		else:
+			gajim.connections[account].change_status('offline','')
+
 	def read_sleepy(self):	
 		'''Check idle status and change that status if needed'''
 		if not self.sleeper.poll():
@@ -1614,25 +1663,28 @@ class Interface:
 		menu.show_all()
 		return menu
 
-	def init_emoticons(self):
-		if not gajim.config.get('emoticons_theme'):
+	def init_emoticons(self, need_reload = False):
+		emot_theme = gajim.config.get('emoticons_theme')
+		if not emot_theme:
 			return
 
 		#initialize emoticons dictionary and unique images list
 		self.emoticons_images = list()
 		self.emoticons = dict()
 
-		emot_theme = gajim.config.get('emoticons_theme')
-		if not emot_theme:
-			return
 		path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
 		if not os.path.exists(path):
 			# It's maybe a user theme
 			path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
-			if not os.path.exists(path): # theme doesn't exist
+			if not os.path.exists(path): # theme doesn't exist, disable emoticons
+				gajim.config.set('emoticons_theme', '')
 				return
 		sys.path.append(path)
-		from emoticons import emoticons as emots
+		import emoticons
+		if need_reload:
+			# we need to reload else that doesn't work when changing emoticon set
+			reload(emoticons) 
+		emots = emoticons.emoticons
 		for emot in emots:
 			emot_file = os.path.join(path, emots[emot])
 			if not self.image_is_ok(emot_file):
@@ -1646,7 +1698,7 @@ class Interface:
 				self.emoticons_images.append((emot, pix))
 			self.emoticons[emot.upper()] = emot_file
 		sys.path.remove(path)
-		del emots
+		del emoticons
 		if self.emoticons_menu:
 			self.emoticons_menu.destroy()
 		self.emoticons_menu = self.prepare_emoticons_menu()
@@ -1707,6 +1759,7 @@ class Interface:
 			'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
 			'PRIVACY_LISTS_ACTIVE_DEFAULT': \
 				self.handle_event_privacy_lists_active_default,
+			'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict,
 		}
 		gajim.handlers = self.handlers
 
@@ -1726,20 +1779,28 @@ class Interface:
 				err_str)
 			sys.exit()
 
-	def handle_event(self, account, jid, type_):
+	def handle_event(self, account, fjid, type_):
 		w = None
-		fjid = jid
-		resource = gajim.get_resource_from_jid(jid)
-		jid = gajim.get_jid_without_resource(jid)
+		resource = gajim.get_resource_from_jid(fjid)
+		jid = gajim.get_jid_without_resource(fjid)
 		if type_ in ('printed_gc_msg', 'gc_msg'):
 			w = self.msg_win_mgr.get_window(jid, account)
-		elif type_ in ('printed_chat', 'chat'):
+		elif type_ in ('printed_chat', 'chat', ''):
+			# '' is for log in/out notifications
 			if self.msg_win_mgr.has_window(fjid, account):
 				w = self.msg_win_mgr.get_window(fjid, account)
 			else:
+				highest_contact = gajim.contacts.get_contact_with_highest_priority(
+					account, jid)
+				# jid can have a window if this resource was lower when he sent
+				# message and is now higher because the other one is offline
+				if resource and highest_contact.resource == resource and \
+				not self.msg_win_mgr.has_window(jid, account):
+					resource = None
+					fjid = jid
 				contact = gajim.contacts.get_contact(account, jid, resource)
-				if isinstance(contact, list):
-					contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+				if not contact or isinstance(contact, list):
+					contact = highest_contact
 				self.roster.new_chat(contact, account, resource = resource)
 				w = self.msg_win_mgr.get_window(fjid, account)
 				gajim.last_message_time[account][jid] = 0 # long time ago
@@ -1763,16 +1824,18 @@ class Interface:
 		elif type_ in ('normal', 'file-request', 'file-request-error',
 		'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
 			# Get the first single message event
-			event = gajim.events.get_first_event(account, jid, type_)
-			# Open the window
-			self.roster.open_event(account, jid, event)
-		elif type_ == 'gmail':
-			if gajim.config.get_per('accounts', account, 'savepass'):
-				url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\
-				(urllib.quote(gajim.config.get_per('accounts', account, 'name')),
-				urllib.quote(gajim.config.get_per('accounts', account, 'password')))
+			event = gajim.events.get_first_event(account, fjid, type_)
+			if not event:
+				# default to jid without resource
+				event = gajim.events.get_first_event(account, jid, type_)
+				# Open the window
+				self.roster.open_event(account, jid, event)
 			else:
-				url = ('http://mail.google.com/')
+				# Open the window
+				self.roster.open_event(account, fjid, event)
+		elif type_ == 'gmail':
+			url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote(
+				gajim.config.get_per('accounts', account, 'name'))
 			helpers.launch_browser_mailer('url', url)
 		elif type_ == 'gc-invitation':
 			event = gajim.events.get_first_event(account, jid, type_)
@@ -1780,6 +1843,7 @@ class Interface:
 			dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
 				data[1])
 			gajim.events.remove_events(account, jid, event)
+			self.roster.draw_contact(jid, account)
 		if w:
 			w.set_active_tab(fjid, account)
 			w.window.present()
@@ -1857,11 +1921,17 @@ class Interface:
 			self.handle_event_file_progress)
 		gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
 		self.register_handlers()
+		if gajim.config.get('enable_zeroconf'):
+			gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
 		for account in gajim.config.get_per('accounts'):
-			gajim.connections[account] = common.connection.Connection(account)
-															
+			if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+				gajim.connections[account] = common.connection.Connection(account)
+
+		# gtk hooks
 		gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
 		gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
+		if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
+			gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
 		
 		self.instances = {'logs': {}}
 		
@@ -1891,6 +1961,12 @@ class Interface:
 		else:
 			self.remote_ctrl = None
 
+		if gajim.config.get('networkmanager_support') and dbus_support.supported:
+			try:
+				import network_manager_listener
+			except:
+				print >> sys.stderr, _('Network Manager support not available')
+
 		self.show_vcard_when_connect = []
 
 		path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
@@ -1904,18 +1980,18 @@ class Interface:
 		self.systray_enabled = False
 		self.systray_capabilities = False
 		
-		if os.name == 'nt':
-			try:
-				import systraywin32
-			except: # user doesn't have trayicon capabilities
-				pass
-			else:
-				self.systray_capabilities = True
-				self.systray = systraywin32.SystrayWin32()
-		else:
+		if os.name == 'nt' and gtk.pygtk_version >= (2, 10, 0) and\
+		gtk.gtk_version >= (2, 10, 0):
+			import statusicon 
+			self.systray = statusicon.StatusIcon() 
+			self.systray_capabilities = True
+		else: # use ours, not GTK+ one
+			# [FIXME: remove this when we migrate to 2.10 and we can do
+			# cool tooltips somehow and (not dying to keep) animation]
+			import systray
 			self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES
 			if self.systray_capabilities:
-			    self.systray = systray.Systray()
+				self.systray = systray.Systray()
 
 		if self.systray_capabilities and gajim.config.get('trayicon'):
 			self.show_systray()
@@ -1945,6 +2021,9 @@ class Interface:
 					'choose another language by setting the speller_language option.'
 					) % lang)
 				gajim.config.set('use_speller', False)
+
+		self.last_ftwindow_update = 0
+
 		gobject.timeout_add(100, self.autoconnect)
 		gobject.timeout_add(200, self.process_connections)
 		gobject.timeout_add(500, self.read_sleepy)
@@ -1968,7 +2047,8 @@ if __name__ == '__main__':
 			cli = gnome.ui.master_client()
 			cli.connect('die', die_cb)
 			
-			path_to_gajim_script = gtkgui_helpers.get_abspath_for_script('gajim')
+			path_to_gajim_script = gtkgui_helpers.get_abspath_for_script(
+				'gajim')
 			
 			if path_to_gajim_script:
 				argv = [path_to_gajim_script]
@@ -1983,5 +2063,6 @@ if __name__ == '__main__':
 		gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
 
 	check_paths.check_and_possibly_create_paths()
+
 	Interface()
 	gtk.main()
diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py
index cc9b1742ee36ea5489ade54c10b2cdcc1147d2d1..93e979cdaf5a3aa68ae2134b7356e7180bf9c03b 100644
--- a/src/gajim_themes_window.py
+++ b/src/gajim_themes_window.py
@@ -9,7 +9,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -51,7 +51,7 @@ class GajimThemesWindow:
 		self.themes_tree = self.xml.get_widget('themes_treeview')
 		self.theme_options_vbox = self.xml.get_widget('theme_options_vbox')
 		self.colorbuttons = {}
-		for chatstate in ('active', 'inactive', 'composing', 'paused', 'gone',
+		for chatstate in ('inactive', 'composing', 'paused', 'gone',
 		'muc_msg', 'muc_directed_msg'):
 			self.colorbuttons[chatstate] = self.xml.get_widget(chatstate + \
 				'_colorbutton')
@@ -198,7 +198,7 @@ class GajimThemesWindow:
 		self.no_update = False
 		gajim.interface.roster.change_roster_style(None)
 
-		for chatstate in ('active', 'inactive', 'composing', 'paused', 'gone',
+		for chatstate in ('inactive', 'composing', 'paused', 'gone',
 		'muc_msg', 'muc_directed_msg'):
 			color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \
 				'_color')
@@ -333,11 +333,6 @@ class GajimThemesWindow:
 			font_props[1] = True
 		return font_props
 
-	def on_active_colorbutton_color_set(self, widget):
-		self.no_update = True
-		self._set_color(True, widget, 'state_active_color')
-		self.no_update = False
-
 	def on_inactive_colorbutton_color_set(self, widget):
 		self.no_update = True
 		self._set_color(True, widget, 'state_inactive_color')
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index 05432780619a95b315a94bcc1db9abbe2afabd9c..a78830a84a46a2db6372a6c6f15c955894b4bd83 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -4,7 +4,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -39,12 +39,13 @@ from common import helpers
 from chat_control import ChatControl
 from chat_control import ChatControlBase
 from conversation_textview import ConversationTextview
+from common.exceptions import GajimGeneralException
 
 #(status_image, type, nick, shown_nick)
 (
 C_IMG, # image to show state (online, new message etc)
-C_TYPE, # type of the row ('contact' or 'group')
-C_NICK, # contact nickame or group name
+C_NICK, # contact nickame or ROLE name
+C_TYPE, # type of the row ('contact' or 'role')
 C_TEXT, # text shown in the cellrenderer
 C_AVATAR, # avatar of the contact
 ) = range(5)
@@ -94,6 +95,9 @@ class PrivateChatControl(ChatControl):
 	TYPE_ID = message_control.TYPE_PM
 
 	def __init__(self, parent_win, contact, acct):
+		room_jid = contact.jid.split('/')[0]
+		room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid, acct)
+		self.room_name = room_ctrl.name
 		ChatControl.__init__(self, parent_win, contact, acct)
 		self.TYPE_ID = 'pm'
 		self.display_names = (_('Private Chat'), _('Private Chats'))
@@ -103,9 +107,10 @@ class PrivateChatControl(ChatControl):
 		if not message:
 			return
 
-		# We need to make sure that we can still send through the room and that the
-		# recipient did not go away
-		contact = gajim.contacts.get_first_contact_from_jid(self.account, self.contact.jid)
+		# We need to make sure that we can still send through the room and that
+		# the recipient did not go away
+		contact = gajim.contacts.get_first_contact_from_jid(self.account,
+			self.contact.jid)
 		if contact is None:
 			# contact was from pm in MUC
 			room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
@@ -114,7 +119,7 @@ class PrivateChatControl(ChatControl):
 				dialogs.ErrorDialog(
 					_('Sending private message failed'),
 					#in second %s code replaces with nickname
-					_('You are no longer in room "%s" or "%s" has left.') % \
+					_('You are no longer in group chat "%s" or "%s" has left.') % \
 					(room, nick))
 				return
 
@@ -172,17 +177,20 @@ class GroupchatControl(ChatControlBase):
 		self.nick = contact.name
 		self.name = self.room_jid.split('@')[0]
 
-		self.hide_chat_buttons_always = gajim.config.get('always_hide_groupchat_buttons')
+		self.hide_chat_buttons_always = gajim.config.get(
+			'always_hide_groupchat_buttons')
 		self.chat_buttons_set_visible(self.hide_chat_buttons_always)
-		self.widget_set_visible(self.xml.get_widget('banner_eventbox'), gajim.config.get('hide_groupchat_banner'))
-		self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'), gajim.config.get('hide_groupchat_occupants_list'))
+		self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
+			gajim.config.get('hide_groupchat_banner'))
+		self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'),
+			gajim.config.get('hide_groupchat_occupants_list'))
 		self.gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
 
 		self._last_selected_contact = None # None or holds jid, account tuple
 		# alphanum sorted
 		self.muc_cmds = ['ban', 'chat', 'query', 'clear', 'close', 'compact',
-			'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick', 'part',
-			'names', 'say', 'topic']
+			'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick',
+			'part', 'names', 'say', 'topic']
 		# muc attention flag (when we are mentioned in a muc)
 		# if True, the room has mentioned us
 		self.attention_flag = False
@@ -200,7 +208,8 @@ class GroupchatControl(ChatControlBase):
 		xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
 
 		widget = xm.get_widget('bookmark_room_menuitem')
-		id = widget.connect('activate', self._on_bookmark_room_menuitem_activate)
+		id = widget.connect('activate',
+			self._on_bookmark_room_menuitem_activate)
 		self.handlers[id] = widget
 
 		widget = xm.get_widget('change_nick_menuitem')
@@ -208,11 +217,13 @@ class GroupchatControl(ChatControlBase):
 		self.handlers[id] = widget
 
 		widget = xm.get_widget('configure_room_menuitem')
-		id = widget.connect('activate', self._on_configure_room_menuitem_activate)
+		id = widget.connect('activate',
+			self._on_configure_room_menuitem_activate)
 		self.handlers[id] = widget
 
 		widget = xm.get_widget('change_subject_menuitem')
-		id = widget.connect('activate', self._on_change_subject_menuitem_activate)
+		id = widget.connect('activate',
+			self._on_change_subject_menuitem_activate)
 		self.handlers[id] = widget
 
 		widget = xm.get_widget('compact_view_menuitem')
@@ -226,29 +237,27 @@ class GroupchatControl(ChatControlBase):
 		self.gc_popup_menu = xm.get_widget('gc_control_popup_menu')
 
 		self.name_label = self.xml.get_widget('banner_name_label')
-		id = self.parent_win.window.connect('focus-in-event',
-						self._on_window_focus_in_event)
-		self.handlers[id] = self.parent_win.window
 
 		# set the position of the current hpaned
 		self.hpaned_position = gajim.config.get('gc-hpaned-position')
 		self.hpaned = self.xml.get_widget('hpaned')
 		self.hpaned.set_position(self.hpaned_position)
 
-		list_treeview = self.list_treeview = self.xml.get_widget('list_treeview')
-		selection = list_treeview.get_selection()
+		self.list_treeview = self.xml.get_widget('list_treeview')
+		selection = self.list_treeview.get_selection()
 		id = selection.connect('changed', 
 				self.on_list_treeview_selection_changed)
 		self.handlers[id] = selection
-		id = list_treeview.connect('style-set', self.on_list_treeview_style_set)
-		self.handlers[id] = list_treeview
+		id = self.list_treeview.connect('style-set',
+			self.on_list_treeview_style_set)
+		self.handlers[id] = self.list_treeview
 		# we want to know when the the widget resizes, because that is
 		# an indication that the hpaned has moved...
 		# FIXME: Find a better indicator that the hpaned has moved.
 		id = self.list_treeview.connect('size-allocate',
 			self.on_treeview_size_allocate)
 		self.handlers[id] = self.list_treeview
-		#status_image, type, nickname, shown_nick
+		#status_image, shown_nick, type, nickname, avatar
 		store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf)
 		store.set_sort_column_id(C_TEXT, gtk.SORT_ASCENDING)
 		self.list_treeview.set_model(store)
@@ -275,7 +284,8 @@ class GroupchatControl(ChatControlBase):
 		renderer_text = gtk.CellRendererText() # nickname
 		column.pack_start(renderer_text, expand = True)
 		column.add_attribute(renderer_text, 'markup', C_TEXT)
-		column.set_cell_data_func(renderer_text, tree_cell_data_func, self.list_treeview)
+		column.set_cell_data_func(renderer_text, tree_cell_data_func,
+			self.list_treeview)
 
 		self.list_treeview.append_column(column)
 
@@ -316,15 +326,6 @@ class GroupchatControl(ChatControlBase):
 
 		menu.show_all()
 
-	def notify_on_new_messages(self):
-		return gajim.config.get('notify_on_all_muc_messages') or \
-			self.attention_flag
-
-	def _on_window_focus_in_event(self, widget, event):
-		'''When window gets focus'''
-		if self.parent_win.get_active_jid() == self.room_jid:
-			self.conv_textview.allow_focus_out_line = True
-	
 	def on_treeview_size_allocate(self, widget, allocation):
 		'''The MUC treeview has resized. Move the hpaned in all tabs to match'''
 		self.hpaned_position = self.hpaned.get_position()
@@ -371,39 +372,44 @@ class GroupchatControl(ChatControlBase):
 
 		has_focus = self.parent_win.window.get_property('has-toplevel-focus')
 		current_tab = self.parent_win.get_active_control() == self
+		color_name = None
 		color = None
 		theme = gajim.config.get('roster_theme')
 		if chatstate == 'attention' and (not has_focus or not current_tab):
 			self.attention_flag = True
-			color = gajim.config.get_per('themes', theme,
+			color_name = gajim.config.get_per('themes', theme,
 							'state_muc_directed_msg_color')
 		elif chatstate:
 			if chatstate == 'active' or (current_tab and has_focus):
 				self.attention_flag = False
-				color = gajim.config.get_per('themes', theme,
-								'state_active_color')
+				# get active color from gtk
+				color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
 			elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\
 					not self.attention_flag:
-				color = gajim.config.get_per('themes', theme, 'state_muc_msg_color')
-		if color:
-			color = gtk.gdk.colormap_get_system().alloc_color(color)
-
+				color_name = gajim.config.get_per('themes', theme,
+					'state_muc_msg_color')
+		if color_name:
+			color = gtk.gdk.colormap_get_system().alloc_color(color_name)
+			
 		label_str = self.name
+		
+		# count waiting highlighted messages
+		unread = ''
+		num_unread = self.get_nb_unread()
+		if num_unread == 1:
+			unread = '*'
+		elif num_unread > 1:
+			unread = '[' + unicode(num_unread) + ']'
+		label_str = unread + label_str
 		return (label_str, color)
 
 	def get_tab_image(self):
-		# Set tab image (always 16x16); unread messages show the 'message' image
-		img_16 = gajim.interface.roster.get_appropriate_state_images(
-			self.room_jid, icon_name = 'message')
-
+		# Set tab image (always 16x16)
 		tab_image = None
-		if self.attention_flag and gajim.config.get('show_unread_tab_icon'):
-			tab_image = img_16['message']
+		if gajim.gc_connected[self.account][self.room_jid]:
+			tab_image = gajim.interface.roster.load_icon('muc_active')
 		else:
-			if gajim.gc_connected[self.account][self.room_jid]:
-				tab_image = img_16['muc_active']
-			else:
-				tab_image = img_16['muc_inactive']
+			tab_image = gajim.interface.roster.load_icon('muc_inactive')
 		return tab_image
 
 	def update_ui(self):
@@ -430,15 +436,18 @@ class GroupchatControl(ChatControlBase):
 			childs[3].set_sensitive(False)
 		return menu
 
-	def on_message(self, nick, msg, tim):
+	def on_message(self, nick, msg, tim, has_timestamp = False, xhtml = None):
 		if not nick:
 			# message from server
-			self.print_conversation(msg, tim = tim)
+			self.print_conversation(msg, tim = tim, xhtml = xhtml)
 		else:
 			# message from someone
-			self.print_conversation(msg, nick, tim)
+			if has_timestamp:
+				self.print_old_conversation(msg, nick, tim, xhtml)
+			else:
+				self.print_conversation(msg, nick, tim, xhtml)
 
-	def on_private_message(self, nick, msg, tim):
+	def on_private_message(self, nick, msg, tim, xhtml):
 		# Do we have a queue?
 		fjid = self.room_jid + '/' + nick
 		no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
@@ -446,11 +455,11 @@ class GroupchatControl(ChatControlBase):
 		# We print if window is opened
 		pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
 		if pm_control:
-			pm_control.print_conversation(msg, tim = tim)
+			pm_control.print_conversation(msg, tim = tim, xhtml = xhtml)
 			return
 
 		event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
-			False, '', None))
+			False, '', None, xhtml))
 		gajim.events.add_event(self.account, fjid, event)
 
 		autopopup = gajim.config.get('autopopup')
@@ -458,7 +467,7 @@ class GroupchatControl(ChatControlBase):
 		iter = self.get_contact_iter(nick)
 		path = self.list_treeview.get_model().get_path(iter)
 		if not autopopup or (not autopopupaway and \
-					gajim.connections[self.account].connected > 2):
+		gajim.connections[self.account].connected > 2):
 			if no_queue: # We didn't have a queue: we change icons
 				model = self.list_treeview.get_model()
 				state_images =\
@@ -467,6 +476,7 @@ class GroupchatControl(ChatControlBase):
 				image = state_images['message']
 				model[iter][C_IMG] = image
 			self.parent_win.show_title()
+			self.parent_win.redraw_tab(self)
 		else:
 			self._start_private_message(nick)
 		# Scroll to line
@@ -499,7 +509,26 @@ class GroupchatControl(ChatControlBase):
 	gc_count_nicknames_colors = 0
 	gc_custom_colors = {}  
 
-	def print_conversation(self, text, contact = '', tim = None):
+	def print_old_conversation(self, text, contact = '', tim = None,
+	xhtml = None):
+		if isinstance(text, str):
+			text = unicode(text, 'utf-8')
+		if contact:
+			if contact == self.nick: # it's us
+				kind = 'outgoing'
+			else:
+				kind = 'incoming'
+		else:
+			kind = 'status'
+		if gajim.config.get('restored_messages_small'):
+			small_attr = ['small']
+		else:
+			small_attr = []
+		ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
+			small_attr, small_attr + ['restored_message'],
+			small_attr + ['restored_message'], xhtml = xhtml)
+
+	def print_conversation(self, text, contact = '', tim = None, xhtml = None):
 		'''Print a line in the conversation:
 		if contact is set: it's a message from someone or an info message (contact
 		= 'info' in such a case)
@@ -553,11 +582,16 @@ class GroupchatControl(ChatControlBase):
 			self.check_and_possibly_add_focus_out_line()
 
 		ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
-			other_tags_for_name, [], other_tags_for_text)
+			other_tags_for_name, [], other_tags_for_text, xhtml = xhtml)
 
 	def get_nb_unread(self):
 		nb = len(gajim.events.get_events(self.account, self.room_jid,
 			['printed_gc_msg']))
+		nb += self.get_nb_unread_pm()
+		return nb
+
+	def get_nb_unread_pm(self):
+		nb = 0
 		for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
 			nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \
 				nick, ['pm']))
@@ -570,8 +604,7 @@ class GroupchatControl(ChatControlBase):
 
 		# Do we play a sound on every muc message?
 		if gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
-			if gajim.config.get('notify_on_all_muc_messages'):
-				sound = 'received'
+			sound = 'received'
 
 		# Are any of the defined highlighting words in the text?
 		if self.needs_visual_notification(text):
@@ -622,13 +655,16 @@ class GroupchatControl(ChatControlBase):
 						word[char_position:char_position+1]
 					if (refer_to_nick_char != ''):
 						refer_to_nick_char_code = ord(refer_to_nick_char)
-						if ((refer_to_nick_char_code < 65 or refer_to_nick_char_code > 123)\
-							or (refer_to_nick_char_code < 97 and refer_to_nick_char_code > 90)):
+						if ((refer_to_nick_char_code < 65 or \
+						refer_to_nick_char_code > 123) or \
+						(refer_to_nick_char_code < 97 and \
+						refer_to_nick_char_code > 90)):
 							return True
 						else: 
-							# This is A->Z or a->z, we can be sure our nick is the beginning
-							# of a real word, do not highlight. Note that we can probably 
-							# do a better detection of non-punctuation characters
+							# This is A->Z or a->z, we can be sure our nick is the
+							# beginning of a real word, do not highlight. Note that we
+							# can probably do a better detection of non-punctuation
+							# characters
 							return False
 					else: # Special word == word, no char after in word
 						return True 
@@ -638,7 +674,7 @@ class GroupchatControl(ChatControlBase):
 		self.subject = subject
 
 		self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
-		subject = gtkgui_helpers.reduce_chars_newlines(subject, max_lines = 2)
+		subject = helpers.reduce_chars_newlines(subject, max_lines = 2)
 		subject = gtkgui_helpers.escape_for_pango_markup(subject)
 		font_attrs, font_attrs_small = self.get_font_attrs()
 		text = '<span %s>%s</span>' % (font_attrs, self.room_jid)
@@ -646,11 +682,10 @@ class GroupchatControl(ChatControlBase):
 			text += '\n<span %s>%s</span>' % (font_attrs_small, subject)
 		self.name_label.set_markup(text)
 		event_box = self.name_label.get_parent()
-		if subject == '':
-			self.subject = _('This room has no subject')
 
-		# tooltip must always hold ALL the subject
-		self.subject_tooltip.set_tip(event_box, self.subject)
+		if self.subject:
+			# tooltip must always hold ALL the subject
+			self.subject_tooltip.set_tip(event_box, self.subject)
 
 	def got_connected(self):
 		gajim.gc_connected[self.account][self.room_jid] = True
@@ -677,7 +712,8 @@ class GroupchatControl(ChatControlBase):
 						gc_contact.affiliation, gc_contact.status,
 						gc_contact.jid)
 
-	def on_send_pm(self, widget=None, model=None, iter=None, nick=None, msg=None):
+	def on_send_pm(self, widget = None, model = None, iter = None, nick = None,
+	msg = None):
 		'''opens a chat window and msg is not None sends private message to a
 		contact in a room'''
 		if nick is None:
@@ -686,14 +722,16 @@ class GroupchatControl(ChatControlBase):
 
 		self._start_private_message(nick)
 		if msg:
-			gajim.interface.msg_win_mgr.get_control(fjid, self.account).send_message(msg)
+			gajim.interface.msg_win_mgr.get_control(fjid, self.account).\
+				send_message(msg)
 
 	def draw_contact(self, nick, selected=False, focus=False):
 		iter = self.get_contact_iter(nick)
 		if not iter:
 			return
 		model = self.list_treeview.get_model()
-		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+			nick)
 		state_images = gajim.interface.roster.jabber_state_images['16']
 		if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
 			image = state_images['message']
@@ -706,7 +744,7 @@ class GroupchatControl(ChatControlBase):
 		if status and gajim.config.get('show_status_msgs_in_roster'):
 			status = status.strip()
 			if status != '':
-				status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 1)
+				status = helpers.reduce_chars_newlines(status, max_lines = 1)
 				# escape markup entities and make them small italic and fg color
 				color = gtkgui_helpers._get_fade_color(self.list_treeview,
 					selected, focus)
@@ -720,6 +758,8 @@ class GroupchatControl(ChatControlBase):
 	def draw_avatar(self, nick):
 		model = self.list_treeview.get_model()
 		iter = self.get_contact_iter(nick)
+		if not iter:
+			return
 		if gajim.config.get('show_avatars_in_roster'):
 			pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.room_jid + \
 				'/' + nick, True)
@@ -731,8 +771,8 @@ class GroupchatControl(ChatControlBase):
 			scaled_pixbuf = None
 		model[iter][C_AVATAR] = scaled_pixbuf
 
-	def chg_contact_status(self, nick, show, status, role, affiliation, jid, reason, actor,
-				statusCode, new_nick):
+	def chg_contact_status(self, nick, show, status, role, affiliation, jid,
+	reason, actor, statusCode, new_nick):
 		'''When an occupant changes his or her status'''
 		if show == 'invisible':
 			return
@@ -824,7 +864,8 @@ class GroupchatControl(ChatControlBase):
 					self.add_contact_to_roster(nick, show, role,
 						affiliation, status, jid)
 				else:
-					c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+					c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+						nick)
 					if c.show == show and c.status == status and \
 						c.affiliation == affiliation: #no change
 						return
@@ -841,20 +882,22 @@ class GroupchatControl(ChatControlBase):
 			print_status = None
 			for bookmark in gajim.connections[self.account].bookmarks:
 				if bookmark['jid'] == self.room_jid:
-					print_status = bookmark['print_status']
+					print_status = bookmark.get('print_status', None)
 					break
-			if print_status is None:
+			if not print_status:
 				print_status = gajim.config.get('print_status_in_muc')
 			nick_jid = nick
 			if jid:
-				nick_jid += ' (%s)' % jid
+				# delete ressource
+				simple_jid = gajim.get_jid_without_resource(jid)
+				nick_jid += ' (%s)' % simple_jid
 			if show == 'offline' and print_status in ('all', 'in_and_out'):
 				st = _('%s has left') % nick_jid
 				if reason:
 					st += ' [%s]' % reason
 			else:
 				if newly_created and print_status in ('all', 'in_and_out'):
-					st = _('%s has joined the room') % nick_jid
+					st = _('%s has joined the group chat') % nick_jid
 				elif print_status == 'all':
 					st = _('%s is now %s') % (nick_jid, helpers.get_uf_show(show))
 			if st:
@@ -881,9 +924,9 @@ class GroupchatControl(ChatControlBase):
 		role_iter = self.get_role_iter(role)
 		if not role_iter:
 			role_iter = model.append(None,
-				(gajim.interface.roster.jabber_state_images['16']['closed'], 'role',
-				role, '<b>%s</b>' % role_name, None))
-		iter = model.append(role_iter, (None, 'contact', nick, name, None))
+				(gajim.interface.roster.jabber_state_images['16']['closed'], role, 
+				'role', '<b>%s</b>' % role_name,  None))
+		iter = model.append(role_iter, (None, nick, 'contact', name, None))
 		if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
 			gc_contact = gajim.contacts.create_gc_contact(room_jid = self.room_jid,
 				name = nick, show = show, status = status, role = role,
@@ -892,7 +935,7 @@ class GroupchatControl(ChatControlBase):
 		self.draw_contact(nick)
 		self.draw_avatar(nick)
 		# Do not ask avatar to irc rooms as irc transports reply with messages
-		r, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid)
+		server = gajim.get_server_from_jid(self.room_jid)
 		if gajim.config.get('ask_avatars_on_startup') and \
 		not server.startswith('irc'):
 			fjid = self.room_jid + '/' + nick
@@ -925,7 +968,8 @@ class GroupchatControl(ChatControlBase):
 		iter = self.get_contact_iter(nick)
 		if not iter:
 			return
-		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+			nick)
 		if gc_contact:
 			gajim.contacts.remove_gc_contact(self.account, gc_contact)
 		parent_iter = model.iter_parent(iter)
@@ -1005,8 +1049,10 @@ class GroupchatControl(ChatControlBase):
 				new_topic = message_array.pop(0)
 				gajim.connections[self.account].send_gc_subject(self.room_jid,
 					new_topic)
-			else:
+			elif self.subject is not '':
 				self.print_conversation(self.subject, 'info')
+			else:
+				self.print_conversation(_('This group chat has no subject'), 'info')
 			self.clear(self.msg_textview)
 			return True
 		elif command == 'invite':
@@ -1034,15 +1080,13 @@ class GroupchatControl(ChatControlBase):
 		elif command == 'join':
 			# example: /join room@conference.example.com/nick
 			if len(message_array):
-				message_array = message_array[0]
-				if message_array.find('@') >= 0:
-					room, servernick = message_array.split('@')
-					if servernick.find('/') >= 0:
-						server, nick = servernick.split('/', 1)
+				room_jid = message_array[0]
+				if room_jid.find('@') >= 0:
+					if room_jid.find('/') >= 0:
+						room_jid, nick = room_jid.split('/', 1)
 					else:
-						server = servernick
 						nick = ''
-					#join_gc window is needed in order to provide for password entry.
+					# join_gc window is needed in order to provide for password entry.
 					if gajim.interface.instances[self.account].has_key('join_gc'):
 						gajim.interface.instances[self.account]['join_gc'].\
 							window.present()
@@ -1050,13 +1094,13 @@ class GroupchatControl(ChatControlBase):
 						try:
 							gajim.interface.instances[self.account]['join_gc'] =\
 								dialogs.JoinGroupchatWindow(self.account,
-									server = server, room = room, nick = nick)
-						except RuntimeError:
+									room_jid = room_jid, nick = nick)
+						except GajimGeneralException:
 							pass
 					self.clear(self.msg_textview)
 				else:
 					#%s is something the user wrote but it is not a jid so we inform
-					s = _('%s does not appear to be a valid JID') % message_array
+					s = _('%s does not appear to be a valid JID') % message_array[0]
 					self.print_conversation(s, 'info')
 			else:
 				self.get_command_help(command)
@@ -1066,21 +1110,21 @@ class GroupchatControl(ChatControlBase):
 			reason = 'offline'
 			if len(message_array):
 				reason = message_array.pop(0)
-			self.parent_win.remove_tab(self,reason)
+			self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND, reason)
 			self.clear(self.msg_textview)
 			return True
 		elif command == 'ban':
 			if len(message_array):
 				message_array = message_array[0].split()
 				nick = message_array.pop(0)
-				room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+				room_nicks = gajim.contacts.get_nick_list(self.account,
+					self.room_jid)
 				reason = ' '.join(message_array)
 				if nick in room_nicks:
-					ban_jid = gajim.construct_fjid(self.room_jid, nick)
-					gajim.connections[self.account].gc_set_affiliation(self.room_jid,
-						ban_jid, 'outcast', reason)
-					self.clear(self.msg_textview)
-				elif nick.find('@') >= 0:
+					gc_contact = gajim.contacts.get_gc_contact(self.account,
+						self.room_jid, nick)
+					nick = gc_contact.jid
+				if nick.find('@') >= 0:
 					gajim.connections[self.account].gc_set_affiliation(self.room_jid,
 						nick, 'outcast', reason)
 					self.clear(self.msg_textview)
@@ -1094,7 +1138,8 @@ class GroupchatControl(ChatControlBase):
 			if len(message_array):
 				message_array = message_array[0].split()
 				nick = message_array.pop(0)
-				room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+				room_nicks = gajim.contacts.get_nick_list(self.account,
+					self.room_jid)
 				reason = ' '.join(message_array)
 				if nick in room_nicks:
 					gajim.connections[self.account].gc_set_role(self.room_jid, nick,
@@ -1154,7 +1199,8 @@ class GroupchatControl(ChatControlBase):
 
 			if not self._process_command(message):
 				# Send the message
-				gajim.connections[self.account].send_gc_message(self.room_jid, message)
+				gajim.connections[self.account].send_gc_message(self.room_jid,
+					message)
 				self.msg_textview.get_buffer().set_text('')
 				self.msg_textview.grab_focus()
 
@@ -1162,10 +1208,11 @@ class GroupchatControl(ChatControlBase):
 		if command == 'help':
 			self.print_conversation(_('Commands: %s') % self.muc_cmds, 'info')
 		elif command == 'ban':
-			s = _('Usage: /%s <nickname|JID> [reason], bans the JID from the room.'
+			s = _('Usage: /%s <nickname|JID> [reason], bans the JID from the group chat.'
 				' The nickname of an occupant may be substituted, but not if it '
-				'contains "@". If the JID is currently in the room, he/she/it will '
-				'also be kicked. Does NOT support spaces in nickname.') % command
+				'contains "@". If the JID is currently in the group chat, '
+				'he/she/it will also be kicked. Does NOT support spaces in '
+				'nickname.') % command
 			self.print_conversation(s, 'info')
 		elif command == 'chat' or command == 'query':
 			self.print_conversation(_('Usage: /%s <nickname>, opens a private chat'
@@ -1177,10 +1224,11 @@ class GroupchatControl(ChatControlBase):
 			self.print_conversation(_('Usage: /%s [reason], closes the current '
 				'window or tab, displaying reason if specified.') % command, 'info')
 		elif command == 'compact':
-			self.print_conversation(_('Usage: /%s, hide the chat buttons.') % command, 'info')
+			self.print_conversation(_('Usage: /%s, hide the chat buttons.') % \
+				command, 'info')
 		elif command == 'invite':
 			self.print_conversation(_('Usage: /%s <JID> [reason], invites JID to '
-				'the current room, optionally providing a reason.') % command,
+				'the current group chat, optionally providing a reason.') % command,
 				'info')
 		elif command == 'join':
 			self.print_conversation(_('Usage: /%s <room>@<server>[/nickname], '
@@ -1188,12 +1236,12 @@ class GroupchatControl(ChatControlBase):
 				% command, 'info')
 		elif command == 'kick':
 			self.print_conversation(_('Usage: /%s <nickname> [reason], removes '
-				'the occupant specified by nickname from the room and optionally '
-				'displays a reason. Does NOT support spaces in nickname.') % \
-				command, 'info')
+				'the occupant specified by nickname from the group chat and '
+				'optionally displays a reason. Does NOT support spaces in '
+				'nickname.') % command, 'info')
 		elif command == 'me':
 			self.print_conversation(_('Usage: /%s <action>, sends action to the '
-				'current room. Use third person. (e.g. /%s explodes.)') % \
+				'current group chat. Use third person. (e.g. /%s explodes.)') % \
 				(command, command), 'info')
 		elif command == 'msg':
 			s = _('Usage: /%s <nickname> [message], opens a private message window'
@@ -1201,16 +1249,16 @@ class GroupchatControl(ChatControlBase):
 				command
 			self.print_conversation(s, 'info')
 		elif command == 'nick':
-			s = _('Usage: /%s <nickname>, changes your nickname in current room.')\
-				% command
+			s = _('Usage: /%s <nickname>, changes your nickname in current group '
+				'chat.')	% command
 			self.print_conversation(s, 'info')
 		elif command == 'names':
-			s = _('Usage: /%s , display the names of room occupants.')\
+			s = _('Usage: /%s , display the names of group chat occupants.')\
 				% command
 			self.print_conversation(s, 'info')
 		elif command == 'topic':
 			self.print_conversation(_('Usage: /%s [topic], displays or updates the'
-				' current room topic.') % command, 'info')
+				' current group chat topic.') % command, 'info')
 		elif command == 'say':
 			self.print_conversation(_('Usage: /%s <message>, sends a message '
 				'without looking for other commands.') % command, 'info')
@@ -1218,7 +1266,8 @@ class GroupchatControl(ChatControlBase):
 			self.print_conversation(_('No help info for /%s') % command, 'info')
 
 	def get_role(self, nick):
-		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+			nick)
 		if gc_contact:
 			return gc_contact.role
 		else:
@@ -1249,7 +1298,13 @@ class GroupchatControl(ChatControlBase):
 				self.handlers[i].disconnect(i)
 			del self.handlers[i]
 
-	def allow_shutdown(self):
+	def allow_shutdown(self, method):
+		'''If check_selection is True, '''
+		if method == self.parent_win.CLOSE_ESC:
+			model, iter = self.list_treeview.get_selection().get_selected()
+			if iter:
+				self.list_treeview.get_selection().unselect_all()
+				return False
 		retval = True
 		includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
 		excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
@@ -1257,9 +1312,10 @@ class GroupchatControl(ChatControlBase):
 		if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \
 		and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\
 		in excludes:
-			pritext = _('Are you sure you want to leave room "%s"?') % self.name
+			pritext = _('Are you sure you want to leave group chat "%s"?')\
+				% self.name
 			sectext = _('If you close this window, you will be disconnected '
-					'from this room.')
+					'from this group chat.')
 
 			dialog = dialogs.ConfirmationDialogCheck(pritext, sectext,
 						_('Do _not ask me again'))
@@ -1275,6 +1331,7 @@ class GroupchatControl(ChatControlBase):
 		return retval
 
 	def set_control_active(self, state):
+		self.conv_textview.allow_focus_out_line = True
 		self.attention_flag = False
 		ChatControlBase.set_control_active(self, state)
 		if not state:
@@ -1299,7 +1356,8 @@ class GroupchatControl(ChatControlBase):
 			_('Please specify the new subject:'), self.subject)
 		response = instance.get_response()
 		if response == gtk.RESPONSE_OK:
-			# Note, we don't update self.subject since we don't know whether it will work yet
+			# Note, we don't update self.subject since we don't know whether it
+			# will work yet
 			subject = instance.input_entry.get_text().decode('utf-8')
 			gajim.connections[self.account].send_gc_subject(self.room_jid, subject)
 
@@ -1324,15 +1382,14 @@ class GroupchatControl(ChatControlBase):
 			'jid': self.room_jid,
 			'autojoin': '0',
 			'password': '',
-			'nick': self.nick,
-			'print_status' : gajim.config.get('print_status_in_muc')
+			'nick': self.nick
 		}
 
 		for bookmark in gajim.connections[self.account].bookmarks:
 			if bookmark['jid'] == bm['jid']:
 				dialogs.ErrorDialog(
 					_('Bookmark already set'),
-					_('Room "%s" is already in your bookmarks.') % bm['jid'])
+					_('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
 				return
 
 		gajim.connections[self.account].bookmarks.append(bm)
@@ -1344,7 +1401,8 @@ class GroupchatControl(ChatControlBase):
 				_('Bookmark has been added successfully'),
 				_('You can manage your bookmarks via Actions menu in your roster.'))
 
-	def handle_message_textview_mykey_press(self, widget, event_keyval, event_keymod):
+	def handle_message_textview_mykey_press(self, widget, event_keyval,
+	event_keymod):
 		# NOTE: handles mykeypress which is custom signal connected to this
 		# CB in new_room(). for this singal see message_textview.py
 
@@ -1356,17 +1414,30 @@ class GroupchatControl(ChatControlBase):
 
 		message_buffer = widget.get_buffer()
 		start_iter, end_iter = message_buffer.get_bounds()
-		message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
+		message = message_buffer.get_text(start_iter, end_iter, False).decode(
+			'utf-8')
 
 		if event.keyval == gtk.keysyms.Tab: # TAB
 			cursor_position = message_buffer.get_insert()
 			end_iter = message_buffer.get_iter_at_mark(cursor_position)
-			text = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
-			if text.endswith(' '):
-				if not self.last_key_tabs:
-					return False
+			text = message_buffer.get_text(start_iter, end_iter, False).decode(
+				'utf-8')
 
 			splitted_text = text.split()
+			# topic completion
+			splitted_text2 = text.split(None, 1)
+			if text.startswith('/topic '):
+				if len(splitted_text2) == 2 and \
+					    self.subject.startswith(splitted_text2[1]) and\
+					    len(self.subject) > len(splitted_text2[1]):
+					message_buffer.insert_at_cursor(
+						self.subject[len(splitted_text2[1]):])
+					return True
+				elif len(splitted_text2) == 1 and text.startswith('/topic  '):
+					message_buffer.delete(start_iter, end_iter)
+					message_buffer.insert_at_cursor('/topic '+self.subject)
+					return True
+
 			# command completion
 			if text.startswith('/') and len(splitted_text) == 1:
 				text = splitted_text[0]
@@ -1435,7 +1506,11 @@ class GroupchatControl(ChatControlBase):
 
 	def on_list_treeview_key_press_event(self, widget, event):
 		if event.keyval == gtk.keysyms.Escape:
-			widget.get_selection().unselect_all()
+			selection = widget.get_selection()
+			model, iter = selection.get_selected()
+			if iter:
+				widget.get_selection().unselect_all()
+				return True
 
 	def on_list_treeview_row_expanded(self, widget, iter, path):
 		'''When a row is expanded: change the icon of the arrow'''
@@ -1473,8 +1548,8 @@ class GroupchatControl(ChatControlBase):
 
 		# looking for user's affiliation and role
 		user_nick = self.nick
-		user_affiliation = gajim.contacts.get_gc_contact(self.account, self.room_jid,
-			user_nick).affiliation
+		user_affiliation = gajim.contacts.get_gc_contact(self.account,
+			self.room_jid, user_nick).affiliation
 		user_role = self.get_role(user_nick)
 
 		# making menu from glade
@@ -1483,9 +1558,10 @@ class GroupchatControl(ChatControlBase):
 		# these conditions were taken from JEP 0045
 		item = xml.get_widget('kick_menuitem')
 		if user_role != 'moderator' or \
-			(user_affiliation == 'admin' and target_affiliation == 'owner') or \
-			(user_affiliation == 'member' and target_affiliation in ('admin', 'owner')) or \
-			(user_affiliation == 'none' and target_affiliation != 'none'):
+		(user_affiliation == 'admin' and target_affiliation == 'owner') or \
+		(user_affiliation == 'member' and target_affiliation in ('admin',
+		'owner')) or (user_affiliation == 'none' and target_affiliation != \
+		'none'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.kick, nick)
 		self.handlers[id] = item
@@ -1493,18 +1569,18 @@ class GroupchatControl(ChatControlBase):
 		item = xml.get_widget('voice_checkmenuitem')
 		item.set_active(target_role != 'visitor')
 		if user_role != 'moderator' or \
-			user_affiliation == 'none' or \
-			(user_affiliation=='member' and target_affiliation!='none') or \
-			target_affiliation in ('admin', 'owner'):
+		user_affiliation == 'none' or \
+		(user_affiliation=='member' and target_affiliation!='none') or \
+		target_affiliation in ('admin', 'owner'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.on_voice_checkmenuitem_activate, 
-					nick)
+			nick)
 		self.handlers[id] = item
 
 		item = xml.get_widget('moderator_checkmenuitem')
 		item.set_active(target_role == 'moderator')
 		if not user_affiliation in ('admin', 'owner') or \
-			target_affiliation in ('admin', 'owner'):
+		target_affiliation in ('admin', 'owner'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.on_moderator_checkmenuitem_activate,
 					nick)
@@ -1512,8 +1588,8 @@ class GroupchatControl(ChatControlBase):
 	
 		item = xml.get_widget('ban_menuitem')
 		if not user_affiliation in ('admin', 'owner') or \
-			(target_affiliation in ('admin', 'owner') and\
-			user_affiliation != 'owner'):
+		(target_affiliation in ('admin', 'owner') and\
+		user_affiliation != 'owner'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.ban, jid)
 		self.handlers[id] = item
@@ -1521,7 +1597,7 @@ class GroupchatControl(ChatControlBase):
 		item = xml.get_widget('member_checkmenuitem')
 		item.set_active(target_affiliation != 'none')
 		if not user_affiliation in ('admin', 'owner') or \
-			(user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
+		(user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.on_member_checkmenuitem_activate, 
 					jid)
@@ -1711,19 +1787,23 @@ class GroupchatControl(ChatControlBase):
 
 	def grant_voice(self, widget, nick):
 		'''grant voice privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'participant')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'participant')
 
 	def revoke_voice(self, widget, nick):
 		'''revoke voice privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'visitor')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'visitor')
 
 	def grant_moderator(self, widget, nick):
 		'''grant moderator privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'moderator')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'moderator')
 
 	def revoke_moderator(self, widget, nick):
 		'''revoke moderator privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'participant')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'participant')
 
 	def ban(self, widget, jid):
 		'''ban a user'''
@@ -1738,17 +1818,17 @@ class GroupchatControl(ChatControlBase):
 		else:
 			return # stop banning procedure
 		gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
-								'outcast', reason)
+			'outcast', reason)
 
 	def grant_membership(self, widget, jid):
 		'''grant membership privilege to a user'''
 		gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
-								'member')
+			'member')
 
 	def revoke_membership(self, widget, jid):
 		'''revoke membership privilege to a user'''
 		gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
-								'none')
+			'none')
 
 	def grant_admin(self, widget, jid):
 		'''grant administrative privilege to a user'''
@@ -1772,10 +1852,11 @@ class GroupchatControl(ChatControlBase):
 		c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
 		c2 = gajim.contacts.contact_from_gc_contact(c)
 		if gajim.interface.instances[self.account]['infos'].has_key(c2.jid):
-			gajim.interface.instances[self.account]['infos'][c2.jid].window.present()
+			gajim.interface.instances[self.account]['infos'][c2.jid].window.\
+				present()
 		else:
 			gajim.interface.instances[self.account]['infos'][c2.jid] = \
-				vcard.VcardWindow(c2, self.account, is_fake = True)
+				vcard.VcardWindow(c2, self.account, c)
 
 	def on_history(self, widget, nick):
 		jid = gajim.construct_fjid(self.room_jid, nick)
diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py
index 875da8ee4e7c5c0a0f89259ffa4d3b806b8269f3..7e398979dffa0adbadd16e2ea860b7c37446e70d 100644
--- a/src/gtkexcepthook.py
+++ b/src/gtkexcepthook.py
@@ -1,18 +1,7 @@
 ##	gtkexcepthook.py
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##	- Dimitur Kirov <dkirov@gmail.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ##
 ## Initially written and submitted by Gustavo J. A. M. Carneiro
 ##
@@ -33,6 +22,7 @@ import threading
 
 import gtk
 import pango
+from common import i18n
 import dialogs
 
 from cStringIO import StringIO
diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py
index fb6e3eaa850c1738eaac52fa0644757b2d0b1964..39690f7fa81addf4bf78c414fd965876ba0a6178 100644
--- a/src/gtkgui_helpers.py
+++ b/src/gtkgui_helpers.py
@@ -2,7 +2,7 @@
 ##
 ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
 ## Copyright (C) 2004-2005 Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
 ## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
 ## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
@@ -169,30 +169,6 @@ def get_default_font():
 	
 	return None
 	
-def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
-	'''Cut the chars after 'max_chars' on each line
-	and show only the first 'max_lines'.
-	If any of the params is not present (None or 0) the action
-	on it is not performed'''
-
-	def _cut_if_long(str):
-		if len(str) > max_chars:
-			str = str[:max_chars - 3] + '...'
-		return str
-	
-	if max_lines == 0:
-		lines = text.split('\n')
-	else:
-		lines = text.split('\n', max_lines)[:max_lines]
-	if max_chars > 0:
-		if lines:
-			lines = map(lambda e: _cut_if_long(e), lines)
-	if lines:
-		reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines)
-	else:
-		reduced_text = ''
-	return reduced_text
-
 def escape_for_pango_markup(string):
 	# escapes < > & ' "
 	# for pango markup not to break
@@ -207,7 +183,30 @@ def escape_for_pango_markup(string):
 	return escaped_str
 
 def autodetect_browser_mailer():
-	# recognize the environment for appropriate browser/mailer
+	# recognize the environment and set appropriate browser/mailer
+	if user_runs_gnome():
+		gajim.config.set('openwith', 'gnome-open')
+	elif user_runs_kde():
+		gajim.config.set('openwith', 'kfmclient exec')
+	elif user_runs_xfce():
+		gajim.config.set('openwith', 'exo-open')
+	else:
+		gajim.config.set('openwith', 'custom')
+
+def user_runs_gnome():
+	return 'gnome-session' in get_running_processes()
+
+def user_runs_kde():
+	return 'startkde' in get_running_processes()
+
+def user_runs_xfce():
+	procs = get_running_processes()
+	if 'startxfce4' in procs or 'xfce4-session' in procs:
+		return True
+	return False
+
+def get_running_processes():
+	'''returns running processes or None (if not /proc exists)'''
 	if os.path.isdir('/proc'):
 		# under Linux: checking if 'gnome-session' or
 		# 'startkde' programs were run before gajim, by
@@ -237,12 +236,9 @@ def autodetect_browser_mailer():
 
 		# list of processes
 		processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files]
-		if 'gnome-session' in processes:
-			gajim.config.set('openwith', 'gnome-open')
-		elif 'startkde' in processes:
-			gajim.config.set('openwith', 'kfmclient exec')
-		else:
-			gajim.config.set('openwith', 'custom')
+		
+		return processes
+	return []
 
 def move_window(window, x, y):
 	'''moves the window but also checks if out of screen'''
@@ -390,7 +386,7 @@ def possibly_move_window_in_current_desktop(window):
 	current virtual desktop
 	window is GTK window'''
 	if os.name == 'nt':
-		return
+		return False
 
 	root_window = gtk.gdk.screen_get_default().get_root_window()
 	# current user's vd
@@ -406,6 +402,8 @@ def possibly_move_window_in_current_desktop(window):
 			# we are in another VD that the window was
 			# so show it in current VD
 			window.present()
+			return True
+	return False
 
 def file_is_locked(path_to_file):
 	'''returns True if file is locked (WINDOWS ONLY)'''
@@ -680,6 +678,14 @@ default_name = ''):
 		file_path = dialog.get_filename()
 		file_path = decode_filechooser_file_paths((file_path,))[0]
 		if os.path.exists(file_path):
+			# check if we have write permissions
+			if not os.access(file_path, os.W_OK):
+				file_name = os.path.basename(file_path)
+				dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % 
+					file_name),
+				_('A file with this name already exists and you do not have '
+				'permission to overwrite it.'))
+				return
 			dialog2 = dialogs.FTOverwriteConfirmationDialog(
 				_('This file already exists'), _('What do you want to do?'),
 				False)
@@ -688,6 +694,13 @@ default_name = ''):
 			response = dialog2.get_response()
 			if response < 0:
 				return
+		else:
+			dirname = os.path.dirname(file_path)
+			if not os.access(dirname, os.W_OK):
+				dialogs.ErrorDialog(_('Directory "%s" is not writable') % \
+				dirname, _('You do not have permission to create files in this'
+				' directory.'))
+				return
 
 		# Get pixbuf
 		pixbuf = None
@@ -710,8 +723,8 @@ default_name = ''):
 		try:
 			pixbuf.save(file_path, type_)
 		except:
-			#XXX Check for permissions
-			os.remove(file_path)
+			if os.path.exists(file_path):
+				os.remove(file_path)
 			new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
 			dialog2 = dialogs.ConfirmationDialog(_('Extension not supported'),
 				_('Image cannot be saved in %(type)s format. Save as %(new_filename)s?') % {'type': type_, 'new_filename': new_file_path},
@@ -735,3 +748,6 @@ default_name = ''):
 	dialog.set_current_name(default_name)
 	dialog.connect('delete-event', lambda widget, event:
 		on_cancel(widget))
+
+def on_bm_header_changed_state(widget, event):
+	widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
diff --git a/src/history_manager.py b/src/history_manager.py
index 21d74491f81a84b0028fa9f64ec4b6d38e048c07..bde306c32639ebbb7d3503d62a7b45da4a4a34e9 100755
--- a/src/history_manager.py
+++ b/src/history_manager.py
@@ -1,7 +1,4 @@
-#!/bin/sh
-''':'
-exec python -OOt "$0" ${1+"$@"}
-' '''
+#!/usr/bin/env python
 ## history_manager.py
 ##
 ## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
@@ -33,6 +30,9 @@ import dialogs
 import gtkgui_helpers
 from common.logger import LOG_DB_PATH, constants
 
+#FIXME: constants should implement 2 way mappings 
+status = dict((constants.__dict__[i], i[5:].lower()) for i in \
+	constants.__dict__.keys() if i.startswith('SHOW_')) 
 from common import gajim
 from common import helpers
 
@@ -44,14 +44,17 @@ C_SUBJECT,
 C_NICKNAME
 ) = range(2, 6)
 
+
 try:
-	from pysqlite2 import dbapi2 as sqlite
+	import sqlite3 as sqlite # python 2.5
 except ImportError:
-	raise exceptions.PysqliteNotAvailable
+	try:
+		from pysqlite2 import dbapi2 as sqlite
+	except ImportError:
+		raise exceptions.PysqliteNotAvailable
 
 
 class HistoryManager:
-
 	def __init__(self):
 		path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
 		pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
@@ -193,7 +196,9 @@ class HistoryManager:
 		gtk.main_quit()
 	
 	def _fill_jids_listview(self):
-		self.cur.execute('SELECT jid, jid_id FROM jids ORDER BY jid')
+		# get those jids that have at least one entry in logs
+		self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN (SELECT '
+			'distinct logs.jid_id FROM logs) ORDER BY jid')
 		rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
 		for row in rows:
 			self.jids_already_in.append(row[0]) # jid
@@ -310,6 +315,7 @@ class HistoryManager:
 			except ValueError:
 				pass
 			else:
+				color = None
 				if kind in (constants.KIND_SINGLE_MSG_RECV,
 				constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG):
 					# it is the other side
@@ -325,11 +331,14 @@ class HistoryManager:
 						message = ''
 					else:
 						message = ' : ' + message 
-					message = helpers.get_uf_show(show) + message
-					
-				message = '<span foreground="%s">%s</span>' % (color,
-					gtkgui_helpers.escape_for_pango_markup(message))
-				self.logs_liststore.append((log_line_id, jid_id, time_, message,
+					message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message
+
+				message_ = '<span'
+				if color:
+					message_ += ' foreground="%s"' % color
+				message_ += '>%s</span>' % \
+					gtkgui_helpers.escape_for_pango_markup(message)
+				self.logs_liststore.append((log_line_id, jid_id, time_, message_,
 					subject, nickname))
 
 	def _fill_search_results_listview(self, text):
diff --git a/src/history_window.py b/src/history_window.py
index fba1684ff6401e0a7a9c71b134cd25439cf72487..4a9bc9ceca357f9c6fc18021c73d7f5029a0febc 100644
--- a/src/history_window.py
+++ b/src/history_window.py
@@ -8,7 +8,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -59,7 +59,8 @@ class HistoryWindow:
 		
 		self.calendar = xml.get_widget('calendar')
 		scrolledwindow = xml.get_widget('scrolledwindow')
-		self.history_textview = conversation_textview.ConversationTextview(account)
+		self.history_textview = conversation_textview.ConversationTextview(
+			account, used_in_history_window = True)
 		scrolledwindow.add(self.history_textview.tv)
 		self.history_buffer = self.history_textview.tv.get_buffer()
 		self.history_buffer.create_tag('highlight', background = 'yellow')
@@ -209,7 +210,9 @@ class HistoryWindow:
 		
 		if gajim.config.get('print_time') == 'always':
 			before_str = gajim.config.get('before_time')
+			before_str = helpers.from_one_line(before_str)
 			after_str = gajim.config.get('after_time')
+			after_str = helpers.from_one_line(after_str)
 			format = before_str + '%X' + after_str + ' '
 			tim = time.strftime(format, time.localtime(float(tim)))
 			buf.insert(end_iter, tim) # add time
@@ -277,7 +280,9 @@ class HistoryWindow:
 		if contact_name and kind != constants.KIND_GCSTATUS:
 			# add stuff before and after contact name
 			before_str = gajim.config.get('before_nickname')
+			before_str = helpers.from_one_line(before_str)
 			after_str = gajim.config.get('after_nickname')
+			after_str = helpers.from_one_line(after_str)
 			format = before_str + contact_name + after_str + ' '
 			buf.insert_with_tags_by_name(end_iter, format, tag_name)
 
diff --git a/src/htmltextview.py b/src/htmltextview.py
new file mode 100644
index 0000000000000000000000000000000000000000..378a4e2360ed97c33099d377d5fcfa19f69c7a0e
--- /dev/null
+++ b/src/htmltextview.py
@@ -0,0 +1,963 @@
+### Copyright (C) 2005 Gustavo J. A. M. Carneiro
+### Copyright (C) 2006 Santiago Gala
+###
+### This library is free software; you can redistribute it and/or
+### modify it under the terms of the GNU Lesser General Public
+### License as published by the Free Software Foundation; either
+### version 2 of the License, or (at your option) any later version.
+###
+### This library is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+### Lesser General Public License for more details.
+###
+### You should have received a copy of the GNU Lesser General Public
+### License along with this library; if not, write to the
+### Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+### Boston, MA 02111-1307, USA.
+
+
+"""
+A gtk.TextView-based renderer for XHTML-IM, as described in:
+  http://www.jabber.org/jeps/jep-0071.html
+
+Starting with the version posted by Gustavo Carneiro,
+I (Santiago Gala) am trying to make it more compatible
+with the markup that docutils generate, and also more
+modular.
+
+"""
+
+import gobject
+import pango
+import gtk
+import xml.sax, xml.sax.handler
+import re
+import warnings
+from cStringIO import StringIO
+import urllib2
+import operator
+
+from common import gajim
+#from common import i18n
+
+
+import tooltips
+
+
+__all__ = ['HtmlTextView']
+
+whitespace_rx = re.compile("\\s+")
+allwhitespace_rx = re.compile("^\\s*$")
+
+## pixels = points * display_resolution
+display_resolution = 0.3514598*(gtk.gdk.screen_height() /
+					float(gtk.gdk.screen_height_mm()))
+
+#embryo of CSS classes
+classes = {
+	#'system-message':';display: none',
+	'problematic':';color: red',
+}
+
+#styles for elemens
+element_styles = {
+		'u'			: ';text-decoration: underline',
+		'em'		: ';font-style: oblique',
+		'cite'		: '; background-color:rgb(170,190,250); font-style: oblique',
+		'li'		: '; margin-left: 1em; margin-right: 10%',
+		'strong'	: ';font-weight: bold',
+		'pre'		: '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%',
+		'kbd'		: ';background-color:rgb(210,210,210);font-family: monospace',
+		'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%',
+		'dt'		: ';font-weight: bold; font-style: oblique',
+		'dd'		: ';margin-left: 2em; font-style: oblique'
+}
+# no difference for the moment
+element_styles['dfn'] = element_styles['em']
+element_styles['var'] = element_styles['em']
+# deprecated, legacy, presentational
+element_styles['tt']  = element_styles['kbd']
+element_styles['i']   = element_styles['em']
+element_styles['b']   = element_styles['strong']
+
+class_styles = {
+}
+
+"""
+==========
+  JEP-0071
+==========
+
+This Integration Set includes a subset of the modules defined for 
+XHTML 1.0 but does not redefine any existing modules, nor 
+does it define any new modules. Specifically, it includes the 
+following modules only:
+
+- Structure
+- Text
+  
+  * Block
+    
+    phrasal
+       addr, blockquote, pre
+    Struc
+       div,p
+    Heading
+       h1, h2, h3, h4, h5, h6
+    
+  * Inline
+    
+    phrasal
+       abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var
+    structural
+       br, span
+  
+- Hypertext (a)
+- List (ul, ol, dl)
+- Image (img)
+- Style Attribute
+     
+Therefore XHTML-IM uses the following content models:
+
+  Block.mix
+            Block-like elements, e.g., paragraphs
+  Flow.mix
+            Any block or inline elements
+  Inline.mix
+            Character-level elements
+  InlineNoAnchor.class
+			Anchor element 
+  InlinePre.mix
+            Pre element
+
+XHTML-IM also uses the following Attribute Groups:
+
+Core.extra.attrib
+	TBD
+I18n.extra.attrib
+	TBD
+Common.extra
+	style
+
+
+...
+#block level:
+#Heading    h
+#           ( pres           = h1 | h2 | h3 | h4 | h5 | h6 )
+#Block      ( phrasal        = address | blockquote | pre )
+#NOT           ( presentational = hr )
+#           ( structural     = div | p )
+#other:     section
+#Inline     ( phrasal        = abbr | acronym | cite | code | dfn | em | kbd | q | samp | strong | var )
+#NOT        ( presentational =  b  | big | i | small | sub | sup | tt )
+#           ( structural     =  br | span )
+#Param/Legacy    param, font, basefont, center, s, strike, u, dir, menu, isindex
+#
+"""
+
+BLOCK_HEAD = set(( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', ))
+BLOCK_PHRASAL = set(( 'address', 'blockquote', 'pre', ))
+BLOCK_PRES = set(( 'hr', )) #not in xhtml-im
+BLOCK_STRUCT = set(( 'div', 'p', ))
+BLOCK_HACKS = set(( 'table', 'tr' )) # at the very least, they will start line ;)
+BLOCK = BLOCK_HEAD.union(BLOCK_PHRASAL).union(BLOCK_STRUCT).union(BLOCK_PRES).union(BLOCK_HACKS)
+
+INLINE_PHRASAL = set('abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var'.split(', '))
+INLINE_PRES = set('b, i, u, tt'.split(', ')) #not in xhtml-im
+INLINE_STRUCT = set('br, span'.split(', '))
+INLINE = INLINE_PHRASAL.union(INLINE_PRES).union(INLINE_STRUCT)
+
+LIST_ELEMS = set( 'dl, ol, ul'.split(', '))
+
+for name in BLOCK_HEAD:
+	num = eval(name[1])
+	size = (num-1) // 2
+	weigth = (num - 1) % 2
+	element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size],
+													('font-weight: bold', 'font-style: oblique')[weigth],
+											  )
+
+
+def build_patterns(view, config, interface):
+	#extra, rst does not mark _underline_ or /it/ up
+	#actually <b>, <i> or <u> are not in the JEP-0071, but are seen in the wild
+	basic_pattern = r'(?<!\w|\<|/|:)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\w|/|:)|'\
+					r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
+	view.basic_pattern_re = re.compile(basic_pattern)
+	#TODO: emoticons
+	emoticons_pattern = ''
+	if config.get('emoticons_theme'):
+		# When an emoticon is bordered by an alpha-numeric character it is NOT
+		# expanded.  e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
+		# We still allow multiple emoticons side-by-side like :P:P:P
+		# sort keys by length so :qwe emot is checked before :q
+		keys = interface.emoticons.keys()
+		keys.sort(interface.on_emoticon_sort)
+		emoticons_pattern_prematch = ''
+		emoticons_pattern_postmatch = ''
+		emoticon_length = 0
+		for emoticon in keys: # travel thru emoticons list
+			emoticon_escaped = re.escape(emoticon) # espace regexp metachars
+			emoticons_pattern += emoticon_escaped + '|'# | means or in regexp
+			if (emoticon_length != len(emoticon)):
+				# Build up expressions to match emoticons next to other emoticons
+				emoticons_pattern_prematch  = emoticons_pattern_prematch[:-1]  + ')|(?<='
+				emoticons_pattern_postmatch = emoticons_pattern_postmatch[:-1] + ')|(?='
+				emoticon_length = len(emoticon)
+			emoticons_pattern_prematch += emoticon_escaped  + '|'
+			emoticons_pattern_postmatch += emoticon_escaped + '|'
+		# We match from our list of emoticons, but they must either have
+		# whitespace, or another emoticon next to it to match successfully
+		# [\w.] alphanumeric and dot (for not matching 8) in (2.8))
+		emoticons_pattern = '|' + \
+			'(?:(?<![\w.]' + emoticons_pattern_prematch[:-1]   + '))' + \
+			'(?:'       + emoticons_pattern[:-1]            + ')'  + \
+			'(?:(?![\w.]'  + emoticons_pattern_postmatch[:-1]  + '))'
+
+	# because emoticons match later (in the string) they need to be after
+	# basic matches that may occur earlier
+	emot_and_basic_pattern = basic_pattern + emoticons_pattern
+	view.emot_and_basic_re = re.compile(emot_and_basic_pattern, re.IGNORECASE)
+
+
+def _parse_css_color(color):
+	'''_parse_css_color(css_color) -> gtk.gdk.Color'''
+	if color.startswith("rgb(") and color.endswith(')'):
+		r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
+		return gtk.gdk.Color(r, g, b)
+	else:
+		return gtk.gdk.color_parse(color)
+	
+
+class HtmlHandler(xml.sax.handler.ContentHandler):
+	
+	def __init__(self, textview, startiter):
+		xml.sax.handler.ContentHandler.__init__(self)
+		self.textbuf = textview.get_buffer()
+		self.textview = textview
+		self.iter = startiter
+		self.text = ''
+		self.starting=True
+		self.preserve = False
+		self.styles = [] # a gtk.TextTag or None, for each span level
+		self.list_counters = [] # stack (top at head) of list
+								# counters, or None for unordered list
+
+	def _parse_style_color(self, tag, value):
+		color = _parse_css_color(value)
+		tag.set_property("foreground-gdk", color)
+
+	def _parse_style_background_color(self, tag, value):
+		color = _parse_css_color(value)
+		tag.set_property("background-gdk", color)
+		if gtk.gtk_version >= (2, 8):
+			tag.set_property("paragraph-background-gdk", color)
+
+
+	if gtk.gtk_version >= (2, 8, 5) or gobject.pygtk_version >= (2, 8, 1):
+
+		def _get_current_attributes(self):
+			attrs = self.textview.get_default_attributes()
+			self.iter.backward_char()
+			self.iter.get_attributes(attrs)
+			self.iter.forward_char()
+			return attrs
+		
+	else:
+		
+		## Workaround http://bugzilla.gnome.org/show_bug.cgi?id=317455
+		def _get_current_style_attr(self, propname, comb_oper=None):
+			tags = [tag for tag in self.styles if tag is not None]
+			tags.reverse()
+			is_set_name = propname + "-set"
+			value = None
+			for tag in tags:
+				if tag.get_property(is_set_name):
+					if value is None:
+						value = tag.get_property(propname)
+						if comb_oper is None:
+							return value
+					else:
+						value = comb_oper(value, tag.get_property(propname))
+			return value
+
+		class _FakeAttrs(object):
+			__slots__ = ("font", "font_scale")
+
+		def _get_current_attributes(self):
+			attrs = self._FakeAttrs()
+			attrs.font_scale = self._get_current_style_attr("scale",
+															operator.mul)
+			if attrs.font_scale is None:
+				attrs.font_scale = 1.0
+			attrs.font = self._get_current_style_attr("font-desc")
+			if attrs.font is None:
+				attrs.font = self.textview.style.font_desc
+			return attrs
+
+
+	def __parse_length_frac_size_allocate(self, textview, allocation,
+										  frac, callback, args):
+		callback(allocation.width*frac, *args)
+
+	def _parse_length(self, value, font_relative, callback, *args):
+		'''Parse/calc length, converting to pixels, calls callback(length, *args)
+		when the length is first computed or changes'''
+		if value.endswith('%'):
+			frac = float(value[:-1])/100
+			if font_relative:
+				attrs = self._get_current_attributes()
+				font_size = attrs.font.get_size() / pango.SCALE
+				callback(frac*display_resolution*font_size, *args)
+			else:
+				## CSS says "Percentage values: refer to width of the closest
+				##           block-level ancestor"
+				## This is difficult/impossible to implement, so we use
+				## textview width instead; a reasonable approximation..
+				alloc = self.textview.get_allocation()
+				self.__parse_length_frac_size_allocate(self.textview, alloc,
+													   frac, callback, args)
+				self.textview.connect("size-allocate",
+									  self.__parse_length_frac_size_allocate,
+									  frac, callback, args)
+
+		elif value.endswith('pt'): # points
+			callback(float(value[:-2])*display_resolution, *args)
+
+		elif value.endswith('em'): # ems, the height of the element's font
+			attrs = self._get_current_attributes()
+			font_size = attrs.font.get_size() / pango.SCALE
+			callback(float(value[:-2])*display_resolution*font_size, *args)
+
+		elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
+			## FIXME: figure out how to calculate this correctly
+			##        for now 'em' size is used as approximation
+			attrs = self._get_current_attributes()
+			font_size = attrs.font.get_size() / pango.SCALE
+			callback(float(value[:-2])*display_resolution*font_size, *args)
+
+		elif value.endswith('px'): # pixels
+			callback(int(value[:-2]), *args)
+
+		else:
+			warnings.warn("Unable to parse length value '%s'" % value)
+		
+	def __parse_font_size_cb(length, tag):
+		tag.set_property("size-points", length/display_resolution)
+	__parse_font_size_cb = staticmethod(__parse_font_size_cb)
+
+	def _parse_style_display(self, tag, value):
+		if value == 'none':
+			tag.set_property('invisible','true')
+		#Fixme: display: block, inline
+
+	def _parse_style_font_size(self, tag, value):
+		try:
+			scale = {
+				"xx-small": pango.SCALE_XX_SMALL,
+				"x-small": pango.SCALE_X_SMALL,
+				"small": pango.SCALE_SMALL,
+				"medium": pango.SCALE_MEDIUM,
+				"large": pango.SCALE_LARGE,
+				"x-large": pango.SCALE_X_LARGE,
+				"xx-large": pango.SCALE_XX_LARGE,
+				} [value]
+		except KeyError:
+			pass
+		else:
+			attrs = self._get_current_attributes()
+			tag.set_property("scale", scale / attrs.font_scale)
+			return
+		if value == 'smaller':
+			tag.set_property("scale", pango.SCALE_SMALL)
+			return
+		if value == 'larger':
+			tag.set_property("scale", pango.SCALE_LARGE)
+			return
+		self._parse_length(value, True, self.__parse_font_size_cb, tag)
+
+	def _parse_style_font_style(self, tag, value):
+		try:
+			style = {
+				"normal": pango.STYLE_NORMAL,
+				"italic": pango.STYLE_ITALIC,
+				"oblique": pango.STYLE_OBLIQUE,
+				} [value]
+		except KeyError:
+			warnings.warn("unknown font-style %s" % value)
+		else:
+			tag.set_property("style", style)
+
+	def __frac_length_tag_cb(self,length, tag, propname):
+		styles = self._get_style_tags()
+		if styles:
+			length += styles[-1].get_property(propname)
+		tag.set_property(propname, length)
+	#__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
+		
+	def _parse_style_margin_left(self, tag, value):
+		self._parse_length(value, False, self.__frac_length_tag_cb,
+						   tag, "left-margin")
+
+	def _parse_style_margin_right(self, tag, value):
+		self._parse_length(value, False, self.__frac_length_tag_cb,
+						   tag, "right-margin")
+
+	def _parse_style_font_weight(self, tag, value):
+		## TODO: missing 'bolder' and 'lighter'
+		try:
+			weight = {
+				'100': pango.WEIGHT_ULTRALIGHT,
+				'200': pango.WEIGHT_ULTRALIGHT,
+				'300': pango.WEIGHT_LIGHT,
+				'400': pango.WEIGHT_NORMAL,
+				'500': pango.WEIGHT_NORMAL,
+				'600': pango.WEIGHT_BOLD,
+				'700': pango.WEIGHT_BOLD,
+				'800': pango.WEIGHT_ULTRABOLD,
+				'900': pango.WEIGHT_HEAVY,
+				'normal': pango.WEIGHT_NORMAL,
+				'bold': pango.WEIGHT_BOLD,
+				} [value]
+		except KeyError:
+			warnings.warn("unknown font-style %s" % value)
+		else:
+			tag.set_property("weight", weight)
+
+	def _parse_style_font_family(self, tag, value):
+		tag.set_property("family", value)
+
+	def _parse_style_text_align(self, tag, value):
+		try:
+			align = {
+				'left': gtk.JUSTIFY_LEFT,
+				'right': gtk.JUSTIFY_RIGHT,
+				'center': gtk.JUSTIFY_CENTER,
+				'justify': gtk.JUSTIFY_FILL,
+				} [value]
+		except KeyError:
+			warnings.warn("Invalid text-align:%s requested" % value)
+		else:
+			tag.set_property("justification", align)
+	
+	def _parse_style_text_decoration(self, tag, value):
+		if value == "none":
+			tag.set_property("underline", pango.UNDERLINE_NONE)
+			tag.set_property("strikethrough", False)
+		elif value == "underline":
+			tag.set_property("underline", pango.UNDERLINE_SINGLE)
+			tag.set_property("strikethrough", False)
+		elif value == "overline":
+			warnings.warn("text-decoration:overline not implemented")
+			tag.set_property("underline", pango.UNDERLINE_NONE)
+			tag.set_property("strikethrough", False)
+		elif value == "line-through":
+			tag.set_property("underline", pango.UNDERLINE_NONE)
+			tag.set_property("strikethrough", True)
+		elif value == "blink":
+			warnings.warn("text-decoration:blink not implemented")
+		else:
+			warnings.warn("text-decoration:%s not implemented" % value)
+	
+	def _parse_style_white_space(self, tag, value):
+		if value == 'pre':
+			tag.set_property("wrap_mode", gtk.WRAP_NONE)
+		elif value == 'normal':
+			tag.set_property("wrap_mode", gtk.WRAP_WORD)
+		elif value == 'nowrap':
+			tag.set_property("wrap_mode", gtk.WRAP_NONE)
+	 
+	
+	## build a dictionary mapping styles to methods, for greater speed
+	__style_methods = dict()
+	for style in ["background-color", "color", "font-family", "font-size",
+				  "font-style", "font-weight", "margin-left", "margin-right",
+				  "text-align", "text-decoration", "white-space", 'display' ]:
+		try:
+			method = locals()["_parse_style_%s" % style.replace('-', '_')]
+		except KeyError:
+			warnings.warn("Style attribute '%s' not yet implemented" % style)
+		else:
+			__style_methods[style] = method
+	del style
+	## --
+
+	def _get_style_tags(self):
+		return [tag for tag in self.styles if tag is not None]
+
+	def _create_url(self, href, title, type_, id_):
+		tag = self.textbuf.create_tag(id_)
+		if href and href[0] != '#':
+			tag.href = href
+			tag.type_ = type_ # to be used by the URL handler
+			tag.connect('event', self.textview.html_hyperlink_handler, 'url', href)
+			tag.set_property('foreground', '#0000ff')
+			tag.set_property('underline', pango.UNDERLINE_SINGLE)
+			tag.is_anchor = True
+		if title:
+			tag.title = title
+		return tag
+
+
+	def _begin_span(self, style, tag=None, id_=None):
+		if style is None:
+			self.styles.append(tag)
+			return None
+		if tag is None:
+			if id_:
+				tag = self.textbuf.create_tag(id_)
+			else:
+				tag = self.textbuf.create_tag()
+		for attr, val in [item.split(':', 1) for item in style.split(';') if len(item.strip())]:
+			attr = attr.strip().lower()
+			val = val.strip()
+			try:
+				method = self.__style_methods[attr]
+			except KeyError:
+				warnings.warn("Style attribute '%s' requested "
+							  "but not yet implemented" % attr)
+			else:
+				method(self, tag, val)
+		self.styles.append(tag)
+
+	def _end_span(self):
+		self.styles.pop()
+
+	def _jump_line(self):
+		self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol')
+		self.starting = True
+
+	def _insert_text(self, text):
+		if self.starting and text != '\n':
+			self.starting = (text[-1] == '\n')
+		tags = self._get_style_tags()
+		if tags:
+			self.textbuf.insert_with_tags(self.iter, text, *tags)
+		else:
+			self.textbuf.insert(self.iter, text)
+
+	def _starts_line(self):
+		return self.starting or self.iter.starts_line()
+		
+	def _flush_text(self):
+		if not self.text: return
+		text, self.text = self.text, ''
+		if not self.preserve:
+			text = text.replace('\n', ' ')
+			self.handle_specials(whitespace_rx.sub(' ', text))
+		else:
+			self._insert_text(text.strip("\n"))
+
+	def _anchor_event(self, tag, textview, event, iter, href, type_):
+		if event.type == gtk.gdk.BUTTON_PRESS:
+			self.textview.emit("url-clicked", href, type_)
+			return True
+		return False
+
+	def handle_specials(self, text):
+		index = 0
+		se = self.textview.config.get('show_ascii_formatting_chars')
+		if self.textview.config.get('emoticons_theme'):
+			iterator = self.textview.emot_and_basic_re.finditer(text)
+		else:
+			iterator = self.textview.basic_pattern_re.finditer(text)
+		for match in iterator:
+			start, end = match.span()
+			special_text = text[start:end]
+			if start != 0:
+				self._insert_text(text[index:start])
+			index = end # update index
+			#emoticons
+			possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
+			if self.textview.config.get('emoticons_theme') and \
+					possible_emot_ascii_caps in self.textview.interface.emoticons.keys():
+				#it's an emoticon
+				emot_ascii = possible_emot_ascii_caps
+				anchor = self.textbuf.create_child_anchor(self.iter)
+				img = gtk.Image()
+				img.set_from_file(self.textview.interface.emoticons[emot_ascii])
+				img.show()
+				# TODO: add alt/tooltip with the special_text (a11y) 
+				self.textview.add_child_at_anchor(img, anchor)
+			else:
+				# now print it
+				if special_text.startswith('/'): # it's explicit italics
+					self.startElement('i', {})
+				elif special_text.startswith('_'): # it's explicit underline
+					self.startElement("u", {})
+				if se: self._insert_text(special_text[0])
+				self.handle_specials(special_text[1:-1])
+				if se: self._insert_text(special_text[0])
+				if special_text.startswith('_'): # it's explicit underline
+					self.endElement('u')
+				if special_text.startswith('/'): # it's explicit italics
+					self.endElement('i')
+		if index < len(text):
+			self._insert_text(text[index:])
+		
+	def characters(self, content):
+		if self.preserve:
+			self.text += content
+			return
+		if allwhitespace_rx.match(content) is not None and self._starts_line():
+			return
+		self.text += content
+		self.starting = False
+
+
+	def startElement(self, name, attrs):
+		self._flush_text()
+		klass = [i for i in attrs.get('class',' ').split(' ') if i]
+		style = attrs.get('style','')
+		#Add styles defined for classes
+		#TODO: priority between class and style elements?
+		for k in klass:
+			if k  in classes:
+				style += classes[k]
+
+		tag = None
+		#FIXME: if we want to use id, it needs to be unique across
+		# the whole textview, so we need to add something like the
+		# message-id to it.
+		#id_ = attrs.get('id',None) 
+		id_ = None
+		if name == 'a':
+            #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type
+			href = attrs.get('href', None)
+			title = attrs.get('title', attrs.get('rel',href))
+			type_ = attrs.get('type', None)
+			tag = self._create_url(href, title, type_, id_)
+		elif name == 'blockquote':
+			cite = attrs.get('cite', None)
+			if cite:
+				tag = self.textbuf.create_tag(id_)
+				tag.title = title
+				tag.is_anchor = True
+		elif name in LIST_ELEMS:
+			style += ';margin-left: 2em'
+		if name in element_styles:
+			style += element_styles[name]
+
+		if style == '':
+			style = None        
+		self._begin_span(style, tag, id_)
+
+		if name == 'br':
+			pass # handled in endElement
+		elif name == 'hr':
+			pass # handled in endElement
+		elif name in BLOCK:
+			if not self._starts_line():
+				self._jump_line()
+			if name == 'pre':
+				self.preserve = True
+		elif name == 'span':
+			pass
+		elif name in ('dl', 'ul'):
+			if not self._starts_line():
+				self._jump_line()
+			self.list_counters.append(None)
+		elif name == 'ol':
+			if not self._starts_line():
+				self._jump_line()
+			self.list_counters.append(0)
+		elif name == 'li':
+			if self.list_counters[-1] is None:
+				li_head = unichr(0x2022)
+			else:
+				self.list_counters[-1] += 1
+				li_head = "%i." % self.list_counters[-1]
+			self.text = ' '*len(self.list_counters)*4 + li_head + ' '
+			self._flush_text()
+			self.starting = True
+		elif name == 'dd':
+			self._jump_line()
+		elif name == 'dt':
+			if not self.starting:
+				self._jump_line()
+		elif name == 'img':
+			try:
+				## Max image size = 10 MB (to try to prevent DoS)
+				mem = urllib2.urlopen(attrs['src']).read(10*1024*1024)
+				## Caveat: GdkPixbuf is known not to be safe to load
+				## images from network... this program is now potentially
+				## hackable ;)
+				loader = gtk.gdk.PixbufLoader()
+				loader.write(mem); loader.close()
+				pixbuf = loader.get_pixbuf()
+			except Exception, ex:
+				gajim.log.debug(str('Error loading image'+ex))
+				pixbuf = None
+				alt = attrs.get('alt', "Broken image")
+				try:
+					loader.close()
+				except: pass
+			if pixbuf is not None:
+				tags = self._get_style_tags()
+				if tags:
+					tmpmark = self.textbuf.create_mark(None, self.iter, True)
+
+				self.textbuf.insert_pixbuf(self.iter, pixbuf)
+
+				if tags:
+					start = self.textbuf.get_iter_at_mark(tmpmark)
+					for tag in tags:
+						self.textbuf.apply_tag(tag, start, self.iter)
+					self.textbuf.delete_mark(tmpmark)
+			else:
+				self._insert_text("[IMG: %s]" % alt)
+		elif name == 'body' or name == 'html':
+			pass
+		elif name == 'a':
+			pass
+		elif name in INLINE:
+			pass
+		else:
+			warnings.warn("Unhandled element '%s'" % name)
+
+	def endElement(self, name):
+		endPreserving = False
+		newLine = False
+		if name == 'br':
+			newLine = True
+		elif name == 'hr':
+			#FIXME: plenty of unused attributes (width, height,...) :)
+			self._jump_line()
+			try:
+				self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf)
+				#self._insert_text(u"\u2550"*40)
+				self._jump_line()
+			except Exception, e:
+				gajim.log.debug(str("Error in hr"+e))
+		elif name in LIST_ELEMS:
+			self.list_counters.pop()
+		elif name == 'li':
+			newLine = True
+		elif name == 'img':
+			pass
+		elif name == 'body' or name == 'html':
+			pass
+		elif name == 'a':
+			pass
+		elif name in INLINE:
+			pass
+		elif name in ('dd', 'dt', ):
+			pass
+		elif name in BLOCK:
+			if name == 'pre':
+				endPreserving = True
+		else:
+			warnings.warn("Unhandled element '%s'" % name)
+		self._flush_text()
+		if endPreserving:
+			self.preserve = False
+		if newLine:
+			self._jump_line()
+		self._end_span()
+		#if not self._starts_line():
+		#    self.text = ' '
+
+class HtmlTextView(gtk.TextView):
+	__gtype_name__ = 'HtmlTextView'
+	__gsignals__ = {
+		'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type
+	}
+	
+	def __init__(self):
+		gobject.GObject.__init__(self)
+		self.set_wrap_mode(gtk.WRAP_CHAR)
+		self.set_editable(False)
+		self._changed_cursor = False
+		self.connect("motion-notify-event", self.__motion_notify_event)
+		self.connect("leave-notify-event", self.__leave_event)
+		self.connect("enter-notify-event", self.__motion_notify_event)
+		self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL)
+		self.tooltip = tooltips.BaseTooltip()
+		self.config = gajim.config
+		self.interface = gajim.interface
+		# end big hack
+		build_patterns(self,gajim.config,gajim.interface)
+
+	def __leave_event(self, widget, event):
+		if self._changed_cursor:
+			window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+			window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+			self._changed_cursor = False
+
+	def show_tooltip(self, tag):
+		if not self.tooltip.win:
+			# check if the current pointer is still over the line
+			text = getattr(tag, 'title', False)
+			if text:
+				pointer = self.get_pointer()
+				position = self.window.get_origin()
+				win = self.get_toplevel()
+				self.tooltip.show_tooltip(text, 8, position[1] + pointer[1])
+
+	def __motion_notify_event(self, widget, event):
+		x, y, _ = widget.window.get_pointer()
+		x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
+		tags = widget.get_iter_at_location(x, y).get_tags()
+		is_over_anchor = False
+		for tag in tags:
+			if getattr(tag, 'is_anchor', False):
+				is_over_anchor = True
+				break
+		if self.tooltip.timeout != 0:
+			# Check if we should hide the line tooltip
+			if not is_over_anchor:
+				self.tooltip.hide_tooltip()
+		if not self._changed_cursor and is_over_anchor:
+			window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+			window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
+			self._changed_cursor = True
+			gobject.timeout_add(500,
+								self.show_tooltip, tag)
+		elif self._changed_cursor and not is_over_anchor:
+			window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+			window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+			self._changed_cursor = False
+		return False
+
+	def display_html(self, html):
+		buffer = self.get_buffer()
+		eob = buffer.get_end_iter()
+		## this works too if libxml2 is not available
+		# parser = xml.sax.make_parser(['drv_libxml2'])
+		# parser.setFeature(xml.sax.handler.feature_validation, True)
+		parser = xml.sax.make_parser()
+		parser.setContentHandler(HtmlHandler(self, eob))
+		parser.parse(StringIO(html))
+		
+		#if not eob.starts_line():
+		#    buffer.insert(eob, "\n")
+
+if gobject.pygtk_version < (2, 8):
+	gobject.type_register(HtmlTextView)
+
+change_cursor = None
+
+if __name__ == '__main__':
+
+	htmlview = HtmlTextView()
+
+	tooltip = tooltips.BaseTooltip()
+	def on_textview_motion_notify_event(widget, event):
+		'''change the cursor to a hand when we are over a mail or an url'''
+		global change_cursor
+		pointer_x, pointer_y, spam = htmlview.window.get_pointer()
+		x, y = htmlview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
+								   pointer_y)
+		tags = htmlview.get_iter_at_location(x, y).get_tags()
+		if change_cursor:
+			htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+					 gtk.gdk.Cursor(gtk.gdk.XTERM))
+			change_cursor = None
+		tag_table = htmlview.get_buffer().get_tag_table()
+		over_line = False
+		for tag in tags:
+			try:
+				if tag.is_anchor:
+					htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+										gtk.gdk.Cursor(gtk.gdk.HAND2))
+					change_cursor = tag
+				elif tag == tag_table.lookup('focus-out-line'):
+					over_line = True
+			except: pass
+
+		#if line_tooltip.timeout != 0:
+			# Check if we should hide the line tooltip
+		#	if not over_line:
+		#		line_tooltip.hide_tooltip()
+		#if over_line and not line_tooltip.win:
+		#	line_tooltip.timeout = gobject.timeout_add(500,
+		#		show_line_tooltip)
+		#	htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+		#		gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+		#	change_cursor = tag
+
+	htmlview.connect('motion_notify_event', on_textview_motion_notify_event)
+
+	def handler(texttag, widget, event, iter, kind, href):
+		if event.type == gtk.gdk.BUTTON_PRESS:
+			print href
+
+	htmlview.html_hyperlink_handler = handler
+
+	htmlview.display_html('<div><span style="color: red; text-decoration:underline">Hello</span><br/>\n'
+						  '  <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
+						  '  <span style="font-size: 500%; font-family: serif">World</span>\n'
+						  '</div>\n')
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	  <p style='font-size:large'>
+		<span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>, 
+		I&apos;m <span style='color:green'>green</span>
+		with <span style='font-weight: bold'>envy</span>!
+	  </p>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	  <p>As Emerson said in his essay <span style='font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
+	  <p style='margin-left: 5px; margin-right: 2%'>
+		&quot;A foolish consistency is the hobgoblin of little minds.&quot;
+	  </p>
+	</body>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	  <p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
+	  <p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
+			  alt='A License to Jabber'
+			  height='261'
+			  width='537'/></p>
+	</body>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	  <ul style='background-color:rgb(120,140,100)'>
+	   <li> One </li>
+	   <li> Two </li>
+	   <li> Three </li>
+	  </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n):
+  def faciter(n,acc): 
+	if n==0: return acc
+	return faciter(n-1, acc*n)
+  if n&lt;0: raise ValueError("Must be non-negative")
+  return faciter(n,1)</pre>
+	</body>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	 <ol style='background-color:rgb(120,140,100)'>
+	   <li> One </li>
+	   <li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
+			 <li> One </li>
+			 <li> Two </li>
+			 <li> Three </li>
+			</ul></li>
+	   <li> Three </li></ol>
+	</body>
+		""")
+	htmlview.show()
+	sw = gtk.ScrolledWindow()
+	sw.set_property("hscrollbar-policy", gtk.POLICY_AUTOMATIC)
+	sw.set_property("vscrollbar-policy", gtk.POLICY_AUTOMATIC)
+	sw.set_property("border-width", 0)
+	sw.add(htmlview)
+	sw.show()
+	frame = gtk.Frame()
+	frame.set_shadow_type(gtk.SHADOW_IN)
+	frame.show()
+	frame.add(sw)
+	w = gtk.Window()
+	w.add(frame)
+	w.set_default_size(400, 300)
+	w.show_all()
+	w.connect("destroy", lambda w: gtk.main_quit())
+	gtk.main()
diff --git a/src/message_control.py b/src/message_control.py
index 1c1b0aa50ca930e009b8eee2457e5401ce0b929b..8c9c0612c61291f5c4429c84ad175c9361137cb3 100644
--- a/src/message_control.py
+++ b/src/message_control.py
@@ -57,7 +57,7 @@ class MessageControl:
 		or inactive (state is False)'''
 		pass  # Derived types MUST implement this method
 
-	def allow_shutdown(self):
+	def allow_shutdown(self, method):
 		'''Called to check is a control is allowed to shutdown.
 		If a control is not in a suitable shutdown state this method
 		should return False'''
@@ -68,10 +68,6 @@ class MessageControl:
 		# NOTE: Derived classes MUST implement this
 		pass
 
-	def notify_on_new_messages(self):
-		# NOTE: Derived classes MUST implement this
-		return False
-
 	def repaint_themed_widgets(self, theme):
 		pass # NOTE: Derived classes SHOULD implement this
 
diff --git a/src/message_textview.py b/src/message_textview.py
index cdf21ab54154d7a46df009d9a237ebb2baa51ebb..de5e61c5f3b84a37663b79a19111a74a97f7c773 100644
--- a/src/message_textview.py
+++ b/src/message_textview.py
@@ -8,7 +8,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
diff --git a/src/message_window.py b/src/message_window.py
index f190956c8ae16831b04815411600d3c1792161dd..2ca82cef8fcc25027caa662d87e3ed5d1b17d4c0 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -4,7 +4,7 @@
 ##                         Vincent Hanquez <tab@snarc.org>
 ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
 ##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Nikos Kouremenos <kourem@gmail.com>
 ##                    Dimitur Kirov <dkirov@gmail.com>
 ##                    Travis Shirk <travis@pobox.com>
 ##                    Norman Rasmussen <norman@rasmussen.co.za>
@@ -40,6 +40,13 @@ class MessageWindow:
 	# DND_TARGETS is the targets needed by drag_source_set and drag_dest_set
 	DND_TARGETS = [('GAJIM_TAB', 0, 81)]
 	hid = 0 # drag_data_received handler id
+	(
+		CLOSE_TAB_MIDDLE_CLICK,
+		CLOSE_ESC,
+		CLOSE_CLOSE_BUTTON,
+		CLOSE_COMMAND,
+		CLOSE_CTRL_KEY
+	) = range(5)
 	
 	def __init__(self, acct, type):
 		# A dictionary of dictionaries where _contacts[account][jid] == A MessageControl
@@ -104,6 +111,16 @@ class MessageWindow:
 		self.notebook.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.DND_TARGETS,
 			gtk.gdk.ACTION_MOVE)
 
+	def change_account_name(self, old_name, new_name):
+		if self._controls.has_key(old_name):
+			self._controls[new_name] = self._controls[old_name]
+			del self._controls[old_name]
+		for ctrl in self.controls():
+			if ctrl.account == old_name:
+				ctrl.account = new_name
+		if self.account == old_name:
+			self.account = new_name
+
 	def get_num_controls(self):
 		n = 0
 		for dict in self._controls.values():
@@ -130,7 +147,7 @@ class MessageWindow:
 	def _on_window_delete(self, win, event):
 		# Make sure all controls are okay with being deleted
 		for ctrl in self.controls():
-			if not ctrl.allow_shutdown():
+			if not ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON):
 				return True # halt the delete
 		return False
 
@@ -189,7 +206,7 @@ class MessageWindow:
 			self.popup_menu(event)
 		elif event.button == 2: # middle click
 			ctrl = self._widget_to_control(child)
-			self.remove_tab(ctrl)
+			self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK)
 
 	def _on_message_textview_mykeypress_event(self, widget, event_keyval,
 		event_keymod):
@@ -214,15 +231,17 @@ class MessageWindow:
 
 	def _on_close_button_clicked(self, button, control):
 		'''When close button is pressed: close a tab'''
-		self.remove_tab(control)
+		self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
 
 	def show_title(self, urgent = True, control = None):
 		'''redraw the window's title'''
 		unread = 0
 		for ctrl in self.controls():
 			if ctrl.type_id == message_control.TYPE_GC and not \
-				gajim.config.get('notify_on_all_muc_messages') and not \
-				ctrl.attention_flag:
+			gajim.config.get('notify_on_all_muc_messages') and not \
+			ctrl.attention_flag:
+				# count only pm messages
+				unread += ctrl.get_nb_unread_pm()
 				continue
 			unread += ctrl.get_nb_unread()
 
@@ -269,10 +288,11 @@ class MessageWindow:
 		ctrl_page = self.notebook.page_num(ctrl.widget)
 		self.notebook.set_current_page(ctrl_page)
 	
-	def remove_tab(self, ctrl, reason = None):
-		'''reason is only for gc (offline status message)'''
+	def remove_tab(self, ctrl, mothod, reason = None, force = False):
+		'''reason is only for gc (offline status message)
+		if force is True, do not ask any confirmation'''
 		# Shutdown the MessageControl
-		if not ctrl.allow_shutdown():
+		if not force and not ctrl.allow_shutdown(mothod):
 			return
 		if reason is not None: # We are leaving gc with a status message
 			ctrl.shutdown(reason)
@@ -292,28 +312,22 @@ class MessageWindow:
 		if len(self._controls[ctrl.account]) == 0:
 			del self._controls[ctrl.account]
 
-		# Notify a dupicate nick to update their banner and clear account display
-		for c in self.controls():
-			if c == self:
-				continue
-			if ctrl.contact.get_shown_name() == c.contact.get_shown_name():
-				c.draw_banner()
-
-		if self.get_num_controls() == 1: # we are going from two tabs to one
-			show_tabs_if_one_tab = gajim.config.get('tabs_always_visible')
-			self.notebook.set_show_tabs(show_tabs_if_one_tab)
-			if not show_tabs_if_one_tab:
-				self.alignment.set_property('top-padding', 0)
-			self.show_title()
-		elif self.get_num_controls() == 0:
+		if self.get_num_controls() == 0:
 			# These are not called when the window is destroyed like this, fake it
 			gajim.interface.msg_win_mgr._on_window_delete(self.window, None)
 			gajim.interface.msg_win_mgr._on_window_destroy(self.window)
 			# dnd clean up
 			self.notebook.disconnect(self.hid)
 			self.notebook.drag_dest_unset()
-
 			self.window.destroy()
+			return # don't show_title, we are dead
+		elif self.get_num_controls() == 1: # we are going from two tabs to one
+			show_tabs_if_one_tab = gajim.config.get('tabs_always_visible')
+			self.notebook.set_show_tabs(show_tabs_if_one_tab)
+			if not show_tabs_if_one_tab:
+				self.alignment.set_property('top-padding', 0)
+		self.show_title()
+
 			
 	def redraw_tab(self, ctrl, chatstate = None):
 		hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0]
@@ -488,9 +502,9 @@ class MessageWindow:
 			elif event.keyval == gtk.keysyms.Tab: # CTRL + TAB
 				self.move_to_next_unread_tab(True)
 			elif event.keyval == gtk.keysyms.F4: # CTRL + F4
-				self.remove_tab(ctrl)
+				self.remove_tab(ctrl, self.CLOSE_CTRL_KEY)
 			elif event.keyval == gtk.keysyms.w: # CTRL + W
-				self.remove_tab(ctrl)
+				self.remove_tab(ctrl, self.CLOSE_CTRL_KEY)
 
 		# MOD1 (ALT) mask
 		elif event.state & gtk.gdk.MOD1_MASK:
@@ -513,7 +527,7 @@ class MessageWindow:
 		# Close tab bindings
 		elif event.keyval == gtk.keysyms.Escape and \
 				gajim.config.get('escape_key_closes'): # Escape
-			self.remove_tab(ctrl)
+			self.remove_tab(ctrl, self.CLOSE_ESC)
 		else:
 			# If the active control has a message_textview pass the event to it
 			active_ctrl = self.get_active_control()
@@ -618,7 +632,11 @@ class MessageWindowMgr:
 		# Map the mode to a int constant for frequent compares
 		mode = gajim.config.get('one_message_window')
 		self.mode = common.config.opt_one_window_types.index(mode)
-	
+
+	def change_account_name(self, old_name, new_name):
+		for win in self.windows():
+			win.change_account_name(old_name, new_name)
+
 	def _new_window(self, acct, type):
 		win = MessageWindow(acct, type)
 		# we track the lifetime of this window
diff --git a/src/music_track_listener.py b/src/music_track_listener.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cffd7a55e042e0f64769758313375e8821fc081
--- /dev/null
+++ b/src/music_track_listener.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+##	musictracklistener.py
+##
+## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro@gmail.com>
+## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+import gobject
+if __name__ == '__main__':
+	# install _() func before importing dbus_support
+	from common import i18n
+
+from common import dbus_support
+if dbus_support.supported:
+	import dbus
+	import dbus.glib
+
+class MusicTrackInfo(object):
+	__slots__ = ['title', 'album', 'artist', 'duration', 'track_number']
+
+
+class MusicTrackListener(gobject.GObject):
+	__gsignals__ = {
+		'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)),
+	}
+
+	_instance = None
+	@classmethod
+	def get(cls):
+		if cls._instance is None:
+			cls._instance = cls()
+		return cls._instance
+	
+	def __init__(self):
+		super(MusicTrackListener, self).__init__()
+		self._last_playing_music = None
+		
+		bus = dbus.SessionBus()
+
+		## Muine
+		bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
+			'org.gnome.Muine.Player')
+		bus.add_signal_receiver(self._player_name_owner_changed,
+			'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine')
+		bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged',
+			'org.gnome.Muine.Player')
+
+		## Rhythmbox
+		bus.add_signal_receiver(self._rhythmbox_music_track_change_cb,
+			'playingUriChanged', 'org.gnome.Rhythmbox.Player')
+		bus.add_signal_receiver(self._player_name_owner_changed,
+			'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
+		bus.add_signal_receiver(self._player_playing_changed_cb,
+			'playingChanged', 'org.gnome.Rhythmbox.Player')
+
+	def do_music_track_changed(self, info):
+		if info is not None:
+			self._last_playing_music = info
+
+	def _player_name_owner_changed(self, name, old, new):
+		if not new:
+			self.emit('music-track-changed', None)
+
+	def _player_playing_changed_cb(self, playing):
+		if playing:
+			self.emit('music-track-changed', self._last_playing_music)
+		else:
+			self.emit('music-track-changed', None)
+
+	def _muine_properties_extract(self, song_string):
+		d = dict((x.strip() for x in  s1.split(':', 1)) for s1 in \
+			song_string.split('\n'))
+		info = MusicTrackInfo()
+		info.title = d['title']
+		info.album = d['album']
+		info.artist = d['artist']
+		info.duration = int(d['duration'])
+		info.track_number = int(d['track_number'])
+		return info
+
+	def _muine_music_track_change_cb(self, arg):
+		info = self._muine_properties_extract(arg)
+		self.emit('music-track-changed', info)
+
+	def _rhythmbox_properties_extract(self, props):
+		info = MusicTrackInfo()
+		info.title = props['title']
+		info.album = props['album']
+		info.artist = props['artist']
+		info.duration = int(props['duration'])
+		info.track_number = int(props['track-number'])
+		return info
+	
+	def _rhythmbox_music_track_change_cb(self, uri):
+		bus = dbus.SessionBus()
+		rbshellobj = bus.get_object('org.gnome.Rhythmbox',
+			'/org/gnome/Rhythmbox/Shell')
+		rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
+		props = rbshell.getSongProperties(uri)
+		info = self._rhythmbox_properties_extract(props)
+		self.emit('music-track-changed', info)
+
+	def get_playing_track(self):
+		'''Return a MusicTrackInfo for the currently playing
+		song, or None if no song is playing'''
+
+		bus = dbus.SessionBus()
+
+		## Check Muine playing track
+		if dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
+			'org.gnome.Muine'):
+			obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player')
+			player = dbus.Interface(obj, 'org.gnome.Muine.Player')
+			if player.GetPlaying():
+				song_string = player.GetCurrentSong()
+				song = self._muine_properties_extract(song_string)
+				self._last_playing_music = song
+				return song
+
+		## Check Rhythmbox playing song
+		if dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
+			'org.gnome.Rhythmbox'):
+			rbshellobj = bus.get_object('org.gnome.Rhythmbox',
+				'/org/gnome/Rhythmbox/Shell')
+			player = dbus.Interface(
+				bus.get_object('org.gnome.Rhythmbox',
+				'/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player')
+			rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
+			uri = player.getPlayingUri()
+			props = rbshell.getSongProperties(uri)
+			info = self._rhythmbox_properties_extract(props)
+			self._last_playing_music = info
+			return info
+
+		return None
+
+# here we test :)
+if __name__ == '__main__':
+	def music_track_change_cb(listener, music_track_info):
+		if music_track_info is None:
+			print "Stop!"
+		else:
+			print music_track_info.title
+	listener = MusicTrackListener.get()
+	listener.connect('music-track-changed', music_track_change_cb)
+	track = listener.get_playing_track()
+	if track is None:
+		print 'Now not playing anything'
+	else:
+		print 'Now playing: "%s" by %s' % (track.title, track.artist)
+	gobject.MainLoop().run()
diff --git a/src/network_manager_listener.py b/src/network_manager_listener.py
new file mode 100644
index 0000000000000000000000000000000000000000..47c1a870e0f1dffef3e3c9052fd22ce9498616e0
--- /dev/null
+++ b/src/network_manager_listener.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+##	network_manager_listener.py
+## Copyright (C) 2006 Jeffrey C. Ollie <jeff at ocjtech.us>
+## Copyright (C) 2006 Stefan Bethge <stefan at lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+from common import gajim
+
+def device_now_active(self, *args):
+	for connection in gajim.connections.itervalues():
+		if gajim.config.get_per('accounts', connection.name, 'listen_to_network_manager') and gajim.config.get_per('accounts', connection.name, 'sync_with_global_status'):
+			connection.change_status('online', '')
+
+def device_no_longer_active(self, *args):
+	for connection in gajim.connections.itervalues():
+		if gajim.config.get_per('accounts', connection.name, 'listen_to_network_manager') and gajim.config.get_per('accounts', connection.name, 'sync_with_global_status'):
+			connection.change_status('offline', '')
+
+
+from common.dbus_support import system_bus
+
+import dbus
+import dbus.glib
+
+bus = system_bus.SystemBus()
+
+bus.add_signal_receiver(device_no_longer_active,
+	'DeviceNoLongerActive',
+	'org.freedesktop.NetworkManager',
+	'org.freedesktop.NetworkManager',
+	'/org/freedesktop/NetworkManager')
+
+bus.add_signal_receiver(device_now_active,
+	'DeviceNowActive',
+	'org.freedesktop.NetworkManager',
+	'org.freedesktop.NetworkManager',
+	'/org/freedesktop/NetworkManager')
+
diff --git a/src/notify.py b/src/notify.py
index 908ff25d9de2e84fe1c94ce9c2e72dd6e70192d4..c79463a7b2d651ba4737145372bf63227e73b7a1 100644
--- a/src/notify.py
+++ b/src/notify.py
@@ -4,7 +4,7 @@
 ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
 ## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
 ##
-## DBUS/libnotify connection code:
+## Notification daemon connection via D-Bus code:
 ## Copyright (C) 2005 by Sebastian Estienne
 ##
 ## This program is free software; you can redistribute it and/or modify
@@ -25,12 +25,19 @@ import gtkgui_helpers
 from common import gajim
 from common import helpers
 
-import dbus_support
+from common import dbus_support
 if dbus_support.supported:
 	import dbus
-	if dbus_support.version >= (0, 41, 0):
-		import dbus.glib
-		import dbus.service
+	import dbus.glib
+	import dbus.service
+
+
+USER_HAS_PYNOTIFY = True # user has pynotify module
+try:
+	import pynotify
+	pynotify.init('Gajim Notification')
+except ImportError:
+	USER_HAS_PYNOTIFY = False
 
 def get_show_in_roster(event, account, contact):
 	'''Return True if this event must be shown in roster, else False'''
@@ -42,27 +49,23 @@ def get_show_in_roster(event, account, contact):
 			return False
 	if event == 'message_received':
 		chat_control = helpers.get_chat_control(account, contact)
-		if not chat_control:
-			return True
-	elif event == 'ft_request':
-		return True
-	return False
+		if chat_control:
+			return False
+	return True
 
 def get_show_in_systray(event, account, contact):
-	'''Return True if this event must be shown in roster, else False'''
+	'''Return True if this event must be shown in systray, else False'''
 	num = get_advanced_notification(event, account, contact)
 	if num != None:
 		if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
 			return True
 		if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
 			return False
-	if event in ('message_received', 'ft_request', 'gc_msg_highlight',
-	'ft_request'):
-		return True
-	return False
+	return gajim.config.get('trayicon_notification_on_events')
 
 def get_advanced_notification(event, account, contact):
-	'''Returns the number of the first advanced notification or None'''
+	'''Returns the number of the first (top most)
+	advanced notification else None'''
 	num = 0
 	notif = gajim.config.get_per('notifications', str(num))
 	while notif:
@@ -98,7 +101,7 @@ def get_advanced_notification(event, account, contact):
 			if tab_opened == 'both':
 				tab_opened_ok = True
 			else:
-				chat_control = helper.get_chat_control(account, contact)
+				chat_control = helpers.get_chat_control(account, contact)
 				if (chat_control and tab_opened == 'yes') or (not chat_control and \
 				tab_opened == 'no'):
 					tab_opened_ok = True
@@ -109,17 +112,19 @@ def get_advanced_notification(event, account, contact):
 		notif = gajim.config.get_per('notifications', str(num))
 
 def notify(event, jid, account, parameters, advanced_notif_num = None):
-	'''Check what type of notifications we want, depending on basic configuration
-	of notifications and advanced one and do these notifications'''
+	'''Check what type of notifications we want, depending on basic 
+	and the advanced configuration of notifications and do these notifications;
+	advanced_notif_num holds the number of the first (top most) advanced
+	notification'''
 	# First, find what notifications we want
 	do_popup = False
 	do_sound = False
 	do_cmd = False
-	if (event == 'status_change'):
+	if event == 'status_change':
 		new_show = parameters[0]
 		status_message = parameters[1]
-		# Default : No popup for status change
-	elif  (event == 'contact_connected'):
+		# Default: No popup for status change
+	elif event == 'contact_connected':
 		status_message = parameters
 		j = gajim.get_jid_without_resource(jid)
 		server = gajim.get_server_from_jid(j)
@@ -135,43 +140,44 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 		'enabled') and not gajim.block_signed_in_notifications[account] and \
 		not block_transport:
 			do_sound = True
-	elif (event == 'contact_disconnected'):
+	elif event == 'contact_disconnected':
 		status_message = parameters
 		if helpers.allow_showing_notification(account, 'notify_on_signout'):
 			do_popup = True
 		if gajim.config.get_per('soundevents', 'contact_disconnected',
 			'enabled'):
 			do_sound = True
-	elif (event == 'new_message'):
+	elif event == 'new_message':
 		message_type = parameters[0]
-		first = parameters[1]
+		is_first_message = parameters[1]
 		nickname = parameters[2]
 		message = parameters[3]
 		if helpers.allow_showing_notification(account, 'notify_on_new_message',
-		advanced_notif_num, first):
+		advanced_notif_num, is_first_message):
 			do_popup = True
-		if first and helpers.allow_sound_notification('first_message_received',
-		advanced_notif_num):
+		if is_first_message and helpers.allow_sound_notification(
+		'first_message_received', advanced_notif_num):
 			do_sound = True
-		elif not first and helpers.allow_sound_notification(
+		elif not is_first_message and helpers.allow_sound_notification(
 		'next_message_received', advanced_notif_num):
 			do_sound = True
 	else:
 		print '*Event not implemeted yet*'
 
-	if advanced_notif_num != None and gajim.config.get_per('notifications',
+	if advanced_notif_num is not None and gajim.config.get_per('notifications',
 	str(advanced_notif_num), 'run_command'):
 		do_cmd = True
 			
 	# Do the wanted notifications	
-	if (do_popup):
-		if (event == 'contact_connected' or event == 'contact_disconnected' or \
-			event == 'status_change'): # Common code for popup for these 3 events
-			if (event == 'contact_disconnected'):
+	if do_popup:
+		if event in ('contact_connected', 'contact_disconnected',
+		'status_change'): # Common code for popup for these three events
+			if event == 'contact_disconnected':
 				show_image = 'offline.png'
 				suffix = '_notif_size_bw.png'
 			else: #Status Change or Connected
-				# TODO : for status change, we don't always 'online.png', but we 
+				# FIXME: for status change,
+				# we don't always 'online.png', but we 
 				# first need 48x48 for all status
 				show_image = 'online.png'
 				suffix = '_notif_size_colored.png'	
@@ -186,7 +192,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 						iconset, '48x48', show_image)
 			path = gtkgui_helpers.get_path_to_generic_or_avatar(img,
 				jid = jid, suffix = suffix)
-			if (event == 'status_change'):
+			if event == 'status_change':
 				title = _('%(nick)s Changed Status') % \
 					{'nick': gajim.get_name_from_jid(account, jid)}
 				text = _('%(nick)s is now %(status)s') % \
@@ -196,7 +202,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 					text =  text + " : " + status_message
 				popup(_('Contact Changed Status'), jid, account,
 					path_to_image = path, title = title, text = text)
-			elif (event == 'contact_connected'):
+			elif event == 'contact_connected':
 				title = _('%(nickname)s Signed In') % \
 					{'nickname': gajim.get_name_from_jid(account, jid)}
 				text = ''
@@ -204,7 +210,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 					text = status_message
 				popup(_('Contact Signed In'), jid, account,
 					path_to_image = path, title = title, text = text)
-			elif (event == 'contact_disconnected'):
+			elif event == 'contact_disconnected':
 				title = _('%(nickname)s Signed Out') % \
 					{'nickname': gajim.get_name_from_jid(account, jid)}
 				text = ''
@@ -212,7 +218,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 					text = status_message
 				popup(_('Contact Signed Out'), jid, account,
 					path_to_image = path, title = title, text = text)
-		elif (event == 'new_message'):
+		elif event == 'new_message':
 			if message_type == 'normal': # single message
 				event_type = _('New Single Message')
 				img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
@@ -222,10 +228,10 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 				text = message
 			elif message_type == 'pm': # private message
 				event_type = _('New Private Message')
-				room_name, t = gajim.get_room_name_and_server_from_room_jid(jid)
+				room_name = gajim.get_nick_from_jid(jid)
 				img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
 					'priv_msg_recv.png')
-				title = _('New Private Message from room %s') % room_name
+				title = _('New Private Message from group chat %s') % room_name
 				text = _('%(nickname)s: %(message)s') % {'nickname': nickname,
 					'message': message}
 			else: # chat message
@@ -239,18 +245,18 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
 			popup(event_type, jid, account, message_type,
 				path_to_image = path, title = title, text = text)
 
-	if (do_sound):
+	if do_sound:
 		snd_file = None
 		snd_event = None # If not snd_file, play the event
-		if (event == 'new_message'):
-			if advanced_notif_num != None and gajim.config.get_per('notifications',
-			str(advanced_notif_num), 'sound') == 'yes':
+		if event == 'new_message':
+			if advanced_notif_num is not None and gajim.config.get_per(
+			'notifications', str(advanced_notif_num), 'sound') == 'yes':
 				snd_file = gajim.config.get_per('notifications',
 					str(advanced_notif_num), 'sound_file')
-			elif advanced_notif_num != None and gajim.config.get_per(
+			elif advanced_notif_num is not None and gajim.config.get_per(
 			'notifications', str(advanced_notif_num), 'sound') == 'no':
 				pass # do not set snd_event
-			elif first:
+			elif is_first_message:
 				snd_event = 'first_message_received'
 			else:
 				snd_event = 'next_message_received'
@@ -276,20 +282,61 @@ def popup(event_type, jid, account, msg_type = '', path_to_image = None,
 	the older style PopupNotificationWindow method.'''
 	text = gtkgui_helpers.escape_for_pango_markup(text)
 	title = gtkgui_helpers.escape_for_pango_markup(title)
+
 	if gajim.config.get('use_notif_daemon') and dbus_support.supported:
 		try:
-			DesktopNotification(event_type, jid, account, msg_type, path_to_image,
-				title, text)
-			return
+			DesktopNotification(event_type, jid, account, msg_type,
+				path_to_image, title, text)
+			return	# sucessfully did D-Bus Notification procedure!
 		except dbus.dbus_bindings.DBusException, e:
-			# Connection to D-Bus failed, try popup
+			# Connection to D-Bus failed
 			gajim.log.debug(str(e))
 		except TypeError, e:
 			# This means that we sent the message incorrectly
 			gajim.log.debug(str(e))
-	instance = dialogs.PopupNotificationWindow(event_type, jid, account, msg_type, \
-		path_to_image, title, text)
-	gajim.interface.roster.popup_notification_windows.append(instance)
+	# we failed to speak to notification daemon via D-Bus
+	if USER_HAS_PYNOTIFY: # try via libnotify
+		if not text:
+			text = gajim.get_name_from_jid(account, jid) # default value of text
+		if not title:
+			title = event_type
+		# default image
+		if not path_to_image:
+			path_to_image = os.path.abspath(
+				os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
+					'chat_msg_recv.png')) # img to display
+		
+		
+		notification = pynotify.Notification(title, text)
+		timeout = gajim.config.get('notification_timeout') * 1000 # make it ms
+		notification.set_timeout(timeout)
+		
+		notification.set_category(event_type)
+		notification.set_data('event_type', event_type)
+		notification.set_data('jid', jid)
+		notification.set_data('account', account)
+		notification.set_data('msg_type', event_type)
+		notification.set_data('path_to_image', path_to_image)
+		notification.add_action('default', 'Default Action',
+			on_pynotify_notification_clicked)
+		
+		notification.show()
+
+	else: # go old style
+		instance = dialogs.PopupNotificationWindow(event_type, jid,
+			account, msg_type, path_to_image, title, text)
+		gajim.interface.roster.popup_notification_windows.append(
+			instance)
+
+def on_pynotify_notification_clicked(notification, action):
+	event_type = notification.get_data('event_type')
+	jid = notification.get_data('jid')
+	account = notification.get_data('account')
+	msg_type = notification.get_data('msg_type')
+	path_to_image = notification.get_data('path_to_image')
+
+	notification.close()
+	gajim.interface.handle_event(account, jid, msg_type)
 
 class NotificationResponseManager:
 	'''Collects references to pending DesktopNotifications and manages there
@@ -342,7 +389,7 @@ class NotificationResponseManager:
 notification_response_manager = NotificationResponseManager()
 
 class DesktopNotification:
-	'''A DesktopNotification that interfaces with DBus via the Desktop
+	'''A DesktopNotification that interfaces with D-Bus via the Desktop
 	Notification specification'''
 	def __init__(self, event_type, jid, account, msg_type = '',
 		path_to_image = None, title = None, text = None):
diff --git a/src/profile_window.py b/src/profile_window.py
index 41bbd8de3c329248d77205ef72de5ffb0cbab361..b5bd628095cb23d4a65be1bc544c6264b5e8a6b6 100644
--- a/src/profile_window.py
+++ b/src/profile_window.py
@@ -13,18 +13,17 @@
 ## GNU General Public License for more details.
 ##
 
+# THIS FILE IS FOR **OUR** PROFILE (when we edit our INFO)
+
 import gtk
 import gobject
 import base64
 import mimetypes
 import os
-import time
-import locale
 
 import gtkgui_helpers
 import dialogs
 
-from common import helpers
 from common import gajim
 from common.i18n import Q_
 
@@ -60,17 +59,40 @@ class ProfileWindow:
 	def __init__(self, account):
 		self.xml = gtkgui_helpers.get_glade('profile_window.glade')
 		self.window = self.xml.get_widget('profile_window')
+		self.progressbar = self.xml.get_widget('progressbar')
+		self.statusbar = self.xml.get_widget('statusbar')
+		self.context_id = self.statusbar.get_context_id('profile')
 
 		self.account = account
 		self.jid = gajim.get_jid_from_account(account)
 
 		self.avatar_mime_type = None
 		self.avatar_encoded = None
+		self.message_id = self.statusbar.push(self.context_id,
+			_('Retrieving profile...'))
+		self.update_progressbar_timeout_id = gobject.timeout_add(100,
+			self.update_progressbar)
+		self.remove_statusbar_timeout_id = None
 
+		# Create Image for avatar button
+		image = gtk.Image()
+		self.xml.get_widget('PHOTO_button').set_image(image)
 		self.xml.signal_autoconnect(self)
 		self.window.show_all()
 
+	def update_progressbar(self):
+		self.progressbar.pulse()
+		return True # loop forever
+
+	def remove_statusbar(self, message_id):
+		self.statusbar.remove(self.context_id, message_id)
+		self.remove_statusbar_timeout_id = None
+
 	def on_profile_window_destroy(self, widget):
+		if self.update_progressbar_timeout_id is not None:
+			gobject.source_remove(self.update_progressbar_timeout_id)
+		if self.remove_statusbar_timeout_id is not None:
+			gobject.source_remove(self.remove_statusbar_timeout_id)
 		del gajim.interface.instances[self.account]['profile']
 
 	def on_profile_window_key_press_event(self, widget, event):
@@ -79,8 +101,10 @@ class ProfileWindow:
 
 	def on_clear_button_clicked(self, widget):
 		# empty the image
-		self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person',
-			gtk.ICON_SIZE_DIALOG)
+		button = self.xml.get_widget('PHOTO_button')
+		image = button.get_image()
+		image.set_from_pixbuf(None)
+		button.set_label(_('Click to set your avatar'))
 		self.avatar_encoded = None
 		self.avatar_mime_type = None
 
@@ -124,24 +148,36 @@ class ProfileWindow:
 			pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
 			# rescale it
 			pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
-			image = self.xml.get_widget('PHOTO_image')
+			button = self.xml.get_widget('PHOTO_button')
+			image = button.get_image()
 			image.set_from_pixbuf(pixbuf)
+			button.set_label('')
 			self.avatar_encoded = base64.encodestring(data)
 			# returns None if unknown type
 			self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
 
-		self.dialog = dialogs.ImageChooserDialog(on_response_ok = on_ok)
+		def on_clear(widget):
+			self.dialog.destroy()
+			self.on_clear_button_clicked(widget)
+
+		self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok,
+			on_response_clear = on_clear)
 
 	def on_PHOTO_button_press_event(self, widget, event):
 		'''If right-clicked, show popup'''
 		if event.button == 3 and self.avatar_encoded: # right click
 			menu = gtk.Menu()
-			nick = gajim.config.get_per('accounts', self.account, 'name')
-			menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
-			menuitem.connect('activate',
-				gtkgui_helpers.on_avatar_save_as_menuitem_activate,
-				self.jid, None, nick + '.jpeg')
-			menu.append(menuitem)
+			
+			# Try to get pixbuf
+			pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid)
+
+			if pixbuf:
+				nick = gajim.config.get_per('accounts', self.account, 'name')
+				menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+				menuitem.connect('activate',
+					gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+					self.jid, None, nick + '.jpeg')
+				menu.append(menuitem)
 			# show clear
 			menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
 			menuitem.connect('activate', self.on_clear_button_clicked)
@@ -162,18 +198,23 @@ class ProfileWindow:
 	def set_values(self, vcard):
 		if not 'PHOTO' in vcard:
 			# set default image
-			image = self.xml.get_widget('PHOTO_image')
-			image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
+			button = self.xml.get_widget('PHOTO_button')
+			image = button.get_image()
+			image.set_from_pixbuf(None)
+			button.set_label(_('Click to set your avatar'))
 		for i in vcard.keys():
 			if i == 'PHOTO':
 				pixbuf, self.avatar_encoded, self.avatar_mime_type = \
 					get_avatar_pixbuf_encoded_mime(vcard[i])
-				image = self.xml.get_widget('PHOTO_image')
+				button = self.xml.get_widget('PHOTO_button')
+				image = button.get_image()
 				if not pixbuf:
-					image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
+					image.set_from_pixbuf(None)
+					button.set_label(_('Click to set your avatar'))
 					continue
 				pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
 				image.set_from_pixbuf(pixbuf)
+				button.set_label('')
 				continue
 			if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
 				for entry in vcard[i]:
@@ -191,6 +232,18 @@ class ProfileWindow:
 						vcard[i], 0)
 				else:
 					self.set_value(i + '_entry', vcard[i])
+		if self.update_progressbar_timeout_id is not None:
+			if self.message_id:
+				self.statusbar.remove(self.context_id, self.message_id)
+			self.message_id = self.statusbar.push(self.context_id,
+				_('Information received'))
+			self.remove_statusbar_timeout_id = gobject.timeout_add(3000,
+				self.remove_statusbar, self.message_id)
+			gobject.source_remove(self.update_progressbar_timeout_id)
+			# redraw progressbar after avatar is set so that windows is already
+			# resized. Else progressbar is not correctly redrawn
+			gobject.idle_add(self.progressbar.set_fraction, 0)
+			self.update_progressbar_timeout_id = None
 
 	def add_to_vcard(self, vcard, entry, txt):
 		'''Add an information to the vCard dictionary'''
@@ -248,6 +301,9 @@ class ProfileWindow:
 		return vcard
 
 	def on_publish_button_clicked(self, widget):
+		if self.update_progressbar_timeout_id:
+			# Operation in progress
+			return
 		if gajim.connections[self.account].connected < 2:
 			dialogs.ErrorDialog(_('You are not connected to the server'),
         		_('Without a connection you can not publish your contact '
@@ -261,8 +317,42 @@ class ProfileWindow:
 			nick = gajim.config.get_per('accounts', self.account, 'name')
 		gajim.nicks[self.account] = nick
 		gajim.connections[self.account].send_vcard(vcard)
+		self.message_id = self.statusbar.push(self.context_id,
+			_('Sending profile...'))
+		self.update_progressbar_timeout_id = gobject.timeout_add(100,
+			self.update_progressbar)
+
+	def vcard_published(self):
+		if self.message_id:
+			self.statusbar.remove(self.context_id, self.message_id)
+		self.message_id = self.statusbar.push(self.context_id,
+			_('Information published'))
+		self.remove_statusbar_timeout_id = gobject.timeout_add(3000,
+			self.remove_statusbar, self.message_id)
+		if self.update_progressbar_timeout_id is not None:
+			gobject.source_remove(self.update_progressbar_timeout_id)
+			self.progressbar.set_fraction(0)
+			self.update_progressbar_timeout_id = None
+
+	def vcard_not_published(self):
+		if self.message_id:
+			self.statusbar.remove(self.context_id, self.message_id)
+		self.message_id = self.statusbar.push(self.context_id,
+			_('Information NOT published'))
+		self.remove_statusbar_timeout_id = gobject.timeout_add(3000,
+			self.remove_statusbar, self.message_id)
+		if self.update_progressbar_timeout_id is not None:
+			gobject.source_remove(self.update_progressbar_timeout_id)
+			self.progressbar.set_fraction(0)
+			self.update_progressbar_timeout_id = None
+		dialogs.InformationDialog(_('vCard publication failed'),
+			_('There was an error while publishing your personal information, '
+			'try again later.'))
 
 	def on_retrieve_button_clicked(self, widget):
+		if self.update_progressbar_timeout_id:
+			# Operation in progress
+			return
 		entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
 			'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
 			'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
@@ -275,9 +365,18 @@ class ProfileWindow:
 			for e in entries:
 				self.xml.get_widget(e + '_entry').set_text('')
 			self.xml.get_widget('DESC_textview').get_buffer().set_text('')
-			self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person',
-				gtk.ICON_SIZE_DIALOG)
+			button = self.xml.get_widget('PHOTO_button')
+			image = button.get_image()
+			image.set_from_pixbuf(None)
+			button.set_label(_('Click to set your avatar'))
 			gajim.connections[self.account].request_vcard(self.jid)
 		else:
 			dialogs.ErrorDialog(_('You are not connected to the server'),
-  		    	_('Without a connection, you can not get your contact information.'))
+			_('Without a connection, you can not get your contact information.'))
+		self.message_id = self.statusbar.push(self.context_id,
+			_('Retrieving profile...'))
+		self.update_progressbar_timeout_id = gobject.timeout_add(100,
+			self.update_progressbar)
+
+	def on_close_button_clicked(self, widget):
+		self.window.destroy()
diff --git a/src/remote_control.py b/src/remote_control.py
index 82dbc54b63d3dfdf277e7d9cc8439c236e40f710..a87c892945bad6f624c8ca871148dc03f1f0b061 100644
--- a/src/remote_control.py
+++ b/src/remote_control.py
@@ -1,19 +1,9 @@
 ##	remote_control.py
 ##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##	- Dimitur Kirov <dkirov@gmail.com>
-##	- Andrew Sayman <lorien420@myrealbox.com>
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
+## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
+## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
+## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -33,60 +23,33 @@ from common import helpers
 from time import time
 from dialogs import AddNewContactWindow, NewChatDialog
 
-import dbus_support
+from common import dbus_support
 if dbus_support.supported:
 	import dbus
-	if dbus_support.version >= (0, 41, 0):
+	if dbus_support:
 		import dbus.service
-		import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it
-		DbusPrototype = dbus.service.Object
-	elif dbus_support.version >= (0, 20, 0):
-		DbusPrototype = dbus.Object
-	else: #dbus is not defined
-		DbusPrototype = str 
+		import dbus.glib
 
 INTERFACE = 'org.gajim.dbus.RemoteInterface'
 OBJ_PATH = '/org/gajim/dbus/RemoteObject'
 SERVICE = 'org.gajim.dbus'
 
-# type mapping, it is different in each version
-ident = lambda e: e
-if dbus_support.version[1] >= 43:
-	# in most cases it is a utf-8 string
-	DBUS_STRING = dbus.String
-
-	# general type (for use in dicts, 
-	#   where all values should have the same type)
-	DBUS_VARIANT = dbus.Variant
-	DBUS_BOOLEAN = dbus.Boolean
-	DBUS_DOUBLE = dbus.Double
-	DBUS_INT32 = dbus.Int32
-	# dictionary with string key and binary value
-	DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv")
-	# dictionary with string key and value
-	DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
-	# empty type
-	DBUS_NONE = lambda : dbus.Variant(0)
-
-else: # 33, 35, 36
-	DBUS_DICT_SV = lambda : {}
-	DBUS_DICT_SS = lambda : {}
-	DBUS_STRING = lambda e: unicode(e).encode('utf-8')
-	# this is the only way to return lists and dicts of mixed types
-	DBUS_VARIANT = lambda e: (isinstance(e, (str, unicode)) and \
-								DBUS_STRING(e)) or repr(e)
-	DBUS_NONE = lambda : ''
-	if dbus_support.version[1] >= 41: # 35, 36
-		DBUS_BOOLEAN = dbus.Boolean
-		DBUS_DOUBLE = dbus.Double
-		DBUS_INT32 = dbus.Int32
-	else: # 33
-		DBUS_BOOLEAN = ident
-		DBUS_INT32 = ident
-		DBUS_DOUBLE = ident
-
-STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
-	'invisible']
+# type mapping
+
+# in most cases it is a utf-8 string
+DBUS_STRING = dbus.String
+
+# general type (for use in dicts, where all values should have the same type)
+DBUS_VARIANT = dbus.Variant
+DBUS_BOOLEAN = dbus.Boolean
+DBUS_DOUBLE = dbus.Double
+DBUS_INT32 = dbus.Int32
+# dictionary with string key and binary value
+DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv")
+# dictionary with string key and value
+DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
+# empty type
+DBUS_NONE = lambda : dbus.Variant(0)
 
 def get_dbus_struct(obj):
 	''' recursively go through all the items and replace
@@ -123,65 +86,35 @@ class Remote:
 		self.signal_object = None
 		session_bus = dbus_support.session_bus.SessionBus()
 
-		if dbus_support.version[1] >= 41:
-			service = dbus.service.BusName(SERVICE, bus=session_bus)
-			self.signal_object = SignalObject(service)
-		elif dbus_support.version[1] <= 40 and dbus_support.version[1] >= 20:
-			service=dbus.Service(SERVICE, session_bus)
-			self.signal_object = SignalObject(service)
+		service = dbus.service.BusName(SERVICE, bus=session_bus)
+		self.signal_object = SignalObject(service)
 
 	def raise_signal(self, signal, arg):
 		if self.signal_object:
 			self.signal_object.raise_signal(signal, 
-										get_dbus_struct(arg))
+				get_dbus_struct(arg))
 
 
-class SignalObject(DbusPrototype):
-	''' Local object definition for /org/gajim/dbus/RemoteObject. This doc must 
-	not be visible, because the clients can access only the remote object. '''
+class SignalObject(dbus.service.Object):
+	''' Local object definition for /org/gajim/dbus/RemoteObject.
+	(This docstring is not be visible, because the clients can access only the remote object.)'''
 
 	def __init__(self, service):
 		self.first_show = True
 		self.vcard_account = None
 
 		# register our dbus API
-		if dbus_support.version[1] >= 41:
-			DbusPrototype.__init__(self, service, OBJ_PATH)
-		elif dbus_support.version[1] >= 30:
-			DbusPrototype.__init__(self, OBJ_PATH, service)
-		else:
-			DbusPrototype.__init__(self, OBJ_PATH, service, 
-			[	self.toggle_roster_appearance,
-				self.show_next_unread,
-				self.list_contacts,
-				self.list_accounts,
-				self.account_info,
-				self.change_status,
-				self.open_chat,
-				self.send_message,
-                                self.send_single_message,
-				self.contact_info,
-				self.send_file,
-				self.prefs_list,
-				self.prefs_store,
-				self.prefs_del,
-				self.prefs_put,
-				self.add_contact,
-				self.remove_contact,
-				self.get_status,
-				self.get_status_message,
-				self.start_chat,
-				self.send_xml,
-			])
+		dbus.service.Object.__init__(self, service, OBJ_PATH)
 
 	def raise_signal(self, signal, arg):
-		''' raise a signal, with a single string message '''
+		'''raise a signal, with a single string message'''
 		from dbus import dbus_bindings
 		message = dbus_bindings.Signal(OBJ_PATH, INTERFACE, signal)
 		i = message.get_iter(True)
 		i.append(arg)
 		self._connection.send(message)
 
+	@dbus.service.method(INTERFACE)
 	def get_status(self, *args):
 		'''get_status(account = None)
 		returns status (show to be exact) which is the global one
@@ -193,8 +126,9 @@ class SignalObject(DbusPrototype):
 			return helpers.get_global_show()
 		# return show for the given account
 		index = gajim.connections[account].connected
-		return DBUS_STRING(STATUS_LIST[index])
+		return DBUS_STRING(gajim.SHOW_LIST[index])
 
+	@dbus.service.method(INTERFACE)
 	def get_status_message(self, *args):
 		'''get_status(account = None)
 		returns status which is the global one
@@ -208,7 +142,7 @@ class SignalObject(DbusPrototype):
 		status = gajim.connections[account].status
 		return DBUS_STRING(status)
 
-
+	@dbus.service.method(INTERFACE)
 	def get_account_and_contact(self, account, jid):
 		''' get the account (if not given) and contact instance from jid'''
 		connected_account = None
@@ -236,6 +170,7 @@ class SignalObject(DbusPrototype):
 
 		return connected_account, contact
 
+	@dbus.service.method(INTERFACE)
 	def send_file(self, *args):
 		'''send_file(file_path, jid, account=None) 
 		send file, located at 'file_path' to 'jid', using account 
@@ -254,7 +189,7 @@ class SignalObject(DbusPrototype):
 		return False
 
 	def _send_message(self, jid, message, keyID, account, type = 'chat', subject = None):
-		''' can be called from send_chat_message (default when send_message)
+		'''can be called from send_chat_message (default when send_message)
 		or send_single_message'''
 		if not jid or not message:
 			return None # or raise error
@@ -269,28 +204,31 @@ class SignalObject(DbusPrototype):
 			return True
 		return False
 
+	@dbus.service.method(INTERFACE)
 	def send_chat_message(self, *args):
-		''' send_message(jid, message, keyID=None, account=None)
+		'''send_message(jid, message, keyID=None, account=None)
 		send chat 'message' to 'jid', using account (optional) 'account'.
 		if keyID is specified, encrypt the message with the pgp key '''
 		jid, message, keyID, account = self._get_real_arguments(args, 4)
 		jid = self._get_real_jid(jid, account)
 		return self._send_message(jid, message, keyID, account)
 
+	@dbus.service.method(INTERFACE)
 	def send_single_message(self, *args):
-		''' send_single_message(jid, subject, message, keyID=None, account=None)
+		'''send_single_message(jid, subject, message, keyID=None, account=None)
 		send single 'message' to 'jid', using account (optional) 'account'.
 		if keyID is specified, encrypt the message with the pgp key '''
 		jid, subject, message, keyID, account = self._get_real_arguments(args, 5)
 		jid = self._get_real_jid(jid, account)
 		return self._send_message(jid, message, keyID, account, type, subject)
 
+	@dbus.service.method(INTERFACE)
 	def open_chat(self, *args):
 		''' start_chat(jid, account=None) -> shows the tabbed window for new 
 		message to 'jid', using account(optional) 'account' '''
 		jid, account = self._get_real_arguments(args, 2)
 		if not jid:
-			# FIXME: raise exception for missing argument (dbus0.35+)
+			raise MissingArgument
 			return None
 		jid = self._get_real_jid(jid, account)
 
@@ -332,13 +270,14 @@ class SignalObject(DbusPrototype):
 			return True
 		return False
 
+	@dbus.service.method(INTERFACE)
 	def change_status(self, *args, **keywords):
 		''' change_status(status, message, account). account is optional -
 		if not specified status is changed for all accounts. '''
 		status, message, account = self._get_real_arguments(args, 3)
 		if status not in ('offline', 'online', 'chat', 
 			'away', 'xa', 'dnd', 'invisible'):
-			# FIXME: raise exception for bad status (dbus0.35)
+			raise InvalidArgument
 			return None
 		if account:
 			gobject.idle_add(gajim.interface.roster.send_status, account, 
@@ -346,25 +285,29 @@ class SignalObject(DbusPrototype):
 		else:
 			# account not specified, so change the status of all accounts
 			for acc in gajim.contacts.get_accounts():
+				if not gajim.config.get_per('accounts', acc, 'sync_with_global_status'):
+					continue
 				gobject.idle_add(gajim.interface.roster.send_status, acc, 
 					status, message)
 		return None
 
-	def show_next_unread(self, *args):
-		''' Show the window(s) with next waiting messages in tabbed/group chats. '''
+	@dbus.service.method(INTERFACE)
+	def show_next_pending_event(self, *args):
+		'''Show the window(s) with next pending event in tabbed/group chats.'''
 		if gajim.events.get_nb_events():
 			gajim.interface.systray.handle_first_event()
 
+	@dbus.service.method(INTERFACE)
 	def contact_info(self, *args):
-		''' get vcard info for a contact. Return cached value of the vcard.
+		'''get vcard info for a contact. Return cached value of the vcard.
 		'''
 		[jid] = self._get_real_arguments(args, 1)
 		if not isinstance(jid, unicode):
 			jid = unicode(jid)
 		if not jid:
-			# FIXME: raise exception for missing argument (0.3+)
+			raise MissingArgument
 			return None
-		jid = self._get_real_jid(jid, account)
+		jid = self._get_real_jid(jid)
 
 		cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
 		if cached_vcard:
@@ -373,8 +316,9 @@ class SignalObject(DbusPrototype):
 		# return empty dict
 		return DBUS_DICT_SV()
 
+	@dbus.service.method(INTERFACE)
 	def list_accounts(self, *args):
-		''' list register accounts '''
+		'''list register accounts'''
 		result = gajim.contacts.get_accounts()
 		if result and len(result) > 0:
 			result_array = []
@@ -383,8 +327,9 @@ class SignalObject(DbusPrototype):
 			return result_array
 		return None
 
+	@dbus.service.method(INTERFACE)
 	def account_info(self, *args):
-		''' show info on account: resource, jid, nick, prio, message '''
+		'''show info on account: resource, jid, nick, prio, message'''
 		[for_account] = self._get_real_arguments(args, 1)
 		if not gajim.connections.has_key(for_account):
 			# account is invalid
@@ -392,19 +337,19 @@ class SignalObject(DbusPrototype):
 		account = gajim.connections[for_account]
 		result = DBUS_DICT_SS()
 		index = account.connected
-		result['status'] = DBUS_STRING(STATUS_LIST[index])
+		result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
 		result['name'] = DBUS_STRING(account.name)
 		result['jid'] = DBUS_STRING(gajim.get_jid_from_account(account.name))
 		result['message'] = DBUS_STRING(account.status)
-		result['priority'] = DBUS_STRING(unicode(gajim.config.get_per('accounts', 
-								account.name, 'priority')))
+		result['priority'] = DBUS_STRING(unicode(account.priority))
 		result['resource'] = DBUS_STRING(unicode(gajim.config.get_per('accounts', 
-								account.name, 'resource')))
+			account.name, 'resource')))
 		return result
 
+	@dbus.service.method(INTERFACE)
 	def list_contacts(self, *args):
-		''' list all contacts in the roster. If the first argument is specified,
-		then return the contacts for the specified account '''
+		'''list all contacts in the roster. If the first argument is specified,
+		then return the contacts for the specified account'''
 		[for_account] = self._get_real_arguments(args, 1)
 		result = []
 		accounts = gajim.contacts.get_accounts()
@@ -426,6 +371,7 @@ class SignalObject(DbusPrototype):
 			return None
 		return result
 
+	@dbus.service.method(INTERFACE)
 	def toggle_roster_appearance(self, *args):
 		''' shows/hides the roster window '''
 		win = gajim.interface.roster.window
@@ -439,6 +385,7 @@ class SignalObject(DbusPrototype):
 			else:
 				win.window.focus(long(time()))
 
+	@dbus.service.method(INTERFACE)
 	def prefs_list(self, *args):
 		prefs_dict = DBUS_DICT_SS()
 		def get_prefs(data, name, path, value):
@@ -453,6 +400,7 @@ class SignalObject(DbusPrototype):
 		gajim.config.foreach(get_prefs)
 		return prefs_dict
 
+	@dbus.service.method(INTERFACE)
 	def prefs_store(self, *args):
 		try:
 			gajim.interface.save_config()
@@ -460,6 +408,7 @@ class SignalObject(DbusPrototype):
 			return False
 		return True
 
+	@dbus.service.method(INTERFACE)
 	def prefs_del(self, *args):
 		[key] = self._get_real_arguments(args, 1)
 		if not key:
@@ -473,6 +422,7 @@ class SignalObject(DbusPrototype):
 			gajim.config.del_per(key_path[0], key_path[1], key_path[2])
 		return True
 
+	@dbus.service.method(INTERFACE)
 	def prefs_put(self, *args):
 		[key] = self._get_real_arguments(args, 1)
 		if not key:
@@ -486,6 +436,7 @@ class SignalObject(DbusPrototype):
 		gajim.config.set_per(key_path[0], key_path[1], subname, value)
 		return True
 
+	@dbus.service.method(INTERFACE)
 	def add_contact(self, *args):
 		[jid, account] = self._get_real_arguments(args, 2)
 		if account:
@@ -501,6 +452,7 @@ class SignalObject(DbusPrototype):
 			AddNewContactWindow(account = None, jid = jid)
 		return True
 
+	@dbus.service.method(INTERFACE)
 	def remove_contact(self, *args):
 		[jid, account] = self._get_real_arguments(args, 2)
 		jid = self._get_real_jid(jid, account)
@@ -595,9 +547,11 @@ class SignalObject(DbusPrototype):
 		contact_dict['resources'] = DBUS_VARIANT(contact_dict['resources'])
 		return contact_dict
 
+	@dbus.service.method(INTERFACE)
 	def get_unread_msgs_number(self, *args):
-		return str(gajim.events.get_nb_events)
+		return str(gajim.events.get_nb_events())
 
+	@dbus.service.method(INTERFACE)
 	def start_chat(self, *args):
 		[account] = self._get_real_arguments(args, 1)
 		if not account:
@@ -606,6 +560,7 @@ class SignalObject(DbusPrototype):
 		NewChatDialog(account)
 		return True
 
+	@dbus.service.method(INTERFACE)
 	def send_xml(self, *args):
 		xml, account = self._get_real_arguments(args, 2)
 		if account:
@@ -613,36 +568,3 @@ class SignalObject(DbusPrototype):
 		else:
 			for acc in gajim.contacts.get_accounts():
 				gajim.connections[acc].send_stanza(xml)
-
-	if dbus_support.version[1] >= 30 and dbus_support.version[1] <= 40:
-		method = dbus.method
-		signal = dbus.signal
-	elif dbus_support.version[1] >= 41:
-		method = dbus.service.method
-		signal = dbus.service.signal
-
-	# prevent using decorators, because they are not supported 
-	# on python < 2.4
-	# FIXME: use decorators when python2.3 (and dbus 0.23) is OOOOOOLD
-	toggle_roster_appearance = method(INTERFACE)(toggle_roster_appearance)
-	list_contacts = method(INTERFACE)(list_contacts)
-	list_accounts = method(INTERFACE)(list_accounts)
-	show_next_unread = method(INTERFACE)(show_next_unread)
-	change_status = method(INTERFACE)(change_status)
-	open_chat = method(INTERFACE)(open_chat)
-	contact_info = method(INTERFACE)(contact_info)
-	send_message = method(INTERFACE)(send_chat_message)
-	send_single_message = method(INTERFACE)(send_single_message)
-	send_file = method(INTERFACE)(send_file)
-	prefs_list = method(INTERFACE)(prefs_list)
-	prefs_put = method(INTERFACE)(prefs_put)
-	prefs_del = method(INTERFACE)(prefs_del)
-	prefs_store = method(INTERFACE)(prefs_store)
-	remove_contact = method(INTERFACE)(remove_contact)
-	add_contact = method(INTERFACE)(add_contact)
-	get_status = method(INTERFACE)(get_status)
-	get_status_message = method(INTERFACE)(get_status_message)
-	account_info = method(INTERFACE)(account_info)
-	get_unread_msgs_number = method(INTERFACE)(get_unread_msgs_number)
-	start_chat = method(INTERFACE)(start_chat)
-	send_xml = method(INTERFACE)(send_xml)
diff --git a/src/roster_window.py b/src/roster_window.py
index c14c467b95744e7119af2ac9b80b0e9a57f2a4d9..c773a7265d1bc497c47549018c058b10e323943b 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 ##	roster_window.py
 ##
 ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
@@ -35,11 +36,18 @@ import notify
 
 from common import gajim
 from common import helpers
+from common import passwords
+from common.exceptions import GajimGeneralException
+
 from message_window import MessageWindowMgr
 from chat_control import ChatControl
 from groupchat_control import GroupchatControl
 from groupchat_control import PrivateChatControl
 
+from common import dbus_support
+if dbus_support.supported:
+	from music_track_listener import MusicTrackListener
+
 #(icon, name, type, jid, account, editable, second pixbuf)
 (
 C_IMG, # image to show state (online, new message etc)
@@ -51,11 +59,8 @@ C_EDITABLE, # cellrenderer text that holds name editable or not?
 C_SECPIXBUF, # secondary_pixbuf (holds avatar or padlock)
 ) = range(7)
 
-
-DEFAULT_ICONSET = 'dcraven'
-
 class RosterWindow:
-	'''Class for main window of gtkgui interface'''
+	'''Class for main window of the GTK+ interface'''
 
 	def get_account_iter(self, name):
 		model = self.tree.get_model()
@@ -76,9 +81,8 @@ class RosterWindow:
 		root = self.get_account_iter(account)
 		group_iter = model.iter_children(root)
 		# C_NAME column contacts the pango escaped group name
-		name = gtkgui_helpers.escape_for_pango_markup(name)
 		while group_iter:
-			group_name = model[group_iter][C_NAME].decode('utf-8')
+			group_name = model[group_iter][C_JID].decode('utf-8')
 			if name == group_name:
 				break
 			group_iter = model.iter_next(group_iter)
@@ -129,6 +133,31 @@ class RosterWindow:
 			group_iter = model.iter_next(group_iter)
 		return found
 
+	def get_path(self, jid, account):
+		''' Try to get line of contact in roster	'''
+		iters = self.get_contact_iter(jid, account)
+		if iters:
+			path = self.tree.get_model().get_path(iters[0])
+		else:
+			path = None
+		return path
+
+	def show_and_select_path(self, path, jid, account):	
+		'''Show contact in roster (if he is invisible for example) 
+		and select line'''
+		if not path:
+			# contact is in roster but we curently don't see him online
+			# show him
+			self.add_contact_to_roster(jid, account)
+			iters = self.get_contact_iter(jid, account)
+			path = self.tree.get_model().get_path(iters[0])
+		# popup == False so we show awaiting event in roster
+		# show and select contact line in roster (even if he is not in roster) 
+		self.tree.expand_row(path[0:1], False)
+		self.tree.expand_row(path[0:2], False)
+		self.tree.scroll_to_cell(path)
+		self.tree.set_cursor(path)
+
 	def add_account_to_roster(self, account):
 		model = self.tree.get_model()
 		if self.get_account_iter(account):
@@ -144,8 +173,7 @@ class RosterWindow:
 		show = gajim.SHOW_LIST[gajim.connections[account].connected]
 
 		tls_pixbuf = None
-		if gajim.con_types.has_key(account) and \
-			gajim.con_types[account] in ('tls', 'ssl'):
+		if gajim.account_is_securely_connected(account):
 			tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
 				gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
 
@@ -163,12 +191,8 @@ class RosterWindow:
 		else:
 			accounts = [account]
 		num_of_accounts = len(accounts)
-		num_of_secured = 0
-		for acct in accounts:
-			if gajim.con_types.has_key(acct) and \
-			gajim.con_types[acct] in ('tls', 'ssl'):
-				num_of_secured += 1
-		if num_of_secured:
+		num_of_secured = gajim.get_number_of_securely_connected_accounts()
+		if num_of_secured and gajim.con_types.has_key(account) and gajim.con_types[account] in ('tls', 'ssl'):
 			tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
 				gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
 			if num_of_secured < num_of_accounts:
@@ -193,12 +217,20 @@ class RosterWindow:
 		else:
 			model[iter][C_SECPIXBUF] = None
 		path = model.get_path(iter)
+		account_name = account
+		accounts = [account]
 		if self.regroup:
-			account = _('Merged accounts')
+			account_name = _('Merged accounts')
+			accounts = []
 		if not self.tree.row_expanded(path) and model.iter_has_child(iter):
-			model[iter][C_NAME] = '[%s]' % account
-		else:
-			model[iter][C_NAME] = account
+			# account row not expanded
+			account_name = '[%s]' % account_name
+		if gajim.account_is_connected(account) or (self.regroup and \
+		gajim.get_number_of_connected_accounts()):
+			nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
+				accounts = accounts)		
+			account_name += ' (%s/%s)' % (repr(nbr_on),repr(nbr_total))
+		model[iter][C_NAME] = account_name
 
 	def remove_newly_added(self, jid, account):
 		if jid in gajim.newly_added[account]:
@@ -207,13 +239,19 @@ class RosterWindow:
 
 	def add_contact_to_roster(self, jid, account):
 		'''Add a contact to the roster and add groups if they aren't in roster
-		force is about	force to add it, even if it is offline and show offline
+		force is about force to add it, even if it is offline and show offline
 		is False, because it has online children, so we need to show it.
 		If add_children is True, we also add all children, even if they were not
 		already drawn'''
 		showOffline = gajim.config.get('showoffline')
 		model = self.tree.get_model()
 		contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+		nb_events = gajim.events.get_nb_roster_events(account, contact.jid)
+		# count events from all resources
+		for contact_ in gajim.contacts.get_contact(account, jid):
+			if contact_.resource:
+				nb_events += gajim.events.get_nb_roster_events(account,
+					contact_.get_full_jid())
 		if not contact:
 			return
 		# If contact already in roster, do not add it
@@ -223,23 +261,19 @@ class RosterWindow:
 			self.add_self_contact(account)
 			return
 		if gajim.jid_is_transport(contact.jid):
+			# if jid is transport, check if we wanna show it in roster
+			if not gajim.config.get('show_transports_group') and not nb_events:
+				return
 			contact.groups = [_('Transports')]
+		elif not showOffline and not gajim.account_is_connected(account) and \
+		nb_events == 0:
+			return
 
-		# JEP-0162
-		hide = True
-		if contact.sub in ('both', 'to'):
-			hide = False
-		elif contact.ask == 'subscribe':
-			hide = False
-		elif contact.name or len(contact.groups):
-			hide = False
-
-		observer = False
-		if hide:
-			if contact.sub == 'from':
-				observer = True
-			else:
-				return
+		# XEP-0162
+		hide = contact.is_hidden_from_roster()
+		if hide and contact.sub != 'from':
+			return
+		observer = contact.is_observer()
 
 		if observer:
 			# if he has a tag, remove it
@@ -289,10 +323,10 @@ class RosterWindow:
 				return
 
 		if (contact.show in ('offline', 'error') or hide) and \
-			not showOffline and (not _('Transports') in contact.groups or \
-			gajim.connections[account].connected < 2) and \
-			len(gajim.events.get_events(account, jid)) == 0 and \
-			not _('Not in Roster') in contact.groups:
+		not showOffline and (not _('Transports') in contact.groups or \
+		gajim.connections[account].connected < 2) and \
+		len(gajim.contacts.get_contact(account, jid)) == 1 and nb_events == 0 and\
+		not _('Not in Roster') in contact.groups:
 			return
 
 		# Remove brother contacts that are already in roster to add them
@@ -307,25 +341,28 @@ class RosterWindow:
 			groups = [_('Observers')]
 		elif not groups:
 			groups = [_('General')]
-		for g in groups:
-			iterG = self.get_group_iter(g, account)
+		for group in groups:
+			iterG = self.get_group_iter(group, account)
 			if not iterG:
 				IterAcct = self.get_account_iter(account)
 				iterG = model.append(IterAcct, [
 					self.jabber_state_images['16']['closed'],
-					gtkgui_helpers.escape_for_pango_markup(g), 'group', g, account,
-					False, None])
-			if not gajim.groups[account].has_key(g): # It can probably never append
-				if account + g in self.collapsed_rows:
+					gtkgui_helpers.escape_for_pango_markup(group), 'group',
+					group, account, False, None])
+				self.draw_group(group, account)
+				if model.iter_n_children(IterAcct) == 1: # We added the first one
+					self.draw_account(account)
+			if group not in gajim.groups[account]: # It can probably never append
+				if account + group in self.collapsed_rows:
 					ishidden = False
 				else:
 					ishidden = True
-				gajim.groups[account][g] = { 'expand': ishidden }
+				gajim.groups[account][group] = {'expand': ishidden}
 			if not account in self.collapsed_rows:
 				self.tree.expand_row((model.get_path(iterG)[0]), False)
 
 			typestr = 'contact'
-			if g == _('Transports'):
+			if group == _('Transports'):
 				typestr = 'agent'
 
 			name = contact.get_shown_name()
@@ -333,7 +370,7 @@ class RosterWindow:
 			model.append(iterG, (None, name, typestr, contact.jid, account,
 				False, None))
 
-			if gajim.groups[account][g]['expand']:
+			if gajim.groups[account][group]['expand']:
 				self.tree.expand_row(model.get_path(iterG), False)
 		self.draw_contact(jid, account)
 		self.draw_avatar(jid, account)
@@ -343,6 +380,36 @@ class RosterWindow:
 				data['jid'])
 			self.add_contact_to_roster(data['jid'], data['account'])
 
+	def draw_group(self, group, account):
+		iter = self.get_group_iter(group, account)
+		if not iter:
+			return
+		if self.regroup:
+			accounts = []
+		else:
+			accounts = [account]
+		nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
+			accounts = accounts, groups = [group])
+		model = self.tree.get_model()
+		model.set_value(iter, 1 , gtkgui_helpers.escape_for_pango_markup(
+			'%s (%s/%s)' % (group, repr(nbr_on), repr(nbr_total))))		
+
+	def add_to_not_in_the_roster(self, account, jid, nick = ''):
+		''' add jid to group "not in the roster", he MUST not be in roster yet,
+		 return contact '''
+		keyID = ''
+		attached_keys = gajim.config.get_per('accounts', account,
+			'attached_gpg_keys').split()
+		if jid in attached_keys:
+			keyID = attached_keys[attached_keys.index(jid) + 1]
+		contact = gajim.contacts.create_contact(jid = jid,
+			name = nick, groups = [_('Not in Roster')],
+			show = 'not in roster', status = '', sub = 'none',
+			keyID = keyID)
+		gajim.contacts.add_contact(account, contact)
+		self.add_contact_to_roster(contact.jid, account)
+		return contact
+
 	def get_self_contact_iter(self, account):
 		model = self.tree.get_model()
 		iterAcct = self.get_account_iter(account)
@@ -388,25 +455,23 @@ class RosterWindow:
 			return
 		if contact.jid in gajim.newly_added[account]:
 			return
-		if contact.jid.find('@') < 1 and gajim.connections[account].connected > 1:
-			# It's an agent
+		if gajim.jid_is_transport(contact.jid) and gajim.account_is_connected(
+		account) and gajim.config.get('show_transports_group'):
+			# It's an agent and we show them
 			return
 		if contact.jid in gajim.to_be_removed[account]:
 			gajim.to_be_removed[account].remove(contact.jid)
-		# JEP-0162
-		hide = True
-		if contact.sub in ('both', 'to', 'from'):
-			hide = False
-		elif contact.ask == 'subscribe':
-			hide = False
-		elif contact.name or len(contact.groups):
-			hide = False
-
-		showOffline = gajim.config.get('showoffline')
-		if (contact.show in ('offline', 'error') or hide) and \
-			not showOffline and (not _('Transports') in contact.groups or \
-			gajim.connections[account].connected < 2) and \
-			len(gajim.events.get_events(account, contact.jid, ['chat'])) == 0:
+		
+		
+		hide = contact.is_hidden_from_roster()
+
+		show_offline = gajim.config.get('showoffline')
+		show_transports = gajim.config.get('show_transports_group')
+		if (_('Transports') in contact.groups and not show_transports) or \
+		((contact.show in ('offline', 'error') or hide) and not show_offline and \
+		(not _('Transports') in contact.groups or \
+		gajim.account_is_disconnected(account))) and \
+		len(gajim.events.get_events(account, contact.jid, ['chat'])) == 0:
 			self.remove_contact(contact, account)
 		else:
 			self.draw_contact(contact.jid, account)
@@ -459,7 +524,7 @@ class RosterWindow:
 	def get_appropriate_state_images(self, jid, size = '16',
 		icon_name = 'online'):
 		'''check jid and return the appropriate state images dict for
-		the demanded size. icon_name is taken into account when jis is from
+		the demanded size. icon_name is taken into account when jid is from
 		transport: transport iconset doesn't contain all icons, so we fall back
 		to jabber one'''
 		transport = gajim.get_transport_name_from_jid(jid)
@@ -483,18 +548,27 @@ class RosterWindow:
 			return
 		name = gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name())
 
-		if len(contact_instances) > 1:
-			name += ' (' + unicode(len(contact_instances)) + ')'
+		nb_connected_contact = 0
+		for c in contact_instances:
+			if c.show not in ('error', 'offline'):
+				nb_connected_contact += 1
+		if nb_connected_contact > 1:
+			name += ' (' + unicode(nb_connected_contact) + ')'
 
 		# show (account_name) if there are 2 contact with same jid in merged mode
 		if self.regroup:
 			add_acct = False
 			# look through all contacts of all accounts
-			for a in gajim.connections:
-				for j in gajim.contacts.get_jid_list(a):
+			for account_iter in gajim.connections:
+				if account_iter == account: # useless to add accout name
+					continue
+				for jid_iter in gajim.contacts.get_jid_list(account_iter):
 					# [0] cause it'fster than highest_prio
-					c = gajim.contacts.get_first_contact_from_jid(a, j)
-					if c.name == contact.name and (j, a) != (jid, account):
+					contact_iter = gajim.contacts.\
+						get_first_contact_from_jid(account_iter, jid_iter)
+					if contact_iter.get_shown_name() == \
+					contact.get_shown_name() and\
+					(jid_iter, account_iter) != (jid, account):
 						add_acct = True
 						break
 				if add_acct:
@@ -507,7 +581,7 @@ class RosterWindow:
 		if contact.status and gajim.config.get('show_status_msgs_in_roster'):
 			status = contact.status.strip()
 			if status != '':
-				status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 1)
+				status = helpers.reduce_chars_newlines(status, max_lines = 1)
 				# escape markup entities and make them small italic and fg color
 				color = gtkgui_helpers._get_fade_color(self.tree, selected, focus)
 				colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue)
@@ -597,20 +671,19 @@ class RosterWindow:
 			win = gajim.interface.msg_win_mgr.get_window(room_jid,  account)
 			win.window.present()
 			win.set_active_tab(room_jid,  account)
-			dialogs.ErrorDialog(_('You are already in room %s') % room_jid)
+			dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid)
 			return
 		invisible_show = gajim.SHOW_LIST.index('invisible')
 		if gajim.connections[account].connected == invisible_show:
-			dialogs.ErrorDialog(_('You cannot join a room while you are invisible')
+			dialogs.ErrorDialog(_('You cannot join a group chat while you are invisible')
 				)
 			return
-		room, server = room_jid.split('@')
 		if not gajim.interface.msg_win_mgr.has_window(room_jid, account):
 			self.new_room(room_jid, nick, account)
 		gc_win = gajim.interface.msg_win_mgr.get_window(room_jid, account)
 		gc_win.set_active_tab(room_jid, account)
 		gc_win.window.present()
-		gajim.connections[account].join_gc(nick, room, server, password)
+		gajim.connections[account].join_gc(nick, room_jid, password)
 		if password:
 			gajim.gc_passwords[room_jid] = password
 
@@ -625,9 +698,6 @@ class RosterWindow:
 		self.join_gc_room(account, bookmark['jid'], bookmark['nick'],
 			bookmark['password'])
 
-	def on_bm_header_changed_state(self, widget, event):
-		widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
-
 	def on_send_server_message_menuitem_activate(self, widget, account):
 		server = gajim.config.get_per('accounts', account, 'hostname')
 		server += '/announce/online'
@@ -687,26 +757,37 @@ class RosterWindow:
 		update_motd_menuitem = xml.get_widget('update_motd_menuitem')
 		delete_motd_menuitem = xml.get_widget('delete_motd_menuitem')
 
-		send_single_message_menuitem.connect('activate',
-			self.on_send_single_message_menuitem_activate, account)
-
 		xml_console_menuitem.connect('activate',
 			self.on_xml_console_menuitem_activate, account)
 
-		privacy_lists_menuitem.connect('activate',
-			self.on_privacy_lists_menuitem_activate, account)
-
-		send_server_message_menuitem.connect('activate',
-			self.on_send_server_message_menuitem_activate, account)
+		if gajim.connections[account] and gajim.connections[account].privacy_rules_supported:
+			privacy_lists_menuitem.connect('activate',
+				self.on_privacy_lists_menuitem_activate, account)
+		else:
+			privacy_lists_menuitem.set_sensitive(False)
+
+		if gajim.connections[account].is_zeroconf:
+			send_single_message_menuitem.set_sensitive(False)
+			administrator_menuitem.set_sensitive(False)
+			send_server_message_menuitem.set_sensitive(False)
+			set_motd_menuitem.set_sensitive(False)
+			update_motd_menuitem.set_sensitive(False)
+			delete_motd_menuitem.set_sensitive(False)
+		else:
+			send_single_message_menuitem.connect('activate',
+				self.on_send_single_message_menuitem_activate, account)
+			
+			send_server_message_menuitem.connect('activate',
+				self.on_send_server_message_menuitem_activate, account)
 
-		set_motd_menuitem.connect('activate',
-			self.on_set_motd_menuitem_activate, account)
+			set_motd_menuitem.connect('activate',
+				self.on_set_motd_menuitem_activate, account)
 
-		update_motd_menuitem.connect('activate',
-			self.on_update_motd_menuitem_activate, account)
+			update_motd_menuitem.connect('activate',
+				self.on_update_motd_menuitem_activate, account)
 
-		delete_motd_menuitem.connect('activate',
-			self.on_delete_motd_menuitem_activate, account)
+			delete_motd_menuitem.connect('activate',
+				self.on_delete_motd_menuitem_activate, account)
 
 		advanced_menuitem_menu.show_all()
 
@@ -718,6 +799,9 @@ class RosterWindow:
 			return
 		new_chat_menuitem = self.xml.get_widget('new_chat_menuitem')
 		join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
+		muc_icon = self.load_icon('muc_active')
+		if muc_icon:
+			join_gc_menuitem.set_image(muc_icon)
 		add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem')
 		service_disco_menuitem = self.xml.get_widget('service_disco_menuitem')
 		advanced_menuitem = self.xml.get_widget('advanced_menuitem')
@@ -777,23 +861,36 @@ class RosterWindow:
 			disco_sub_menu = gtk.Menu()
 			new_chat_sub_menu = gtk.Menu()
 
-			for account in gajim.connections:
+			accounts_list = gajim.contacts.get_accounts() 
+			accounts_list.sort() 
+			for account in accounts_list:
 				if gajim.connections[account].connected <= 1:
 					# if offline or connecting
 					continue
-				
+
+				# new chat
+				new_chat_item = gtk.MenuItem(_('using account %s') % account,
+					False)
+				new_chat_sub_menu.append(new_chat_item)
+				new_chat_item.connect('activate',
+					self.on_new_chat_menuitem_activate,	account)
+
+				if gajim.config.get_per('accounts', account, 'is_zeroconf'): 
+					continue 	
+
 				# join gc
 				label = gtk.Label()
 				label.set_markup('<u>' + account.upper() +'</u>')
 				label.set_use_underline(False)
 				gc_item = gtk.MenuItem()
 				gc_item.add(label)
-				gc_item.connect('state-changed', self.on_bm_header_changed_state)
+				gc_item.connect('state-changed',
+					gtkgui_helpers.on_bm_header_changed_state)
 				gc_sub_menu.append(gc_item)
 				
 				self.add_bookmarks_list(gc_sub_menu, account)
 
-				# the 'manage gc bookmarks' item is showed
+				# the 'manage gc bookmarks' item is shown
 				# below to avoid duplicate code
 
 				# add
@@ -807,12 +904,6 @@ class RosterWindow:
 				disco_item.connect('activate',
 					self.on_service_disco_menuitem_activate, account)
 
-				# new chat
-				new_chat_item = gtk.MenuItem(_('using account %s') % account,
-					False)
-				new_chat_sub_menu.append(new_chat_item)
-				new_chat_item.connect('activate',
-					self.on_new_chat_menuitem_activate,	account)
 
 			add_new_contact_menuitem.set_submenu(add_sub_menu)
 			add_sub_menu.show_all()
@@ -823,7 +914,7 @@ class RosterWindow:
 
 		elif connected_accounts == 1: # user has only one account
 			for account in gajim.connections:
-				if gajim.connections[account].connected > 1: # THE connected account
+				if gajim.account_is_connected(account): # THE connected account
 					# gc
 					self.add_bookmarks_list(gc_sub_menu, account)
 					# add
@@ -851,22 +942,30 @@ class RosterWindow:
 		
 		if connected_accounts == 0:
 			# no connected accounts, make the menuitems insensitive
-			new_chat_menuitem.set_sensitive(False)
-			join_gc_menuitem.set_sensitive(False)
-			add_new_contact_menuitem.set_sensitive(False)
-			service_disco_menuitem.set_sensitive(False)
+			for item in [new_chat_menuitem, join_gc_menuitem,\
+					add_new_contact_menuitem, service_disco_menuitem]:
+				item.set_sensitive(False)
 		else: # we have one or more connected accounts
-			new_chat_menuitem.set_sensitive(True)
-			join_gc_menuitem.set_sensitive(True)
-			add_new_contact_menuitem.set_sensitive(True)
-			service_disco_menuitem.set_sensitive(True)
+			for item in [new_chat_menuitem, join_gc_menuitem,\
+						add_new_contact_menuitem, service_disco_menuitem]:
+				item.set_sensitive(True)
+			
+			# disable some fields if only local account is there
+			if connected_accounts == 1:
+				for account in gajim.connections:
+					if gajim.account_is_connected(account) and \
+							gajim.connections[account].is_zeroconf:
+						for item in [join_gc_menuitem,\
+								add_new_contact_menuitem, service_disco_menuitem]:
+							item.set_sensitive(False)
+
 			# show the 'manage gc bookmarks' item
 			newitem = gtk.SeparatorMenuItem() # separator
 			gc_sub_menu.append(newitem)
 
 		connected_accounts_with_vcard = []
 		for account in gajim.connections:
-			if gajim.connections[account].connected > 1 and \
+			if gajim.account_is_connected(account) and \
 			gajim.connections[account].vcard_supported:
 				connected_accounts_with_vcard.append(account)
 		if len(connected_accounts_with_vcard) > 1:
@@ -894,11 +993,12 @@ class RosterWindow:
 		else:
 			profile_avatar_menuitem.set_sensitive(True)
 
-			newitem = gtk.ImageMenuItem(_('Manage Bookmarks...'))
+			newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
 			img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
 				gtk.ICON_SIZE_MENU)
 			newitem.set_image(img)
-			newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
+			newitem.connect('activate',
+				self.on_manage_bookmarks_menuitem_activate)
 			gc_sub_menu.append(newitem)
 			gc_sub_menu.show_all()
 			
@@ -917,7 +1017,11 @@ class RosterWindow:
 			advanced_menuitem_menu.show_all()
 		else: # user has *more* than one account : build advanced submenus
 			advanced_sub_menu = gtk.Menu()
+			accounts = [] # Put accounts in a list to sort them
 			for account in gajim.connections:
+				accounts.append(account)
+			accounts.sort()
+			for account in accounts:
 				advanced_item = gtk.MenuItem(_('for account %s') % account, False)
 				advanced_sub_menu.append(advanced_item)
 				advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
@@ -947,8 +1051,8 @@ class RosterWindow:
 		item.connect('activate', self.on_history_manager_menuitem_activate)
 		
 	def add_bookmarks_list(self, gc_sub_menu, account):
-		'''Print join new room item and bookmarks list for an account'''
-		item = gtk.MenuItem(_('_Join New Room'))
+		'''Show join new group chat item and bookmarks list for an account'''
+		item = gtk.MenuItem(_('_Join New Group Chat'))
 		item.connect('activate', self.on_join_gc_activate, account)
 		gc_sub_menu.append(item)
 
@@ -1069,24 +1173,32 @@ class RosterWindow:
 		contact.show = show
 		contact.status = status
 		if show in ('offline', 'error') and \
-		len(gajim.events.get_events(account, contact.jid)) == 0:
+		len(gajim.events.get_events(account, contact.get_full_jid())) == 0:
 			if len(contact_instances) > 1:
 				# if multiple resources
 				gajim.contacts.remove_contact(account, contact)
 		self.remove_contact(contact, account)
 		self.add_contact_to_roster(contact.jid, account)
 		# print status in chat window and update status/GPG image
-		for j in (contact.jid, contact.get_full_jid()):
+		jid_list = [contact.jid]
+		if contact.get_full_jid() != contact.jid:
+			jid_list.append(contact.get_full_jid())
+		for j in jid_list:
 			if gajim.interface.msg_win_mgr.has_window(j, account):
 				jid = contact.jid
 				win = gajim.interface.msg_win_mgr.get_window(j, account)
 				ctrl = win.get_control(j, account)
+				ctrl.contact = contact
 				ctrl.update_ui()
 				win.redraw_tab(ctrl)
 
 				name = contact.get_shown_name()
-				if contact.resource != '':
+
+				# if multiple resources (or second one disconnecting)
+				if (len(contact_instances) > 1 or (len(contact_instances) == 1 and \
+				show in ('offline', 'error'))) and contact.resource != '':
 					name += '/' + contact.resource
+				
 				uf_show = helpers.get_uf_show(show)
 				if status: 
 					ctrl.print_conversation(_('%s is now %s (%s)') % (name, uf_show,
@@ -1098,6 +1210,14 @@ class RosterWindow:
 				account, contact.jid):
 					ctrl.draw_banner()
 
+		if not contact.groups:
+			self.draw_group(_('General'), account)
+		else:
+			for group in contact.groups:
+				self.draw_group(group, account)
+
+		self.draw_account(account) 
+
 	def on_info(self, widget, contact, account):
 		'''Call vcard_information_window class to display contact's information'''
 		info = gajim.interface.instances[account]['infos']
@@ -1106,6 +1226,19 @@ class RosterWindow:
 		else:
 			info[contact.jid] = vcard.VcardWindow(contact, account)
 
+	def on_info_zeroconf(self, widget, contact, account):
+		info = gajim.interface.instances[account]['infos']
+		if info.has_key(contact.jid):
+			info[contact.jid].window.present()
+		else:
+			contact = gajim.contacts.get_first_contact_from_jid(account, 
+							contact.jid)
+			if contact.show in ('offline', 'error'):
+				# don't show info on offline contacts
+				return
+			info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
+
+
 	def show_tooltip(self, contact):
 		pointer = self.tree.get_pointer()
 		props = self.tree.get_path_at_pos(pointer[0], pointer[1])
@@ -1149,8 +1282,15 @@ class RosterWindow:
 				if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
 					self.tooltip.id = row
 					contacts = gajim.contacts.get_contact(account, jid)
+					connected_contacts = []
+					for c in contacts:
+						if c.show not in ('offline', 'error'):
+							connected_contacts.append(c)
+					if not connected_contacts:
+						# no connected contacts, show the ofline one
+						connected_contacts = contacts
 					self.tooltip.timeout = gobject.timeout_add(500,
-						self.show_tooltip, contacts)
+						self.show_tooltip, connected_contacts)
 			elif model[iter][C_TYPE] == 'account':
 				# we're on an account entry in the roster
 				account = model[iter][C_ACCOUNT].decode('utf-8')
@@ -1164,13 +1304,18 @@ class RosterWindow:
 				contacts = []
 				connection = gajim.connections[account]
 				# get our current contact info
-				contact = gajim.contacts.create_contact(jid = jid, name = account,
-					show = connection.get_status(), sub = '',
+
+				nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
+					accounts = [account])
+				account_name = account
+				if gajim.account_is_connected(account):
+					account_name += '(%s/%s)' % (repr(nbr_on), repr(nbr_total))
+				contact = gajim.contacts.create_contact(jid = jid,
+					name = account_name, show = connection.get_status(), sub = '',
 					status = connection.status,
 					resource = gajim.config.get_per('accounts', connection.name,
 						'resource'),
-					priority = gajim.config.get_per('accounts', connection.name,
-						'priority'),
+					priority = connection.priority,
 					keyID = gajim.config.get_per('accounts', connection.name,
 						'keyid'))
 				contacts.append(contact)
@@ -1205,7 +1350,7 @@ class RosterWindow:
 		gajim.connections[account].request_register_agent_info(contact.jid)
 
 	def on_remove_agent(self, widget, list_):
-		'''When an agent is requested to log in or off. list_ is a list of
+		'''When an agent is requested to be removed. list_ is a list of
 		(contact, account) tuple'''
 		for (contact, account) in list_:
 			if gajim.config.get_per('accounts', account, 'hostname') == \
@@ -1227,6 +1372,17 @@ class RosterWindow:
 				gajim.contacts.remove_jid(account, contact.jid)
 				gajim.contacts.remove_contact(account, contact)
 
+		# Check if there are unread events from some contacts
+		has_unread_events = False
+		for (contact, account) in list_:
+			for jid in gajim.events.get_events(account):
+				if jid.endswith(contact.jid):
+					has_unread_events = True
+					break
+		if has_unread_events:
+			dialogs.ErrorDialog(_('You have unread messages'),
+				_('You must read them before removing this transport.'))
+			return
 		if len(list_) == 1:
 			pritext = _('Transport "%s" will be removed') % contact.jid
 			sectext = _('You will no longer be able to send and receive messages to contacts from this transport.')
@@ -1269,6 +1425,22 @@ class RosterWindow:
 		model[iter][C_EDITABLE] = True # set 'editable' to True
 		self.tree.set_cursor(path, self.tree.get_column(0), True)
 
+	def on_remove_group_item_activated(self, widget, group, account):
+		dlg = dialogs.ConfirmationDialogCheck(_('Remove Group'),
+			_('Do you want to remove the group %s from the roster ?' % group),
+			_('Remove also all contacts in this group from your roster'))
+		dlg.set_default_response(gtk.BUTTONS_OK_CANCEL)
+		response = dlg.run()
+		if response == gtk.RESPONSE_OK:
+			for contact in gajim.contacts.get_contacts_from_group(account, group):
+				if not dlg.is_checked():
+					self.remove_contact_from_group(account, contact, group)
+					gajim.connections[account].update_contact(contact.jid,
+						contact.name, contact.groups)
+					self.add_contact_to_roster(contact.jid, account)
+				else:
+					gajim.connections[account].unsubscribe(contact.jid) 
+		
 	def on_assign_pgp_key(self, widget, contact, account):
 		attached_keys = gajim.config.get_per('accounts', account,
 			'attached_gpg_keys').split()
@@ -1316,6 +1488,8 @@ class RosterWindow:
 	contact = None):
 		if contact is None:
 			dialogs.SingleMessageWindow(account, action = 'send')
+		elif type(contact) == type([]):
+			dialogs.SingleMessageWindow(account, contact, 'send')
 		else:
 			jid = contact.jid
 			if contact.jid == gajim.get_jid_from_account(account):
@@ -1333,13 +1507,129 @@ class RosterWindow:
 		'''Make contact's popup menu'''
 		model = self.tree.get_model()
 		jid = model[iter][C_JID].decode('utf-8')
-		path = model.get_path(iter)
+		tree_path = model.get_path(iter)
 		account = model[iter][C_ACCOUNT].decode('utf-8')
 		our_jid = jid == gajim.get_jid_from_account(account)
 		contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
 		if not contact:
 			return
 
+		if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+			xml = gtkgui_helpers.get_glade('zeroconf_contact_context_menu.glade')
+			zeroconf_contact_context_menu = xml.get_widget('zeroconf_contact_context_menu')
+			
+			start_chat_menuitem = xml.get_widget('start_chat_menuitem')
+			rename_menuitem = xml.get_widget('rename_menuitem')
+			edit_groups_menuitem = xml.get_widget('edit_groups_menuitem')
+			# separator has with send file, assign_openpgp_key_menuitem, etc..
+			above_send_file_separator = xml.get_widget('above_send_file_separator')
+			send_file_menuitem = xml.get_widget('send_file_menuitem')
+			assign_openpgp_key_menuitem = xml.get_widget(
+				'assign_openpgp_key_menuitem')
+			add_special_notification_menuitem = xml.get_widget(
+				'add_special_notification_menuitem')
+			
+			add_special_notification_menuitem.hide()
+			add_special_notification_menuitem.set_no_show_all(True)
+
+			if not our_jid:
+				# add a special img for rename menuitem
+				path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
+					'kbd_input.png')
+				img = gtk.Image()
+				img.set_from_file(path_to_kbd_input_img)
+				rename_menuitem.set_image(img)
+
+			above_information_separator = xml.get_widget(
+				'above_information_separator')
+
+			# skip a separator
+			information_menuitem = xml.get_widget('information_menuitem')
+			history_menuitem = xml.get_widget('history_menuitem')
+
+			contacts = gajim.contacts.get_contact(account, jid)
+			if len(contacts) > 1: # sevral resources
+				sub_menu = gtk.Menu()
+				start_chat_menuitem.set_submenu(sub_menu)
+
+				iconset = gajim.config.get('iconset')
+				path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
+				for c in contacts:
+					# icon MUST be different instance for every item
+					state_images = self.load_iconset(path)
+					item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')')
+					icon_name = helpers.get_icon_name_to_show(c, account)
+					icon = state_images[icon_name]
+					item.set_image(icon)
+					sub_menu.append(item)
+					item.connect('activate', self.on_open_chat_window, c, account,
+						c.resource)
+
+			else: # one resource
+				start_chat_menuitem.connect('activate',
+					self.on_roster_treeview_row_activated, tree_path)
+
+			if contact.resource:
+				send_file_menuitem.connect('activate',
+					self.on_send_file_menuitem_activate, account, contact)
+			else: # if we do not have resource we cannot send file
+				send_file_menuitem.hide()
+				send_file_menuitem.set_no_show_all(True)
+
+			rename_menuitem.connect('activate', self.on_rename, iter, tree_path)
+			if contact.show in ('offline', 'error'):
+				information_menuitem.set_sensitive(False)
+				send_file_menuitem.set_sensitive(False)
+			else:
+				information_menuitem.connect('activate', self.on_info_zeroconf, contact,
+					account)
+			history_menuitem.connect('activate', self.on_history, contact,
+				account)
+
+			if _('Not in Roster') not in contact.groups:
+				#contact is in normal group
+				edit_groups_menuitem.set_no_show_all(False)
+				assign_openpgp_key_menuitem.set_no_show_all(False)
+				edit_groups_menuitem.connect('activate', self.on_edit_groups, [(
+					contact,account)])
+
+				if gajim.config.get('usegpg'):
+					assign_openpgp_key_menuitem.connect('activate',
+						self.on_assign_pgp_key, contact, account)
+
+			else: # contact is in group 'Not in Roster'
+				edit_groups_menuitem.hide()
+				edit_groups_menuitem.set_no_show_all(True)
+				# hide first of the two consecutive separators
+				above_send_file_separator.hide()
+				above_send_file_separator.set_no_show_all(True)
+				assign_openpgp_key_menuitem.hide()
+				assign_openpgp_key_menuitem.set_no_show_all(True)
+
+			# Remove many items when it's self contact row
+			if our_jid:
+				for menuitem in (rename_menuitem, edit_groups_menuitem,
+				above_information_separator):
+					menuitem.set_no_show_all(True)
+					menuitem.hide()
+
+			# Unsensitive many items when account is offline
+			if gajim.connections[account].connected < 2:
+				for widget in [start_chat_menuitem,	rename_menuitem, edit_groups_menuitem, send_file_menuitem]:
+					widget.set_sensitive(False)
+
+			event_button = gtkgui_helpers.get_possible_button_event(event)
+
+			zeroconf_contact_context_menu.attach_to_widget(self.tree, None)
+			zeroconf_contact_context_menu.connect('selection-done',
+				gtkgui_helpers.destroy_widget)
+			zeroconf_contact_context_menu.show_all()
+			zeroconf_contact_context_menu.popup(None, None, None, event_button,
+				event.time)
+			return
+
+
+		# normal account
 		xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade')
 		roster_contact_context_menu = xml.get_widget(
 			'roster_contact_context_menu')
@@ -1371,6 +1661,10 @@ class RosterWindow:
 			img.set_from_file(path_to_kbd_input_img)
 			rename_menuitem.set_image(img)
 
+		muc_icon = self.load_icon('muc_active')
+		if muc_icon:
+			invite_menuitem.set_image(muc_icon)
+
 		above_subscription_separator = xml.get_widget(
 			'above_subscription_separator')
 		subscription_menuitem = xml.get_widget('subscription_menuitem')
@@ -1434,7 +1728,7 @@ class RosterWindow:
 
 		submenu = gtk.Menu()
 		invite_menuitem.set_submenu(submenu)
-		menuitem = gtk.ImageMenuItem(_('_New room'))
+		menuitem = gtk.ImageMenuItem(_('_New group chat'))
 		icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
 		menuitem.set_image(icon)
 		menuitem.connect('activate', self.on_invite_to_new_room, [(contact,
@@ -1461,7 +1755,7 @@ class RosterWindow:
 				menuitem.connect('activate', self.on_invite_to_room,
 					[(contact, account)], room_jid, acct)
 				submenu.append(menuitem)
-		rename_menuitem.connect('activate', self.on_rename, iter, path)
+		rename_menuitem.connect('activate', self.on_rename, iter, tree_path)
 		remove_from_roster_menuitem.connect('activate', self.on_req_usub,
 			[(contact, account)])
 		information_menuitem.connect('activate', self.on_info, contact,
@@ -1557,9 +1851,9 @@ class RosterWindow:
 				try:
 					gajim.interface.instances[account]['join_gc'] = \
 						dialogs.JoinGroupchatWindow(account,
-							server = gajim.connections[account].muc_jid[type_],
+							gajim.connections[account].muc_jid[type_],
 							automatic = {'invities': jid_list})
-				except RuntimeError:
+				except GajimGeneralException:
 					continue
 				break
 
@@ -1609,7 +1903,7 @@ class RosterWindow:
 		else:
 
 			sub_menu = gtk.Menu()
-			menuitem = gtk.ImageMenuItem(_('_New room'))
+			menuitem = gtk.ImageMenuItem(_('_New group chat'))
 			icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
 			menuitem.set_image(icon)
 			menuitem.connect('activate', self.on_invite_to_new_room, list_)
@@ -1645,6 +1939,19 @@ class RosterWindow:
 		edit_groups_item = gtk.MenuItem(_('Edit _Groups'))
 		menu.append(edit_groups_item)
 		edit_groups_item.connect('activate', self.on_edit_groups, list_)
+		
+		account = None
+		for (contact, current_account) in list_:
+			# check that we use the same account for every sender
+			if account is not None and account != current_account:
+				account = None
+				break
+			account = current_account
+		if account is not None:
+			send_group_message_item = gtk.MenuItem(_('Send Group M_essage'))
+			menu.append(send_group_message_item)
+			send_group_message_item.connect('activate',
+				self.on_send_single_message_menuitem_activate, account, list_)
 
 		# unsensitive if one account is not connected
 		if one_account_offline:
@@ -1663,24 +1970,58 @@ class RosterWindow:
 		path = model.get_path(iter)
 		group = model[iter][C_JID].decode('utf-8')
 		account = model[iter][C_ACCOUNT].decode('utf-8')
-		if group in helpers.special_groups + (_('General'),):
-			return
 
 		menu = gtk.Menu()
+		if not group in helpers.special_groups + (_('General'),):
 
-		rename_item = gtk.ImageMenuItem(_('Re_name'))
-		# add a special img for rename menuitem
-		path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
-			'kbd_input.png')
-		img = gtk.Image()
-		img.set_from_file(path_to_kbd_input_img)
-		rename_item.set_image(img)
-		menu.append(rename_item)
-		rename_item.connect('activate', self.on_rename, iter, path)
+			rename_item = gtk.ImageMenuItem(_('Re_name'))
+			# add a special img for rename menuitem
+			path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
+				'kbd_input.png')
+			img = gtk.Image()
+			img.set_from_file(path_to_kbd_input_img)
+			rename_item.set_image(img)
+			menu.append(rename_item)
+			rename_item.connect('activate', self.on_rename, iter, path)
+
+			# Remove group
+			remove_item = gtk.ImageMenuItem(_('_Remove from Roster'))
+			icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
+			remove_item.set_image(icon)
+			menu.append(remove_item)
+			remove_item.connect('activate', self.on_remove_group_item_activated,
+				group, account)
+
+			# unsensitive if account is not connected
+			if gajim.connections[account].connected < 2:
+				rename_item.set_sensitive(False)
+		send_group_message_item = gtk.MenuItem(_('Send Group M_essage'))
 
-		# unsensitive if account is not connected
-		if gajim.connections[account].connected < 2:
-			rename_item.set_sensitive(False)
+		send_group_message_submenu = gtk.Menu()
+		send_group_message_item.set_submenu(send_group_message_submenu)
+		menu.append(send_group_message_item)
+
+		group_message_to_all_item = gtk.MenuItem(_('To all users'))
+		send_group_message_submenu.append(group_message_to_all_item)
+
+		group_message_to_all_online_item = gtk.MenuItem(_('To all online users'))
+		send_group_message_submenu.append(group_message_to_all_online_item)
+		list_ = [] # list of (jid, account) tuples
+		list_online = [] # list of (jid, account) tuples
+
+		group = model[iter][C_NAME]
+		for jid in gajim.contacts.get_jid_list(account):
+			contact = gajim.contacts.get_contact_with_highest_priority(account,
+					jid)
+			if group in contact.groups or (contact.groups == [] and group == _('General')):
+				if contact.show not in ('offline', 'error'):
+					list_online.append((contact, account))
+				list_.append((contact, account))
+
+		group_message_to_all_online_item.connect('activate',
+			self.on_send_single_message_menuitem_activate, account, list_online)
+		group_message_to_all_item.connect('activate',
+			self.on_send_single_message_menuitem_activate, account, list_)
 
 		event_button = gtkgui_helpers.get_possible_button_event(event)
 
@@ -1695,7 +2036,6 @@ class RosterWindow:
 		jid = model[iter][C_JID].decode('utf-8')
 		path = model.get_path(iter)
 		account = model[iter][C_ACCOUNT].decode('utf-8')
-		is_connected = gajim.connections[account].connected > 1
 		contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
 		menu = gtk.Menu()
 
@@ -1704,7 +2044,8 @@ class RosterWindow:
 		item.set_image(icon)
 		menu.append(item)
 		show = contact.show
-		if (show != 'offline' and show != 'error') or not is_connected:
+		if (show != 'offline' and show != 'error') or\
+			gajim.account_is_disconnected(account):
 			item.set_sensitive(False)
 		item.connect('activate', self.on_agent_logging, jid, None, account)
 
@@ -1712,7 +2053,8 @@ class RosterWindow:
 		icon = gtk.image_new_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
 		item.set_image(icon)
 		menu.append(item)
-		if show in ('offline', 'error') or not is_connected:
+		if show in ('offline', 'error') or gajim.account_is_disconnected(
+			account):
 			item.set_sensitive(False)
 		item.connect('activate', self.on_agent_logging, jid, 'unavailable',
 			account)
@@ -1725,7 +2067,7 @@ class RosterWindow:
 		item.set_image(icon)
 		menu.append(item)
 		item.connect('activate', self.on_edit_agent, contact, account)
-		if not is_connected:
+		if gajim.account_is_disconnected(account):
 			item.set_sensitive(False)
 
 		item = gtk.ImageMenuItem(_('Execute Command...'))
@@ -1746,7 +2088,7 @@ class RosterWindow:
 		item.set_image(img)
 		menu.append(item)
 		item.connect('activate', self.on_rename, iter, path)
-		if not is_connected:
+		if gajim.account_is_disconnected(account):
 			item.set_sensitive(False)
 
 		item = gtk.ImageMenuItem(_('_Remove from Roster'))
@@ -1754,7 +2096,7 @@ class RosterWindow:
 		item.set_image(icon)
 		menu.append(item)
 		item.connect('activate', self.on_remove_agent, [(contact, account)])
-		if not is_connected:
+		if gajim.account_is_disconnected(account):
 			item.set_sensitive(False)
 
 		event_button = gtkgui_helpers.get_possible_button_event(event)
@@ -1772,13 +2114,17 @@ class RosterWindow:
 			gajim.interface.instances[account]['account_modification'] = \
 				config.AccountModificationWindow(account)
 
-	def on_open_gmail_inbox(self, widget, account):
-		if gajim.config.get_per('accounts', account, 'savepass'):
-			url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\
-			(urllib.quote(gajim.config.get_per('accounts', account, 'name')),
-			urllib.quote(gajim.config.get_per('accounts', account, 'password')))
+	def on_zeroconf_properties(self, widget, account):
+		if gajim.interface.instances.has_key('zeroconf_properties'):
+			gajim.interface.instances['zeroconf_properties'].\
+			window.present()
 		else:
-			url = ('http://mail.google.com/')
+			gajim.interface.instances['zeroconf_properties'] = \
+				config.ZeroconfPropertiesWindow()
+
+	def on_open_gmail_inbox(self, widget, account):
+		url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote(
+			gajim.config.get_per('accounts', account, 'name'))
 		helpers.launch_browser_mailer('url', url)
 
 	def on_change_status_message_activate(self, widget, account):
@@ -1792,82 +2138,132 @@ class RosterWindow:
 		# we have to create our own set of icons for the menu
 		# using self.jabber_status_images is poopoo
 		iconset = gajim.config.get('iconset')
-		if not iconset:
-			iconset = DEFAULT_ICONSET
 		path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
 		state_images = self.load_iconset(path)
 
-		xml = gtkgui_helpers.get_glade('account_context_menu.glade')
-		account_context_menu = xml.get_widget('account_context_menu')
+		if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+			xml = gtkgui_helpers.get_glade('account_context_menu.glade')
+			account_context_menu = xml.get_widget('account_context_menu')
+
+			status_menuitem = xml.get_widget('status_menuitem')
+			join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
+			open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
+			new_message_menuitem = xml.get_widget('new_message_menuitem')
+			add_contact_menuitem = xml.get_widget('add_contact_menuitem')
+			service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
+			execute_command_menuitem = xml.get_widget('execute_command_menuitem')
+			edit_account_menuitem = xml.get_widget('edit_account_menuitem')
+			sub_menu = gtk.Menu()
+			status_menuitem.set_submenu(sub_menu)
 
-		status_menuitem = xml.get_widget('status_menuitem')
-		join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
-		open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
-		new_message_menuitem = xml.get_widget('new_message_menuitem')
-		add_contact_menuitem = xml.get_widget('add_contact_menuitem')
-		service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
-		execute_command_menuitem = xml.get_widget('execute_command_menuitem')
-		edit_account_menuitem = xml.get_widget('edit_account_menuitem')
-		sub_menu = gtk.Menu()
-		status_menuitem.set_submenu(sub_menu)
+			for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+				uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+				item = gtk.ImageMenuItem(uf_show)
+				icon = state_images[show]
+				item.set_image(icon)
+				sub_menu.append(item)
+				item.connect('activate', self.change_status, account, show)
 
-		for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
-			uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+			item = gtk.SeparatorMenuItem()
+			sub_menu.append(item)
+
+			item = gtk.ImageMenuItem(_('_Change Status Message'))
+			path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
+			img = gtk.Image()
+			img.set_from_file(path)
+			item.set_image(img)
+			sub_menu.append(item)
+			item.connect('activate', self.on_change_status_message_activate, account)
+			if gajim.connections[account].connected < 2:
+				item.set_sensitive(False)
+
+			uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
 			item = gtk.ImageMenuItem(uf_show)
-			icon = state_images[show]
+			icon = state_images['offline']
 			item.set_image(icon)
 			sub_menu.append(item)
-			item.connect('activate', self.change_status, account, show)
+			item.connect('activate', self.change_status, account, 'offline')
 
-		item = gtk.SeparatorMenuItem()
-		sub_menu.append(item)
-
-		item = gtk.ImageMenuItem(_('_Change Status Message'))
-		path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
-		img = gtk.Image()
-		img.set_from_file(path)
-		item.set_image(img)
-		sub_menu.append(item)
-		item.connect('activate', self.on_change_status_message_activate, account)
-		if gajim.connections[account].connected < 2:
-			item.set_sensitive(False)
+			if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
+				open_gmail_inbox_menuitem.set_no_show_all(True)
+				open_gmail_inbox_menuitem.hide()
+			else:
+				open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
+					account)
 
-		uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
-		item = gtk.ImageMenuItem(uf_show)
-		icon = state_images['offline']
-		item.set_image(icon)
-		sub_menu.append(item)
-		item.connect('activate', self.change_status, account, 'offline')
+			edit_account_menuitem.connect('activate', self.on_edit_account, account)
+			add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
+			service_discovery_menuitem.connect('activate',
+				self.on_service_disco_menuitem_activate, account)
+			hostname = gajim.config.get_per('accounts', account, 'hostname')
+			contact = gajim.contacts.create_contact(jid = hostname) # Fake contact
+			execute_command_menuitem.connect('activate',
+				self.on_execute_command, contact, account)
+			
+			gc_sub_menu = gtk.Menu() # gc is always a submenu
+			join_group_chat_menuitem.set_submenu(gc_sub_menu)
+			self.add_bookmarks_list(gc_sub_menu, account)
+			new_message_menuitem.connect('activate',
+				self.on_new_message_menuitem_activate, account)
 
-		if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
-			open_gmail_inbox_menuitem.set_no_show_all(True)
-			open_gmail_inbox_menuitem.hide()
+			# make some items insensitive if account is offline
+			if gajim.connections[account].connected < 2:
+				for widget in [add_contact_menuitem, service_discovery_menuitem,
+				join_group_chat_menuitem, new_message_menuitem,
+				execute_command_menuitem]:
+					widget.set_sensitive(False)
 		else:
-			open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
-				account)
+			xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade')
+			account_context_menu = xml.get_widget('zeroconf_context_menu')
 
-		edit_account_menuitem.connect('activate', self.on_edit_account, account)
-		add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
-		service_discovery_menuitem.connect('activate',
-			self.on_service_disco_menuitem_activate, account)
-		hostname = gajim.config.get_per('accounts', account, 'hostname')
-		contact = gajim.contacts.create_contact(jid = hostname) # Fake contact
-		execute_command_menuitem.connect('activate',
-			self.on_execute_command, contact, account)
+			status_menuitem = xml.get_widget('status_menuitem')
+			#join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
+			new_message_menuitem = xml.get_widget('new_message_menuitem')
+			zeroconf_properties_menuitem = xml.get_widget('zeroconf_properties_menuitem')
+			sub_menu = gtk.Menu()
+			status_menuitem.set_submenu(sub_menu)
 
-		gc_sub_menu = gtk.Menu() # gc is always a submenu
-		join_group_chat_menuitem.set_submenu(gc_sub_menu)
-		self.add_bookmarks_list(gc_sub_menu, account)
-		new_message_menuitem.connect('activate',
-			self.on_new_message_menuitem_activate, account)
+			for show in ('online', 'away', 'dnd', 'invisible'):
+				uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+				item = gtk.ImageMenuItem(uf_show)
+				icon = state_images[show]
+				item.set_image(icon)
+				sub_menu.append(item)
+				item.connect('activate', self.change_status, account, show)
 
-		# make some items insensitive if account is offline
-		if gajim.connections[account].connected < 2:
-			for widget in [add_contact_menuitem, service_discovery_menuitem,
-			join_group_chat_menuitem, new_message_menuitem,
-			execute_command_menuitem]:
-				widget.set_sensitive(False)
-		
+			item = gtk.SeparatorMenuItem()
+			sub_menu.append(item)
+
+			item = gtk.ImageMenuItem(_('_Change Status Message'))
+			path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
+			img = gtk.Image()
+			img.set_from_file(path)
+			item.set_image(img)
+			sub_menu.append(item)
+			item.connect('activate', self.on_change_status_message_activate, account)
+			if gajim.connections[account].connected < 2:
+				item.set_sensitive(False)
+
+			uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
+			item = gtk.ImageMenuItem(uf_show)
+			icon = state_images['offline']
+			item.set_image(icon)
+			sub_menu.append(item)
+			item.connect('activate', self.change_status, account, 'offline')
+
+			zeroconf_properties_menuitem.connect('activate', self.on_zeroconf_properties, account)			
+			#gc_sub_menu = gtk.Menu() # gc is always a submenu
+			#join_group_chat_menuitem.set_submenu(gc_sub_menu)
+			#self.add_bookmarks_list(gc_sub_menu, account)
+			#new_message_menuitem.connect('activate',
+			#	self.on_new_message_menuitem_activate, account)
+
+			# make some items insensitive if account is offline
+			#if gajim.connections[account].connected < 2:
+			#	for widget in [join_group_chat_menuitem, new_message_menuitem]:
+			#		widget.set_sensitive(False)
+			#	new_message_menuitem.set_sensitive(False)
+					
 		return account_context_menu
 
 	def make_account_menu(self, event, iter):
@@ -1875,13 +2271,11 @@ class RosterWindow:
 		model = self.tree.get_model()
 		account = model[iter][C_ACCOUNT].decode('utf-8')
 
-		if account != 'all':
+		if account != 'all': # not in merged mode
 			menu = self.build_account_menu(account)
 		else:
 			menu = gtk.Menu()
 			iconset = gajim.config.get('iconset')
-			if not iconset:
-				iconset = DEFAULT_ICONSET
 			path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
 			accounts = [] # Put accounts in a list to sort them
 			for account in gajim.connections:
@@ -1980,6 +2374,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			if not len(list_of_paths):
 				return
 			type = model[list_of_paths[0]][C_TYPE]
+			account = model[list_of_paths[0]][C_ACCOUNT]
 			list_ = []
 			for path in list_of_paths:
 				if model[path][C_TYPE] != type:
@@ -1989,7 +2384,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				contact = gajim.contacts.get_contact_with_highest_priority(account,
 					jid)
 				list_.append((contact, account))
-			if type in ('account', 'group', 'self_contact'):
+			if type in ('account', 'group', 'self_contact') or account == gajim.ZEROCONF_ACC_NAME:
 				return
 			if type == 'contact':
 				self.on_req_usub(widget, list_)
@@ -2083,7 +2478,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 					return True
 				for acct in gajim.connections:
 					if not gajim.config.get_per('accounts', acct,
-						'sync_with_global_status'):
+					'sync_with_global_status'):
 						continue
 					current_show = gajim.SHOW_LIST[gajim.connections[acct].connected]
 					self.send_status(acct, current_show, message)
@@ -2141,7 +2536,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 							account)
 						ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid,
 							account)
-						msg_win.remove_tab(ctrl)
+						msg_win.remove_tab(ctrl, msg_win.CLOSE_CLOSE_BUTTON)
 					else:
 						need_readd = True
 				if need_readd:
@@ -2214,8 +2609,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 					gajim.connections[account].password = passphrase
 					if save:
 						gajim.config.set_per('accounts', account, 'savepass', True)
-						gajim.config.set_per('accounts', account, 'password',
-							passphrase)
+						passwords.save_password(account, passphrase)
 
 			keyid = None
 			use_gpg_agent = gajim.config.get('use_gpg_agent')
@@ -2270,7 +2664,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			if gc_control.account == account:
 				gajim.connections[account].send_gc_status(gc_control.nick,
 					gc_control.room_jid, status, txt)
-		if gajim.connections[account].connected > 1:
+		if gajim.account_is_connected(account):
 			if status == 'online' and gajim.interface.sleeper.getState() != \
 			common.sleepy.STATE_UNKNOWN:
 				gajim.sleeper_state[account] = 'online'
@@ -2287,6 +2681,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			show == 'invisible':
 			return ''
 		dlg = dialogs.ChangeStatusMessageDialog(show)
+		dlg.window.present() # show it on current workspace
 		message = dlg.run()
 		return message
 
@@ -2337,14 +2732,16 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			dlg = dialogs.ChangeStatusMessageDialog(status)
 			message = dlg.run()
 			if message is not None: # None if user pressed Cancel
-				for acct in accounts:
-					if not gajim.config.get_per('accounts', acct,
+				for account in accounts:
+					if not gajim.config.get_per('accounts', account,
 						'sync_with_global_status'):
 						continue
-					current_show = gajim.SHOW_LIST[gajim.connections[acct].connected]
-					self.send_status(acct, current_show, message)
+					current_show = gajim.SHOW_LIST[
+						gajim.connections[account].connected]
+					self.send_status(account, current_show, message)
 			self.combobox_callback_active = False
-			self.status_combobox.set_active(self.previous_status_combobox_active)
+			self.status_combobox.set_active(
+				self.previous_status_combobox_active)
 			self.combobox_callback_active = True
 			return
 		# we are about to change show, so save this new show so in case
@@ -2354,13 +2751,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		connected_accounts = gajim.get_number_of_connected_accounts()
 		if status == 'invisible':
 			bug_user = False
-			for acct in accounts:
-				if connected_accounts < 1 or gajim.connections[acct].connected > 1:
-					if not gajim.config.get_per('accounts', acct,
+			for account in accounts:
+				if connected_accounts < 1 or gajim.account_is_connected(account):
+					if not gajim.config.get_per('accounts', account,
 							'sync_with_global_status'):
 						continue
 					# We're going to change our status to invisible
-					if self.connected_rooms(acct):
+					if self.connected_rooms(account):
 						bug_user = True
 						break
 			if bug_user:
@@ -2380,17 +2777,58 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				global_sync_accounts.append(acct)
 		global_sync_connected_accounts = gajim.get_number_of_connected_accounts(
 			global_sync_accounts)
-		for acct in accounts:
-			if not gajim.config.get_per('accounts', acct, 'sync_with_global_status'):
+		for account in accounts:
+			if not gajim.config.get_per('accounts', account,
+			'sync_with_global_status'):
 				continue
 			# we are connected (so we wanna change show and status)
 			# or no account is connected and we want to connect with new show and status
 
 			if not global_sync_connected_accounts > 0 or \
-			gajim.connections[acct].connected > 1:
-				self.send_status(acct, status, message)
+			gajim.account_is_connected(account):
+				self.send_status(account, status, message)
 		self.update_status_combobox()
 
+	## enable setting status msg from currently playing music track
+	def enable_syncing_status_msg_from_current_music_track(self, enabled):
+		'''if enabled is True, we listen to events from music players about
+		currently played music track, and we update our
+		status message accordinly'''
+		if not dbus_support.supported:
+			# do nothing if user doesn't have D-Bus bindings
+			return
+		if enabled:
+			if self._music_track_changed_signal is None:
+				listener = MusicTrackListener.get()
+				self._music_track_changed_signal = listener.connect(
+					'music-track-changed', self._music_track_changed)
+				track = listener.get_playing_track()
+				self._music_track_changed(listener, track)
+		else:
+			if self._music_track_changed_signal is not None:
+				listener = MusicTrackListener.get()
+				listener.disconnect(self._music_track_changed_signal)
+				self._music_track_changed_signal = None
+				self._music_track_changed(None, None)
+	
+	def _music_track_changed(self, unused_listener, music_track_info):
+		accounts = gajim.connections.keys()
+		if music_track_info is None:
+			status_message = ''
+		else:
+			status_message = _('♪ "%(title)s" by %(artist)s ♪') % \
+				{'title': music_track_info.title,
+					'artist': music_track_info.artist }
+		for account in accounts:
+			if not gajim.config.get_per('accounts', account,
+			'sync_with_global_status'):
+				continue
+			if not gajim.connections[account].connected:
+				continue
+			current_show = gajim.SHOW_LIST[gajim.connections[account].connected]
+			self.send_status(account, current_show, status_message)
+
+
 	def update_status_combobox(self):
 		# table to change index in connection.connected to index in combobox
 		table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
@@ -2404,14 +2842,25 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		if gajim.interface.systray_enabled:
 			gajim.interface.systray.change_status(show)
 
+	def set_account_status_icon(self, account):
+		status = gajim.connections[account].connected
+		model = self.tree.get_model()
+		accountIter = self.get_account_iter(account)
+		if not accountIter:
+			return
+		if not self.regroup:
+			show = gajim.SHOW_LIST[status]
+		else:	# accounts merged
+			show = helpers.get_global_show()
+		model[accountIter][C_IMG] = self.jabber_state_images['16'][show]
+
 	def on_status_changed(self, account, status):
 		'''the core tells us that our status has changed'''
 		if account not in gajim.contacts.get_accounts():
 			return
 		model = self.tree.get_model()
 		accountIter = self.get_account_iter(account)
-		if accountIter:
-			model[accountIter][0] = self.jabber_state_images['16'][status]
+		self.set_account_status_icon(account)
 		if status == 'offline':
 			if self.quit_on_next_offline > -1:
 				self.quit_on_next_offline -= 1
@@ -2459,20 +2908,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def new_chat_from_jid(self, account, jid):
 		jid = gajim.get_jid_without_resource(jid)
 		contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
-		no_contact = False
+		added_to_roster = False
 		if not contact:
-			no_contact = True
-			keyID = ''
-			attached_keys = gajim.config.get_per('accounts', account,
-				'attached_gpg_keys').split()
-			if jid in attached_keys:
-				keyID = attached_keys[attached_keys.index(jid) + 1]
-			contact = gajim.contacts.create_contact(jid = jid,
-				name = '', groups = [_('Not in Roster')],
-				show = 'not in roster', status = '', sub = 'none',
-				keyID = keyID)
-			gajim.contacts.add_contact(account, contact)
-			self.add_contact_to_roster(contact.jid, account)
+			added_to_roster = True
+			contact = self.add_to_not_in_the_roster(account, jid) 
 
 		if not gajim.interface.msg_win_mgr.has_window(contact.jid, account):
 			self.new_chat(contact, account)
@@ -2480,7 +2919,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		mw.set_active_tab(jid, account)
 		mw.window.present()
 		# For JEP-0172
-		if no_contact:
+		if added_to_roster:
 			mc = mw.get_control(jid, account)
 			mc.user_nick = gajim.nicks[account]
 
@@ -2496,11 +2935,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 
 	def on_message(self, jid, msg, tim, account, encrypted = False,
 			msg_type = '', subject = None, resource = '', msg_id = None,
-			user_nick = '', advanced_notif_num = None):
+			user_nick = '', advanced_notif_num = None, xhtml = None):
 		'''when we receive a message'''
 		contact = None
 		# if chat window will be for specific resource
 		resource_for_chat = resource
+		fjid = jid
 		# Try to catch the contact with correct resource
 		if resource:
 			fjid = jid + '/' + resource
@@ -2508,33 +2948,30 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		highest_contact = gajim.contacts.get_contact_with_highest_priority(
 			account, jid)
 		if not contact:
-			# Default to highest prio
-			fjid = jid
-			resource_for_chat = None
-			contact = highest_contact
+			# If there is another resource, it may be a message from an invisible
+			# resource
+			lcontact = gajim.contacts.get_contacts_from_jid(account, jid)
+			if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
+			lcontact[0].show != 'offline')) and jid.find('@') > 0:
+				contact = gajim.contacts.copy_contact(highest_contact)
+				contact.resource = resource
+				if resource:
+					fjid = jid + '/' + resource
+				contact.priority = 0
+				contact.show = 'offline'
+				contact.status = ''
+				gajim.contacts.add_contact(account, contact)
+
+			else:
+				# Default to highest prio
+				fjid = jid
+				resource_for_chat = None
+				contact = highest_contact
 		if not contact:
 			# contact is not in roster
-			keyID = ''
-			attached_keys = gajim.config.get_per('accounts', account,
-				'attached_gpg_keys').split()
-			if jid in attached_keys:
-				keyID = attached_keys[attached_keys.index(jid) + 1]
-			if user_nick:
-				nick = user_nick
-			else:
-				nick = jid.split('@')[0]
-			contact = gajim.contacts.create_contact(jid = jid,
-				name = nick, groups = [_('Not in Roster')],
-				show = 'not in roster', status = '', ask = 'none',
-				keyID = keyID, resource = resource)
-			gajim.contacts.add_contact(account, contact)
-			self.add_contact_to_roster(jid, account)
+			contact = self.add_to_not_in_the_roster(account, jid, user_nick)
 
-		iters = self.get_contact_iter(jid, account)
-		if iters:
-			path = self.tree.get_model().get_path(iters[0])
-		else:
-			path = None
+		path = self.get_path(jid, account) # Try to get line of contact in roster
 
 		# Look for a chat control that has the given resource
 		ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
@@ -2564,21 +3001,21 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			if msg_type == 'error':
 				typ = 'status'
 			ctrl.print_conversation(msg, typ, tim = tim, encrypted = encrypted,
-						subject = subject)
+						subject = subject, xhtml = xhtml)
 			if msg_id:
 				gajim.logger.set_read_messages([msg_id])
 			return
 
 		# We save it in a queue
 		type_ = 'chat'
+		event_type = 'message_received'
 		if msg_type == 'normal':
 			type_ = 'normal'
-		show_in_roster = notify.get_show_in_roster('message_received', account,
-			contact)
-		show_in_systray = notify.get_show_in_systray('message_received', account,
-			contact)
+			event_type = 'single_message_received'
+		show_in_roster = notify.get_show_in_roster(event_type, account, contact)
+		show_in_systray = notify.get_show_in_systray(event_type, account, contact)
 		event = gajim.events.create_event(type_, (msg, subject, msg_type, tim,
-			encrypted, resource, msg_id), show_in_roster = show_in_roster,
+			encrypted, resource, msg_id, xhtml), show_in_roster = show_in_roster,
 			show_in_systray = show_in_systray)
 		gajim.events.add_event(account, fjid, event)
 		if popup:
@@ -2595,21 +3032,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		else:
 			if no_queue: # We didn't have a queue: we change icons
 				self.draw_contact(jid, account)
-				# Redraw parent too
-				self.draw_parent_contact(jid, account)
 			self.show_title() # we show the * or [n]
-			if not path:
-				# contact is in roster but we curently don't see him online
-				# show him
-				self.add_contact_to_roster(jid, account)
-				iters = self.get_contact_iter(jid, account)
-				path = self.tree.get_model().get_path(iters[0])
-			# popup == False so we show awaiting event in roster
-			# show and select contact line in roster (even if he is not in roster) 
-			self.tree.expand_row(path[0:1], False)
-			self.tree.expand_row(path[0:2], False)
-			self.tree.scroll_to_cell(path)
-			self.tree.set_cursor(path)
+			# Show contact in roster (if he is invisible for example) and select
+			# line
+			self.show_and_select_path(path, jid, account)
 
 	def on_preferences_menuitem_activate(self, widget):
 		if gajim.interface.instances.has_key('preferences'):
@@ -2624,7 +3050,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		'''when the join gc menuitem is clicked, show the join gc window'''
 		invisible_show = gajim.SHOW_LIST.index('invisible')
 		if gajim.connections[account].connected == invisible_show:
-			dialogs.ErrorDialog(_('You cannot join a room while you are invisible')
+			dialogs.ErrorDialog(_('You cannot join a group chat while you are invisible')
 				)
 			return
 		if gajim.interface.instances[account].has_key('join_gc'):
@@ -2634,7 +3060,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			try:
 				gajim.interface.instances[account]['join_gc'] = \
 					dialogs.JoinGroupchatWindow(account)
-			except RuntimeError:
+			except GajimGeneralException:
 				pass
 
 	def on_new_message_menuitem_activate(self, widget, account):
@@ -2664,6 +3090,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		else:
 			gajim.interface.instances['file_transfers'].window.show_all()
 
+	def on_show_transports_menuitem_activate(self, widget):
+		gajim.config.set('show_transports_group', widget.get_active())
+		self.draw_roster()
+
 	def on_manage_bookmarks_menuitem_activate(self, widget):
 		config.ManageBookmarksWindow()
 		
@@ -2678,11 +3108,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			else:
 				w.window.destroy()
 	
-	def close_all(self, account):
-		'''close all the windows from an account'''
+	def close_all(self, account, force = False):
+		'''close all the windows from an account
+		if force is True, do not ask confirmation before closing chat/gc windows
+		'''
 		self.close_all_from_dict(gajim.interface.instances[account])
 		for ctrl in gajim.interface.msg_win_mgr.get_controls(acct = account):
-			ctrl.parent_win.remove_tab(ctrl)
+			ctrl.parent_win.remove_tab(ctrl, force = force)
 
 	def on_roster_window_delete_event(self, widget, event):
 		'''When we want to close the window'''
@@ -2850,6 +3282,11 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			gajim.interface.remove_first_event(account, jid, event.type_)
 			ft.show_completed(jid, data)
 			return True
+		elif event.type_ == 'gc-invitation':
+			dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
+				data[1])
+			gajim.interface.remove_first_event(account, jid, event.type_)
+			return True
 		return False
 
 	def on_execute_command(self, widget, contact, account, resource=None):
@@ -2874,6 +3311,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			# last message is long time ago
 			gajim.last_message_time[account][ctrl.get_full_jid()] = 0
 		win.set_active_tab(fjid, account)
+		if gajim.connections[account].is_zeroconf and \
+				gajim.connections[account].status in ('offline', 'invisible'):
+			win.get_control(fjid, account).got_disconnected()
+
 		win.window.present()
 
 	def on_roster_treeview_row_activated(self, widget, path, col = 0):
@@ -2914,7 +3355,9 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 					fjid += '/' + resource
 				if self.open_event(account, fjid, first_ev):
 					return
-			c = gajim.contacts.get_contact_with_highest_priority(account, jid)
+			c = gajim.contacts.get_contact(account, jid, resource)
+			if not c or isinstance(c, list):
+				c = gajim.contacts.get_contact_with_highest_priority(account, jid)
 			if jid == gajim.get_jid_from_account(account):
 				resource = c.resource
 			self.on_open_chat_window(widget, c, account, resource = resource)
@@ -2922,7 +3365,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def on_roster_treeview_row_expanded(self, widget, iter, path):
 		'''When a row is expanded change the icon of the arrow'''
 		model = self.tree.get_model()
-		if gajim.config.get('mergeaccounts'):
+		if self.regroup: # merged accounts
 			accounts = gajim.connections.keys()
 		else:
 			accounts = [model[iter][C_ACCOUNT].decode('utf-8')]
@@ -2954,7 +3397,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		'''When a row is collapsed :
 		change the icon of the arrow'''
 		model = self.tree.get_model()
-		if gajim.config.get('mergeaccounts'):
+		if self.regroup: # merged accounts
 			accounts = gajim.connections.keys()
 		else:
 			accounts = [model[iter][C_ACCOUNT].decode('utf-8')]
@@ -3071,13 +3514,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			try:
 				# Object will add itself to the window dict
 				disco.ServiceDiscoveryWindow(account, address_entry = True)
-			except RuntimeError:
+			except GajimGeneralException:
 				pass
 
 	def load_iconset(self, path, pixbuf2 = None, transport = False):
-		'''load an iconset from the given path, and add pixbuf2 on top left of
-		each static images'''
-		imgs = {}
+		'''load full iconset from the given path, and add
+		pixbuf2 on top left of each static images'''
 		path += '/'
 		if transport:
 			list = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
@@ -3089,15 +3531,28 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			if pixbuf2:
 				list = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
 					'offline', 'error', 'requested', 'message', 'not in roster')
-		for state in list:
+		return self._load_icon_list(list, path, pixbuf2)
+		
+	def load_icon(self, icon_name):
+		'''load an icon from the iconset in 16x16'''
+		iconset = gajim.config.get('iconset')
+		path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16'+ '/')
+		icon_list = self._load_icon_list([icon_name], path)
+		return icon_list[icon_name]
+		
+	def _load_icon_list(self, icons_list, path, pixbuf2 = None):
+		'''load icons in icons_list from the given path, 
+		and add pixbuf2 on top left of each static images'''
+		imgs = {}
+		for icon in icons_list:
 			# try to open a pixfile with the correct method
-			state_file = state.replace(' ', '_')
+			icon_file = icon.replace(' ', '_')
 			files = []
-			files.append(path + state_file + '.gif')
-			files.append(path + state_file + '.png')
+			files.append(path + icon_file + '.gif')
+			files.append(path + icon_file + '.png')
 			image = gtk.Image()
 			image.show()
-			imgs[state] = image
+			imgs[icon] = image
 			for file in files: # loop seeking for either gif or png
 				if os.path.exists(file):
 					image.set_from_file(file)
@@ -3115,8 +3570,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def make_jabber_state_images(self):
 		'''initialise jabber_state_images dict'''
 		iconset = gajim.config.get('iconset')
-		if not iconset:
-			iconset = 'dcraven'
+		if iconset:
+			path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
+			if not os.path.exists(path):
+				iconset = gajim.config.DEFAULT_ICONSET
+		else: 
+			iconset = gajim.config.DEFAULT_ICONSET
+
 		path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '32x32')
 		self.jabber_state_images['32'] = self.load_iconset(path)
 
@@ -3155,7 +3615,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				model[iter][1] = self.jabber_state_images['16'][model[iter][2]]
 			iter = model.iter_next(iter)
 		# Update the systray
-		gajim.interface.systray.set_img()
+		if gajim.interface.systray_enabled:
+			gajim.interface.systray.set_img()
 
 		for win in gajim.interface.msg_win_mgr.windows():
 			for ctrl in win.controls():
@@ -3190,21 +3651,25 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def iconCellDataFunc(self, column, renderer, model, iter, data = None):
 		'''When a row is added, set properties for icon renderer'''
 		theme = gajim.config.get('roster_theme')
-		if model[iter][C_TYPE] == 'account':
+		type_ = model[iter][C_TYPE]
+		if type_ == 'account':
 			color = gajim.config.get_per('themes', theme, 'accountbgcolor')
 			if color:
 				renderer.set_property('cell-background', color)
 			else:
 				self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
 			renderer.set_property('xalign', 0)
-		elif model[iter][C_TYPE] == 'group':
+		elif type_ == 'group':
 			color = gajim.config.get_per('themes', theme, 'groupbgcolor')
 			if color:
 				renderer.set_property('cell-background', color)
 			else:
 				self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
 			renderer.set_property('xalign', 0.2)
-		else:
+		elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
+			if not model[iter][C_JID] or not model[iter][C_ACCOUNT]:
+				# This can append when at the moment we add the row
+				return
 			jid = model[iter][C_JID].decode('utf-8')
 			account = model[iter][C_ACCOUNT].decode('utf-8')
 			if jid in gajim.newly_added[account]:
@@ -3227,7 +3692,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def nameCellDataFunc(self, column, renderer, model, iter, data = None):
 		'''When a row is added, set properties for name renderer'''
 		theme = gajim.config.get('roster_theme')
-		if model[iter][C_TYPE] == 'account':
+		type_ = model[iter][C_TYPE]
+		if type_ == 'account':
 			color = gajim.config.get_per('themes', theme, 'accounttextcolor')
 			if color:
 				renderer.set_property('foreground', color)
@@ -3242,7 +3708,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
 			renderer.set_property('xpad', 0)
 			renderer.set_property('width', 3)
-		elif model[iter][C_TYPE] == 'group':
+		elif type_ == 'group':
 			color = gajim.config.get_per('themes', theme, 'grouptextcolor')
 			if color:
 				renderer.set_property('foreground', color)
@@ -3256,7 +3722,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			renderer.set_property('font',
 				gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
 			renderer.set_property('xpad', 4)
-		else:
+		elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
+			if not model[iter][C_JID] or not model[iter][C_ACCOUNT]:
+				# This can append when at the moment we add the row
+				return
 			jid = model[iter][C_JID].decode('utf-8')
 			account = model[iter][C_ACCOUNT].decode('utf-8')
 			color = gajim.config.get_per('themes', theme, 'contacttextcolor')
@@ -3285,19 +3754,23 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def fill_secondary_pixbuf_rederer(self, column, renderer, model, iter, data=None):
 		'''When a row is added, set properties for secondary renderer (avatar or padlock)'''
 		theme = gajim.config.get('roster_theme')
-		if model[iter][C_TYPE] == 'account':
+		type_ = model[iter][C_TYPE]
+		if type_ == 'account':
 			color = gajim.config.get_per('themes', theme, 'accountbgcolor')
 			if color:
 				renderer.set_property('cell-background', color)
 			else:
 				self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
-		elif model[iter][C_TYPE] == 'group':
+		elif type_ == 'group':
 			color = gajim.config.get_per('themes', theme, 'groupbgcolor')
 			if color:
 				renderer.set_property('cell-background', color)
 			else:
 				self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
-		else: # contact
+		elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
+			if not model[iter][C_JID] or not model[iter][C_ACCOUNT]:
+				# This can append when at the moment we add the row
+				return
 			jid = model[iter][C_JID].decode('utf-8')
 			account = model[iter][C_ACCOUNT].decode('utf-8')
 			if jid in gajim.newly_added[account]:
@@ -3418,34 +3891,55 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 
 	def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
 		c_dest, was_big_brother, context, etime):
-		# children must take the new tag too, so remember old tag
-		old_tag = gajim.contacts.get_metacontacts_tag(account_source,
-			c_source.jid)
-		# remove the source row
-		self.remove_contact(c_source, account_source)
-		# brother inherite big brother groups
-		c_source.groups = []
-		for g in c_dest.groups:
-			c_source.groups.append(g)
-		gajim.connections[account_source].update_contact(c_source.jid,
-			c_source.name, c_source.groups)
-		gajim.contacts.add_metacontact(account_dest, c_dest.jid, account_source,
-			c_source.jid)
-		if was_big_brother:
-			# add brothers too
-			all_jid = gajim.contacts.get_metacontacts_jids(old_tag)
-			for _account in all_jid:
-				for _jid in all_jid[_account]:
-					gajim.contacts.add_metacontact(account_dest, c_dest.jid,
-						_account, _jid)
-					_c = gajim.contacts.get_first_contact_from_jid(_account, _jid)
-					self.remove_contact(_c, _account)
-					self.add_contact_to_roster(_jid, _account)
-					self.draw_contact(_jid, _account)
-		self.add_contact_to_roster(c_source.jid, account_source)
-		self.draw_contact(c_dest.jid, account_dest)
-
-		context.finish(True, True, etime)
+		if not gajim.connections[account_source].metacontacts_supported or not \
+		gajim.connections[account_dest].metacontacts_supported:
+			dialogs.WarningDialog(_('Metacontacts storage not supported by your server'), _('Your server does not support storing metacontacts information. So those information will not be save on next reconnection.'))
+		def merge_contacts(widget = None):
+			if widget: # dialog has been shown
+				dlg.destroy()
+				if dlg.is_checked(): # user does not want to be asked again
+					gajim.config.set('confirm_metacontacts', 'no')
+				else:
+					gajim.config.set('confirm_metacontacts', 'yes')
+			# children must take the new tag too, so remember old tag
+			old_tag = gajim.contacts.get_metacontacts_tag(account_source,
+				c_source.jid)
+			# remove the source row
+			self.remove_contact(c_source, account_source)
+			# brother inherite big brother groups
+			c_source.groups = []
+			for g in c_dest.groups:
+				c_source.groups.append(g)
+			gajim.connections[account_source].update_contact(c_source.jid,
+				c_source.name, c_source.groups)
+			gajim.contacts.add_metacontact(account_dest, c_dest.jid,
+				account_source, c_source.jid)
+			if was_big_brother:
+				# add brothers too
+				all_jid = gajim.contacts.get_metacontacts_jids(old_tag)
+				for _account in all_jid:
+					for _jid in all_jid[_account]:
+						gajim.contacts.add_metacontact(account_dest, c_dest.jid,
+							_account, _jid)
+						_c = gajim.contacts.get_first_contact_from_jid(_account, _jid)
+						self.remove_contact(_c, _account)
+						self.add_contact_to_roster(_jid, _account)
+						self.draw_contact(_jid, _account)
+			self.add_contact_to_roster(c_source.jid, account_source)
+			self.draw_contact(c_dest.jid, account_dest)
+
+			context.finish(True, True, etime)
+
+		confirm_metacontacts = gajim.config.get('confirm_metacontacts')
+		if confirm_metacontacts == 'no':
+			merge_contacts()
+			return
+		pritext = _('You are about to create a metacontact. Are you sure you want to continue?')
+		sectext = _('Metacontacts are a way to regroup several contacts in one line. Generaly it is used when the same person has several Jabber accounts or transport accounts.')
+		dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
+			_('Do _not ask me again'), on_response_ok = merge_contacts)
+		if not confirm_metacontacts: # First time we see this window
+			dlg.checkbutton.set_active(True)
 
 	def on_drop_in_group(self, widget, account, c_source, grp_dest, context,
 		etime, grp_source = None):
@@ -3523,6 +4017,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 						account_dest, c_dest, path)
 			return
 
+		if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
+			# drop on zeroconf account, no contact adds possible
+			return
+
 		if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
 			# dropped before a group : we drop it in the previous group
 			path_dest = (path_dest[0], path_dest[1]-1)
@@ -3534,6 +4032,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			return
 		if type_dest == 'account' and account_source == account_dest:
 			return
+		if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
+			return
 		it = iter_source
 		while model[it][C_TYPE] == 'contact':
 			it = model.iter_parent(it)
@@ -3577,13 +4077,6 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		if jid_source == jid_dest:
 			if grp_source == grp_dest and account_source == account_dest:
 				return
-			if context.action == gtk.gdk.ACTION_COPY:
-				self.on_drop_in_group(None, account_source, c_source, grp_dest,
-					context, etime)
-				return
-			self.on_drop_in_group(None, account_source, c_source, grp_dest,
-				context, etime, grp_source)
-			return
 		if grp_source == grp_dest:
 			# Add meta contact
 			#FIXME: doesn't work under windows:
@@ -3611,13 +4104,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			menu = gtk.Menu()
 			item = gtk.MenuItem(_('Drop %s in group %s') % (c_source.name,
 				grp_dest))
-			item.connect('activate', self.on_drop_in_group, account_dest, c_source,
+			item.connect('activate', self.on_drop_in_group, account_source, c_source,
 				grp_dest, context, etime, grp_source)
 			menu.append(item)
 			c_dest = gajim.contacts.get_contact_with_highest_priority(
 				account_dest, jid_dest)
-			item = gtk.MenuItem(_('Make %s and %s metacontacts') % (c_source.name,
-				c_dest.name))
+			item = gtk.MenuItem(_('Make %s and %s metacontacts') %
+				(c_source.get_shown_name(), c_dest.get_shown_name()))
 			is_big_brother = False
 			if model.iter_has_child(iter_source):
 				is_big_brother = True
@@ -3633,12 +4126,16 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 
 	def show_title(self):
 		change_title_allowed = gajim.config.get('change_roster_title')
+		nb_unread = 0
 		if change_title_allowed:
 			start = ''
-			nb_unread = gajim.events.get_nb_events(['chat', 'normal',
-				'file-request', 'file-error', 'file-completed',
-				'file-request-error', 'file-send-error', 'file-stopped', 'gc_msg',
-				'printed_chat', 'printed_gc_msg'])
+			for account in gajim.connections:
+				# Count events in roster title only if we don't auto open them
+				if not helpers.allow_popup_window(account):
+					nb_unread += gajim.events.get_nb_events(['chat', 'normal',
+						'file-request', 'file-error', 'file-completed',
+						'file-request-error', 'file-send-error', 'file-stopped',
+						'printed_chat'], account)
 			if nb_unread > 1:
 				start = '[' + str(nb_unread) + ']  '
 			elif nb_unread == 1:
@@ -3703,6 +4200,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 	def __init__(self):
 		self.xml = gtkgui_helpers.get_glade('roster_window.glade')
 		self.window = self.xml.get_widget('roster_window')
+		self._music_track_changed_signal = None
 		gajim.interface.msg_win_mgr = MessageWindowMgr()
 		self.advanced_menus = [] # We keep them to destroy them
 		if gajim.config.get('roster_window_skip_taskbar'):
@@ -3826,6 +4324,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		self.xml.get_widget('show_offline_contacts_menuitem').set_active(
 			showOffline)
 
+		show_transports_group = gajim.config.get('show_transports_group')
+		self.xml.get_widget('show_transports_menuitem').set_active(
+			show_transports_group)
+
 		# columns
 
 		# this col has 3 cells:
@@ -3879,6 +4381,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		self.tooltip = tooltips.RosterTooltip()
 		self.draw_roster()
 
+		## Music Track notifications
+		## FIXME: we use a timeout because changing status of
+		## accounts has no effect until they are connected.
+		gobject.timeout_add(1000,
+			self.enable_syncing_status_msg_from_current_music_track,
+			gajim.config.get('set_status_msg_from_current_music_track'))
+
 		if gajim.config.get('show_roster_on_startup'):
 			self.window.show_all()
 		else:
@@ -3891,4 +4400,3 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		if len(gajim.connections) == 0: # if we have no account
 			gajim.interface.instances['account_creation_wizard'] = \
 				config.AccountCreationWizardWindow()
-
diff --git a/src/statusicon.py b/src/statusicon.py
new file mode 100644
index 0000000000000000000000000000000000000000..0201afe64991d2280fc14b3bdf2afd6b444d8661
--- /dev/null
+++ b/src/statusicon.py
@@ -0,0 +1,68 @@
+## statusicon.py
+##
+## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
+##
+## This program is free software; you can redistribute it and/or
+## modify it under the terms of the GNU General Public License
+## as published by the Free Software Foundation; either version 2
+## of the License, or (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+import gtk
+import systray
+
+from common import gajim
+from common import helpers
+
+class StatusIcon(systray.Systray):
+	'''Class for the notification area icon'''
+	#FIXME: when we migrate to GTK 2.10 stick only to this class
+	# (move base stuff from systray.py and rm it)
+	#NOTE: gtk api does NOT allow:
+	# leave, enter motion notify
+	# and can't do cool tooltips we use
+	# and we could use blinking instead of unsupported animation
+	# or we could emulate animation by every foo ms chaning the image
+	def __init__(self):
+		systray.Systray.__init__(self)
+		self.status_icon = gtk.StatusIcon()
+		
+	def show_icon(self):
+		self.status_icon.connect('activate', self.on_status_icon_left_clicked)
+		self.status_icon.connect('popup-menu', self.on_status_icon_right_clicked)
+
+		self.set_img()
+		self.status_icon.props.visible = True	
+
+	def on_status_icon_right_clicked(self, widget, event_button, event_time):
+		self.make_menu(event_button, event_time)
+
+	def hide_icon(self):
+		self.status_icon.props.visible = False
+
+	def on_status_icon_left_clicked(self, widget):
+		self.on_left_click()
+
+	def set_img(self):
+		'''apart from image, we also update tooltip text here'''
+		if not gajim.interface.systray_enabled:
+			return
+		text = helpers.get_notification_icon_tooltip_text()
+		self.status_icon.set_tooltip(text)
+		if gajim.events.get_nb_systray_events():
+			state = 'message'
+		else:
+			state = self.status
+		#FIXME: do not always use 16x16 (ask actually used size and use that)
+		image = gajim.interface.roster.jabber_state_images['16'][state]
+		if image.get_storage_type() == gtk.IMAGE_PIXBUF:
+			self.status_icon.props.pixbuf = image.get_pixbuf()
+		#FIXME: oops they forgot to support GIF animation?
+		#or they were lazy to get it to work under Windows! WTF!
+		#elif image.get_storage_type() == gtk.IMAGE_ANIMATION:
+		#	self.img_tray.set_from_animation(image.get_animation())
diff --git a/src/systray.py b/src/systray.py
index 926c077d1361fbfa1333c14df054c18880b14609..d17c5fc0b8c43983f6330a46138dd1d1e8bcfd79 100644
--- a/src/systray.py
+++ b/src/systray.py
@@ -43,7 +43,7 @@ except:
 
 class Systray:
 	'''Class for icon in the notification area
-	This class is both base class (for systraywin32.py) and normal class
+	This class is both base class (for statusicon.py) and normal class
 	for trayicon in GNU/Linux'''
 
 	def __init__(self):
@@ -94,18 +94,17 @@ class Systray:
 	def on_new_chat(self, widget, account):
 		dialogs.NewChatDialog(account)
 
-	def make_menu(self, event = None):
-		'''create chat with and new message (sub) menus/menuitems
-		event is None when we're in Windows
-		'''
-
+	def make_menu(self, event_button, event_time):
+		'''create chat with and new message (sub) menus/menuitems'''
 		for m in self.popup_menus:
 			m.destroy()
 
 		chat_with_menuitem = self.xml.get_widget('chat_with_menuitem')
-		single_message_menuitem = self.xml.get_widget('single_message_menuitem')
+		single_message_menuitem = self.xml.get_widget(
+			'single_message_menuitem')
 		status_menuitem = self.xml.get_widget('status_menu')
 		join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
+		sounds_mute_menuitem = self.xml.get_widget('sounds_mute_menuitem')
 
 		if self.single_message_handler_id:
 			single_message_menuitem.handler_disconnect(
@@ -124,11 +123,12 @@ class Systray:
 
 		# We need our own set of status icons, let's make 'em!
 		iconset = gajim.config.get('iconset')
-		if not iconset:
-			iconset = 'dcraven'
 		path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
 		state_images = gajim.interface.roster.load_iconset(path)
 
+		if state_images.has_key('muc_active'):
+			join_gc_menuitem.set_image(state_images['muc_active'])
+
 		for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
 			uf_show = helpers.get_uf_show(show, use_mnemonic = True)
 			item = gtk.ImageMenuItem(uf_show)
@@ -159,7 +159,8 @@ class Systray:
 		sub_menu.append(item)
 		item.connect('activate', self.on_show_menuitem_activate, 'offline')
 
-		iskey = connected_accounts > 0
+		iskey = connected_accounts > 0 and not (connected_accounts == 1 and
+				gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
 		chat_with_menuitem.set_sensitive(iskey)
 		single_message_menuitem.set_sensitive(iskey)
 		join_gc_menuitem.set_sensitive(iskey)
@@ -170,12 +171,15 @@ class Systray:
 			self.popup_menus.append(account_menu_for_chat_with)
 
 			account_menu_for_single_message = gtk.Menu()
-			single_message_menuitem.set_submenu(account_menu_for_single_message)
+			single_message_menuitem.set_submenu(
+				account_menu_for_single_message)
 			self.popup_menus.append(account_menu_for_single_message)
 
 			accounts_list = gajim.contacts.get_accounts()
 			accounts_list.sort()
 			for account in accounts_list:
+				if gajim.connections[account].is_zeroconf:
+					continue
 				if gajim.connections[account].connected > 1:
 					#for chat_with
 					item = gtk.MenuItem(_('using account %s') % account)
@@ -194,8 +198,11 @@ class Systray:
 					label.set_use_underline(False)
 					gc_item = gtk.MenuItem()
 					gc_item.add(label)
+					gc_item.connect('state-changed',
+						gtkgui_helpers.on_bm_header_changed_state)
 					gc_sub_menu.append(gc_item)
-					gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account)
+					gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
+						account)
 
 		elif connected_accounts == 1: # one account
 			# one account connected, no need to show 'as jid'
@@ -205,24 +212,25 @@ class Systray:
 							'activate', self.on_new_chat, account)
 					# for single message
 					single_message_menuitem.remove_submenu()
-					self.single_message_handler_id = single_message_menuitem.connect(
-						'activate', self.on_single_message_menuitem_activate, account)
+					self.single_message_handler_id = single_message_menuitem.\
+						connect('activate',
+						self.on_single_message_menuitem_activate, account)
 
 					# join gc
-					gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account)
+					gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
+						account)
 					break # No other connected account
 
-		if event is None:
-			# None means windows (we explicitly popup in systraywin32.py)
-			if self.added_hide_menuitem is False:
-				self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
-				item = gtk.MenuItem(_('Hide this menu'))
-				self.systray_context_menu.prepend(item)
-				self.added_hide_menuitem = True
+		sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
+		if os.name == 'nt' and gtk.pygtk_version >= (2, 10, 0) and\
+		gtk.gtk_version >= (2, 10, 0):
+			self.systray_context_menu.popup(None, None,
+				gtk.status_icon_position_menu, event_button,
+					event_time, self.status_icon)
 
 		else: # GNU and Unices
-			self.systray_context_menu.popup(None, None, None, event.button,
-				event.time)
+			self.systray_context_menu.popup(None, None, None, event_button,
+				event_time)
 		self.systray_context_menu.show_all()
 
 	def on_show_all_events_menuitem_activate(self, widget):
@@ -232,6 +240,10 @@ class Systray:
 				for event in events[account][jid]:
 					gajim.interface.handle_event(account, jid, event.type_)
 
+	def on_sounds_mute_menuitem_activate(self, widget):
+		gajim.config.set('sounds_on', not widget.get_active()) 
+		gajim.interface.save_config()
+
 	def on_show_roster_menuitem_activate(self, widget):
 		win = gajim.interface.roster.window
 		win.present()
@@ -250,11 +262,11 @@ class Systray:
 		if len(gajim.events.get_systray_events()) == 0:
 			# no pending events, so toggle visible/hidden for roster window
 			if win.get_property('visible'): # visible in ANY virtual desktop?
-				win.hide() # we hide it from VD that was visible in
 
-				# but we could be in another VD right now. eg vd2
-				# and we want not only to hide it in vd1 but also show it in vd2
-				gtkgui_helpers.possibly_move_window_in_current_desktop(win)
+				# we could be in another VD right now. eg vd2
+				# and we want to show it in vd2
+				if not gtkgui_helpers.possibly_move_window_in_current_desktop(win):
+					win.hide() # else we hide it from VD that was visible in
 			else:
 				win.present()
 		else:
@@ -275,12 +287,14 @@ class Systray:
 
 	def on_clicked(self, widget, event):
 		self.on_tray_leave_notify_event(widget, None)
-		if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: # Left click
+		if event.type != gtk.gdk.BUTTON_PRESS:
+			return
+		if event.button == 1: # Left click
 			self.on_left_click()
 		elif event.button == 2: # middle click
 			self.on_middle_click()
 		elif event.button == 3: # right click
-			self.make_menu(event)
+			self.make_menu(event.button, event.time)
 
 	def on_show_menuitem_activate(self, widget, show):
 		# we all add some fake (we cannot select those nor have them as show)
@@ -295,6 +309,7 @@ class Systray:
 		active = gajim.interface.roster.status_combobox.get_active()
 		status = model[active][2].decode('utf-8')
 		dlg = dialogs.ChangeStatusMessageDialog(status)
+		dlg.window.present()
 		message = dlg.run()
 		if message is not None: # None if user press Cancel
 			accounts = gajim.connections.keys()
diff --git a/src/systraywin32.py b/src/systraywin32.py
deleted file mode 100644
index c758247e01d2a2c62945e98435f7c9b922e80f39..0000000000000000000000000000000000000000
--- a/src/systraywin32.py
+++ /dev/null
@@ -1,305 +0,0 @@
-## src/systraywin32.py
-##
-## Contributors for this file:
-##	- Yann Le Boulanger <asterix@lagaule.org>
-##	- Nikos Kouremenos <kourem@gmail.com>
-##	- Dimitur Kirov <dkirov@gmail.com>
-##
-## code initially based on 
-## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/334779
-## with some ideas/help from pysystray.sf.net
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
-##                         Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
-##                    Vincent Hanquez <tab@snarc.org>
-##                    Nikos Kouremenos <nkour@jabber.org>
-##                    Dimitur Kirov <dkirov@gmail.com>
-##                    Travis Shirk <travis@pobox.com>
-##                    Norman Rasmussen <norman@rasmussen.co.za>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 2 only.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-
-
-import win32gui
-import pywintypes
-import win32con # winapi constants
-import systray
-import gtk
-import os
-
-WM_TASKBARCREATED = win32gui.RegisterWindowMessage('TaskbarCreated')
-WM_TRAYMESSAGE = win32con.WM_USER + 20
-
-import gtkgui_helpers
-from common import gajim
-from common import i18n
-
-class SystrayWINAPI:
-	def __init__(self, gtk_window):
-		self._window = gtk_window
-		self._hwnd = gtk_window.window.handle
-		self._message_map = {}
-
-		self.notify_icon = None            
-
-		# Sublass the window and inject a WNDPROC to process messages.
-		self._oldwndproc = win32gui.SetWindowLong(self._hwnd,
-			win32con.GWL_WNDPROC, self._wndproc)
-
-
-	def add_notify_icon(self, menu, hicon=None, tooltip=None):
-		""" Creates a notify icon for the gtk window. """
-		if not self.notify_icon:
-			if not hicon:
-				hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
-			self.notify_icon = NotifyIcon(self._hwnd, hicon, tooltip)
-
-			# Makes redraw if the taskbar is restarted.   
-			self.message_map({WM_TASKBARCREATED: self.notify_icon._redraw})
-
-
-	def message_map(self, msg_map={}):
-		""" Maps message processing to callback functions ala win32gui. """
-		if msg_map:
-			if self._message_map:
-				duplicatekeys = [key for key in msg_map.keys()
-								if self._message_map.has_key(key)]
-				
-				for key in duplicatekeys:
-					new_value = msg_map[key]
-					
-					if isinstance(new_value, list):
-						raise TypeError('Dict cannot have list values')
-					
-					value = self._message_map[key]
-					
-					if new_value != value:
-						new_value = [new_value]
-						
-						if isinstance(value, list):
-							value += new_value
-						else:
-							value = [value] + new_value
-						
-						msg_map[key] = value
-			self._message_map.update(msg_map)
-
-	def remove_notify_icon(self):
-		""" Removes the notify icon. """
-		if self.notify_icon:
-			self.notify_icon.remove()
-			self.notify_icon = None
-
-	def remove(self, *args):
-		""" Unloads the extensions. """
-		self._message_map = {}
-		self.remove_notify_icon()
-		self = None
-
-	def show_balloon_tooltip(self, title, text, timeout=10,
-							icon=win32gui.NIIF_NONE):
-		""" Shows a baloon tooltip. """
-		if not self.notify_icon:
-			self.add_notifyicon()
-		self.notify_icon.show_balloon(title, text, timeout, icon)
-
-	def _wndproc(self, hwnd, msg, wparam, lparam):
-		""" A WINDPROC to process window messages. """
-		if self._message_map.has_key(msg):
-			callback = self._message_map[msg]
-			if isinstance(callback, list):
-				for cb in callback:
-					cb(hwnd, msg, wparam, lparam)
-			else:
-				callback(hwnd, msg, wparam, lparam)
-
-		return win32gui.CallWindowProc(self._oldwndproc, hwnd, msg, wparam,
-									lparam)
-									
-
-class NotifyIcon:
-
-	def __init__(self, hwnd, hicon, tooltip=None):
-		self._hwnd = hwnd
-		self._id = 0
-		self._flags = win32gui.NIF_MESSAGE | win32gui.NIF_ICON
-		self._callbackmessage = WM_TRAYMESSAGE
-		self._hicon = hicon
-
-		try:
-			win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
-		except pywintypes.error:
-			pass
-		if tooltip: self.set_tooltip(tooltip)
-
-
-	def _get_nid(self):
-		""" Function to initialise & retrieve the NOTIFYICONDATA Structure. """
-		nid = [self._hwnd, self._id, self._flags, self._callbackmessage, self._hicon]
-
-		if not hasattr(self, '_tip'): self._tip = ''
-		nid.append(self._tip)
-
-		if not hasattr(self, '_info'): self._info = ''
-		nid.append(self._info)
-			
-		if not hasattr(self, '_timeout'): self._timeout = 0
-		nid.append(self._timeout)
-
-		if not hasattr(self, '_infotitle'): self._infotitle = ''
-		nid.append(self._infotitle)
-			
-		if not hasattr(self, '_infoflags'):self._infoflags = win32gui.NIIF_NONE
-		nid.append(self._infoflags)
-
-		return tuple(nid)
-	
-	def remove(self):
-		""" Removes the tray icon. """
-		try:
-			win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._get_nid())
-		except pywintypes.error:
-			pass
-
-
-	def set_tooltip(self, tooltip):
-		""" Sets the tray icon tooltip. """
-		self._flags = self._flags | win32gui.NIF_TIP
-		self._tip = tooltip
-		try:
-			win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
-		except pywintypes.error:
-			pass
-		
-		
-	def show_balloon(self, title, text, timeout=10, icon=win32gui.NIIF_NONE):
-		""" Shows a balloon tooltip from the tray icon. """
-		self._flags = self._flags | win32gui.NIF_INFO
-		self._infotitle = title
-		self._info = text
-		self._timeout = timeout * 1000
-		self._infoflags = icon
-		try:
-			win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
-		except pywintypes.error:
-			pass
-
-	def _redraw(self, *args):
-		""" Redraws the tray icon. """
-		self.remove()
-		try:
-			win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
-		except pywintypes.error:
-			pass
-
-
-class SystrayWin32(systray.Systray):
-	def __init__(self):
-		# Note: gtk window must be realized before installing extensions.
-		systray.Systray.__init__(self)
-		self.jids = []
-		self.status = 'offline'
-		self.xml = gtkgui_helpers.get_glade('systray_context_menu.glade')
-		self.systray_context_menu = self.xml.get_widget('systray_context_menu')
-		self.added_hide_menuitem = False
-		
-#		self.tray_ico_imgs = self.load_icos()
-		
-		w = gtk.Window() # just a window to pass
-		w.realize() # realize it so gtk window exists
-		self.systray_winapi = SystrayWINAPI(w)
-		
-		self.xml.signal_autoconnect(self)
-		
-		# Set up the callback messages
-		self.systray_winapi.message_map({
-			WM_TRAYMESSAGE: self.on_clicked
-			}) 
-
-	def show_icon(self):
-		#self.systray_winapi.add_notify_icon(self.systray_context_menu, tooltip = 'Gajim')
-		#self.systray_winapi.notify_icon.menu = self.systray_context_menu
-		# do not remove set_img does both above. 
-		# maybe I can only change img without readding
-		# the notify icon? HOW??
-		self.set_img()
-
-	def hide_icon(self):
-		self.systray_winapi.remove()
-
-	def on_clicked(self, hwnd, message, wparam, lparam):
-		if lparam == win32con.WM_RBUTTONUP: # Right click
-			self.make_menu()
-			self.systray_winapi.notify_icon.menu.popup(None, None, None, 0, 0)
-		elif lparam == win32con.WM_MBUTTONUP: # Middle click
-			self.on_middle_click()
-		elif lparam == win32con.WM_LBUTTONUP: # Left click
-			self.on_left_click()
-
-	def set_img(self):
-		self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here
-		# see gajim.interface.roster.reload_jabber_state_images() to merge
-
-		if len(self.jids) > 0:
-			state = 'message'
-		else:
-			state = self.status
-		hicon = self.tray_ico_imgs[state]
-		if hicon is None:
-			return
-
-		self.systray_winapi.remove_notify_icon()
-		self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
-			'Gajim')
-		self.systray_winapi.notify_icon.menu = self.systray_context_menu
-
-		nb = gajim.events.get_nb_systray_events()
-
-		if nb > 0:
-			text = i18n.ngettext(
-					'Gajim - %d unread message',
-					'Gajim - %d unread messages',
-					nb, nb, nb)
-		else:
-			text = 'Gajim'
-		self.systray_winapi.notify_icon.set_tooltip(text)
-
-	def load_icos(self):
-		'''load .ico files and return them to a dic of SHOW --> img_obj'''
-		iconset = str(gajim.config.get('iconset'))
-		if not iconset:
-			iconset = 'dcraven'
-		
-		imgs = {}
-		path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', 'icos')
-		# icon folder for missing icons 
-		path_dcraven_iconset = os.path.join(gajim.DATA_DIR, 'iconsets', 'dcraven',
-			'16x16', 'icos')
-		states_list = gajim.SHOW_LIST
-		# trayicon apart from show holds message state too
-		states_list.append('message')
-		for state in states_list:
-			path_to_ico = os.path.join(path, state + '.ico')
-			if not os.path.isfile(path_to_ico): # fallback to dcraven iconset
-				path_to_ico = os.path.join(path_dcraven_iconset, state + '.ico')
-			if os.path.exists(path_to_ico):
-				hinst = win32gui.GetModuleHandle(None)
-				img_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
-				try:
-					image = win32gui.LoadImage(hinst, path_to_ico,
-						win32con.IMAGE_ICON, 0, 0, img_flags)
-				except pywintypes.error:
-					imgs[state] = None
-				else:
-					imgs[state] = image
-
-		return imgs
diff --git a/src/tooltips.py b/src/tooltips.py
index 53915df8ab78b3ecfbd110c8825cfd57dfad6110..a6e40fe2f177e7ba30c5535109ea8ab69b849a5b 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -21,7 +21,6 @@ import time
 import locale
 
 import gtkgui_helpers
-import message_control
 
 from common import gajim
 from common import helpers
@@ -38,7 +37,7 @@ class BaseTooltip:
 				tooltip.hide_tooltip()
 		
 		* data - the text to be displayed  (extenders override this argument and 
-			dislpay more complex contents)
+			display more complex contents)
 		* widget_height  - the height of the widget on which we want to show tooltip
 		* widget_y_position - the vertical position of the widget on the screen
 		
@@ -135,7 +134,7 @@ class BaseTooltip:
 		preferred_y = widget_y_position + widget_height + 4
 		
 		self.preferred_position = [pointer_x, preferred_y]
-		self.widget_height =widget_height
+		self.widget_height = widget_height
 		self.win.ensure_style()
 		self.win.show_all()
 
@@ -177,10 +176,12 @@ class StatusTable:
 				# make sure 'status' is unicode before we send to to reduce_chars
 				if isinstance(status, str):
 					status = unicode(status, encoding='utf-8')
-				# reduce to 200 chars, 1 line
-				status = gtkgui_helpers.reduce_chars_newlines(status, 200, 1)
-				str_status += ' - ' + status
-		return gtkgui_helpers.escape_for_pango_markup(str_status)
+				# reduce to 100 chars, 1 line
+				status = helpers.reduce_chars_newlines(status, 100, 1)
+				str_status = gtkgui_helpers.escape_for_pango_markup(str_status)
+				status = gtkgui_helpers.escape_for_pango_markup(status)
+				str_status += ' - <i>' + status + '</i>'
+		return str_status
 	
 	def add_status_row(self, file_path, show, str_status, status_time = None, show_lock = False):
 		''' appends a new row with status icon to the table '''
@@ -213,13 +214,6 @@ class StatusTable:
 				gtk.ICON_SIZE_MENU)
 			self.table.attach(lock_image, 4, 5, self.current_row,
 				self.current_row + 1, 0, 0, 0, 0)
-		if status_time:
-			self.current_row += 1
-			# decode locale encoded string, the same way as below (10x nk)
-			local_time = time.strftime("%c", status_time)
-			local_time = local_time.decode(locale.getpreferredencoding()) 
-			status_time_label = gtk.Label(local_time)
-			status_time_label.set_alignment(0, 0)
 	
 class NotificationAreaTooltip(BaseTooltip, StatusTable):
 	''' Tooltip that is shown in the notification area '''
@@ -227,27 +221,6 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
 		BaseTooltip.__init__(self)
 		StatusTable.__init__(self)
 
-	def get_accounts_info(self):
-		accounts = []
-		accounts_list = gajim.contacts.get_accounts()
-		accounts_list.sort()
-		for account in accounts_list:
-			status_idx = gajim.connections[account].connected
-			# uncomment the following to hide offline accounts
-			# if status_idx == 0: continue
-			status = gajim.SHOW_LIST[status_idx]
-			message = gajim.connections[account].status
-			single_line = helpers.get_uf_show(status)
-			if message is None:
-				message = ''
-			else:
-				message = message.strip()
-			if message != '':
-				single_line += ': ' + message
-			accounts.append({'name': account, 'status_line': single_line, 
-					'show': status, 'message': message})
-		return accounts
-
 	def fill_table_with_accounts(self, accounts):
 		iconset = gajim.config.get('iconset')
 		if not iconset:
@@ -259,7 +232,7 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
 			# there are possible pango TBs on 'set_markup'
 			if isinstance(message, str):
 				message = unicode(message, encoding = 'utf-8')
-			message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
+			message = helpers.reduce_chars_newlines(message, 100, 1)
 			message = gtkgui_helpers.escape_for_pango_markup(message)
 			if gajim.con_types.has_key(acct['name']) and \
 				gajim.con_types[acct['name']] in ('tls', 'ssl'):
@@ -278,67 +251,16 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
 	def populate(self, data):
 		self.create_window()
 		self.create_table()
-		self.hbox = gtk.HBox()
-		self.table.set_property('column-spacing', 1)
-		text, single_line = '', ''
-
-		unread_chat = gajim.events.get_nb_events(types = ['printed_chat', 'chat'])
-		unread_single_chat = gajim.events.get_nb_events(types = ['normal'])
-		unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg',
-			'gc_msg'])
-		unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm'])
-
-		accounts = self.get_accounts_info()
-
-		if unread_chat or unread_single_chat or unread_gc or unread_pm:
-			text = 'Gajim '
-			awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm
-			if awaiting_events == unread_chat or awaiting_events == unread_single_chat \
-				or awaiting_events == unread_gc or awaiting_events == unread_pm:
-				# This condition is like previous if but with xor... 
-				# Print in one line
-				text += '-'
-			else:
-				# Print in multiple lines
-				text += '\n   '
-			if unread_chat:
-				text += i18n.ngettext(
-					' %d unread message',
-					' %d unread messages',
-					unread_chat, unread_chat, unread_chat)
-				text += '\n   '
-			if unread_single_chat:
-				text += i18n.ngettext(
-					' %d unread single message',
-					' %d unread single messages',
-					unread_single_chat, unread_single_chat, unread_single_chat)
-				text += '\n   '
-			if unread_gc:
-				text += i18n.ngettext(
-					' %d unread group chat message',
-					' %d unread group chat messages',
-					unread_gc, unread_gc, unread_gc)
-				text += '\n   '
-			if unread_pm:
-				text += i18n.ngettext(
-					' %d unread private message',
-					' %d unread private messages',
-					unread_pm, unread_pm, unread_pm)
-				text += '\n   '
-			text = text[:-4] # remove latest '\n   '
-		elif len(accounts) > 1:
-			text = _('Gajim')
-			self.current_current_row = 1
+		accounts = helpers.get_accounts_info()
+		if len(accounts) > 1:
 			self.table.resize(2, 1)
 			self.fill_table_with_accounts(accounts)
+		self.hbox = gtk.HBox()
+		self.table.set_property('column-spacing', 1)
 
-		elif len(accounts) == 1:
-			message = accounts[0]['status_line']
-			message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
-			message = gtkgui_helpers.escape_for_pango_markup(message)
-			text = _('Gajim - %s') % message
-		else:
-			text = _('Gajim - %s') % helpers.get_uf_show('offline')
+		text = helpers.get_notification_icon_tooltip_text()
+		text = gtkgui_helpers.escape_for_pango_markup(text)
+		
 		self.add_text_row(text)
 		self.hbox.add(self.table)
 		self.win.add(self.hbox)
@@ -364,27 +286,37 @@ class GCTooltip(BaseTooltip):
 		vcard_table.set_homogeneous(False)
 		vcard_current_row = 1
 		properties = []
-		
-		if contact.jid.strip() != '':
-			jid_markup = '<span weight="bold">' + contact.jid + '</span>' 
-		else:
-			jid_markup = '<span weight="bold">' + \
+
+		nick_markup = '<b>' + \
 			gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name()) \
-			+ '</span>'
-		properties.append((jid_markup, None))	
-		properties.append((_('Role: '), helpers.get_uf_role(contact.role)))
-		properties.append((_('Affiliation: '), contact.affiliation.capitalize()))
-		if hasattr(contact, 'resource') and contact.resource.strip() != '':
-			properties.append((_('Resource: '), 
-				gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
-		show = helpers.get_uf_show(contact.show)
-		if contact.status:
+			+ '</b>' 
+		properties.append((nick_markup, None))
+
+		if contact.status: # status message 
 			status = contact.status.strip()
 			if status != '':
 				# escape markup entities
-				status = gtkgui_helpers.reduce_chars_newlines(status, 200, 5)
-				show += ' - ' + gtkgui_helpers.escape_for_pango_markup(status)
-		properties.append((_('Status: '), show))
+				status = helpers.reduce_chars_newlines(status, 100, 5)
+				status = '<i>' +\
+					 gtkgui_helpers.escape_for_pango_markup(status) + '</i>'
+				properties.append((status, None))
+		else: # no status message, show SHOW instead
+			show = helpers.get_uf_show(contact.show)
+			show = '<i>' + show + '</i>'
+			properties.append((show, None))
+
+		if contact.jid.strip() != '':
+			properties.append((_('JID: '), contact.jid))
+
+		if hasattr(contact, 'resource') and contact.resource.strip() != '':
+			properties.append((_('Resource: '), 
+				gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
+		if contact.affiliation != 'none':
+			uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
+			affiliation_str = \
+				_('%(owner_or_admin_or_member)s of this group chat') %\
+				{'owner_or_admin_or_member': uf_affiliation}
+			properties.append((affiliation_str, None))
 		
 		# Add avatar
 		puny_name = helpers.sanitize_filename(contact.name)
@@ -410,22 +342,23 @@ class GCTooltip(BaseTooltip):
 			label.set_alignment(0, 0)
 			if property[1]:
 				label.set_markup(property[0])
-				vcard_table.attach(label, 1, 2, vcard_current_row, vcard_current_row + 1, 
-														gtk.FILL,  vertical_fill, 0, 0)
+				vcard_table.attach(label, 1, 2, vcard_current_row,
+					vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
 				label = gtk.Label()
 				label.set_alignment(0, 0)
 				label.set_markup(property[1])
 				label.set_line_wrap(True)
-				vcard_table.attach(label, 2, 3, vcard_current_row, vcard_current_row + 1, 
-										gtk.EXPAND | gtk.FILL, vertical_fill, 0, 0)
+				vcard_table.attach(label, 2, 3, vcard_current_row,
+					vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
+					vertical_fill, 0, 0)
 			else:
 				label.set_markup(property[0])
-				vcard_table.attach(label, 1, 3, vcard_current_row, vcard_current_row + 1, 
-															gtk.FILL, vertical_fill, 0)
+				vcard_table.attach(label, 1, 3, vcard_current_row,
+					vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
 		
 		self.avatar_image.set_alignment(0, 0)
-		vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row +1, 
-									gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
+		vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1, 
+			gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
 		self.win.add(vcard_table)
 
 class RosterTooltip(NotificationAreaTooltip):
@@ -445,8 +378,7 @@ class RosterTooltip(NotificationAreaTooltip):
 		self.create_table()
 		if not contacts or len(contacts) == 0:
 			# Tooltip for merged accounts row
-			accounts = self.get_accounts_info()
-			self.current_current_row = 0
+			accounts = helpers.get_accounts_info()
 			self.table.resize(2, 1)
 			self.spacer_label = ''
 			self.fill_table_with_accounts(accounts)
@@ -458,15 +390,6 @@ class RosterTooltip(NotificationAreaTooltip):
 		prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
 			contacts)
 		
-		transport = gajim.get_transport_name_from_jid(prim_contact.jid)
-		if transport:
-			file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', 
-				transport , '16x16')
-		else:
-			iconset = gajim.config.get('iconset')
-			if not iconset:
-				iconset = 'dcraven'
-			file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
 		puny_jid = helpers.sanitize_filename(prim_contact.jid)
 		table_size = 3
 		
@@ -486,23 +409,12 @@ class RosterTooltip(NotificationAreaTooltip):
 		vcard_table.set_homogeneous(False)
 		vcard_current_row = 1
 		properties = []
-		jid_markup = '<span weight="bold">' + prim_contact.jid + '</span>'
-		properties.append((jid_markup, None))
 
-		properties.append((_('Name: '), gtkgui_helpers.escape_for_pango_markup(
-			prim_contact.get_shown_name())))
-		if prim_contact.sub:
-			properties.append(( _('Subscription: '), 
-				gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
-		if prim_contact.keyID:
-			keyID = None
-			if len(prim_contact.keyID) == 8:
-				keyID = prim_contact.keyID
-			elif len(prim_contact.keyID) == 16:
-				keyID = prim_contact.keyID[8:]
-			if keyID:
-				properties.append((_('OpenPGP: '),
-					gtkgui_helpers.escape_for_pango_markup(keyID)))
+		name_markup = u'<span weight="bold">' + \
+			gtkgui_helpers.escape_for_pango_markup(prim_contact.get_shown_name())\
+			+ '</span>'
+		properties.append((name_markup, None))
+		
 		num_resources = 0
 		# put contacts in dict, where key is priority
 		contacts_dict = {}
@@ -514,12 +426,20 @@ class RosterTooltip(NotificationAreaTooltip):
 				else:
 					contacts_dict[contact.priority] = [contact]
 
-		if num_resources == 1 and contact.resource:
-			properties.append((_('Resource: '),
-				gtkgui_helpers.escape_for_pango_markup(contact.resource) + ' (' + \
-				unicode(contact.priority) + ')'))
 		if num_resources > 1:
 			properties.append((_('Status: '),	' '))
+			transport = gajim.get_transport_name_from_jid(
+				prim_contact.jid)
+			if transport:
+				file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 
+					'transports', transport , '16x16')
+			else:
+				iconset = gajim.config.get('iconset')
+				if not iconset:
+					iconset = 'dcraven'
+				file_path = os.path.join(gajim.DATA_DIR,
+					'iconsets', iconset, '16x16')
+
 			contact_keys = contacts_dict.keys()
 			contact_keys.sort()
 			contact_keys.reverse()
@@ -535,29 +455,63 @@ class RosterTooltip(NotificationAreaTooltip):
 		else: # only one resource
 			if contact.show:
 				show = helpers.get_uf_show(contact.show) 
+				if contact.last_status_time:
+					vcard_current_row += 1
+					if contact.show == 'offline':
+						text = ' - ' + _('Last status: %s')
+					else:
+						text = _(' since %s')
+					
+					if time.strftime('%j', time.localtime())== \
+							time.strftime('%j', contact.last_status_time):
+					# it's today, show only the locale hour representation
+						local_time = time.strftime('%X',
+							contact.last_status_time)
+					else:
+						# time.strftime returns locale encoded string
+						local_time = time.strftime('%c',
+							contact.last_status_time)
+					local_time = local_time.decode(
+						locale.getpreferredencoding())
+					text = text % local_time 
+					show += text
+				show = '<i>' + show + '</i>'
+				# we append show below
+				
 				if contact.status:
 					status = contact.status.strip()
 					if status:
 						# reduce long status
-						# (no more than 200 chars on line and no more than 5 lines)
-						status = gtkgui_helpers.reduce_chars_newlines(status, 200, 5)
+						# (no more than 100 chars on line and no more than 5 lines)
+						status = helpers.reduce_chars_newlines(status, 100, 5)
 						# escape markup entities. 
 						status = gtkgui_helpers.escape_for_pango_markup(status)
-						show += ' - ' + status
-				properties.append((_('Status: '),	show))
-			
-			if contact.last_status_time:
-				vcard_current_row += 1
-				if contact.show == 'offline':
-					text = _('Last status on %s')
-				else:
-					text = _('Since %s')
+						properties.append(('<i>%s</i>' % status, None))
+				properties.append((show, None))
+		
+		properties.append((_('Jabber ID: '), prim_contact.jid ))
 
-				# time.strftime returns locale encoded string
-				local_time = time.strftime('%c', contact.last_status_time)
-				local_time = local_time.decode(locale.getpreferredencoding()) 
-				text = text % local_time 
-				properties.append(('<span style="italic">%s</span>' % text, None))
+		# contact has only one ressource
+		if num_resources == 1 and contact.resource:
+			properties.append((_('Resource: '),
+				gtkgui_helpers.escape_for_pango_markup(contact.resource) +\
+				' (' + unicode(contact.priority) + ')'))
+		
+		if prim_contact.sub and prim_contact.sub != 'both':
+			# ('both' is the normal sub so we don't show it)
+			properties.append(( _('Subscription: '), 
+				gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
+	
+		if prim_contact.keyID:
+			keyID = None
+			if len(prim_contact.keyID) == 8:
+				keyID = prim_contact.keyID
+			elif len(prim_contact.keyID) == 16:
+				keyID = prim_contact.keyID[8:]
+			if keyID:
+				properties.append((_('OpenPGP: '),
+					gtkgui_helpers.escape_for_pango_markup(keyID)))
+		
 		while properties:
 			property = properties.pop(0)
 			vcard_current_row += 1
@@ -568,28 +522,26 @@ class RosterTooltip(NotificationAreaTooltip):
 			label.set_alignment(0, 0)
 			if property[1]:
 				label.set_markup(property[0])
-				vcard_table.attach(label, 1, 2, vcard_current_row, vcard_current_row + 1, 
-														gtk.FILL,  vertical_fill, 0, 0)
+				vcard_table.attach(label, 1, 2, vcard_current_row,
+					vcard_current_row + 1, gtk.FILL,  vertical_fill, 0, 0)
 				label = gtk.Label()
-				if num_resources > 1 and not properties:
-					label.set_alignment(0, 1)
-				else:
-					label.set_alignment(0, 0)
+				label.set_alignment(0, 0)
 				label.set_markup(property[1])
 				label.set_line_wrap(True)
-				vcard_table.attach(label, 2, 3, vcard_current_row, vcard_current_row + 1, 
-										gtk.EXPAND | gtk.FILL, vertical_fill, 0, 0)
+				vcard_table.attach(label, 2, 3, vcard_current_row,
+					vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
+						vertical_fill, 0, 0)
 			else:
-				if isinstance(property[0], unicode):
+				if isinstance(property[0], (unicode, str)): #FIXME: rm unicode?
 					label.set_markup(property[0])
 				else:
 					label = property[0]
-				vcard_table.attach(label, 1, 3, vcard_current_row, vcard_current_row + 1, 
-															gtk.FILL, vertical_fill, 0)
+				vcard_table.attach(label, 1, 3, vcard_current_row,
+					vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
 		self.avatar_image.set_alignment(0, 0)
 		if table_size == 4:
-			vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row +1, 
-										gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
+			vcard_table.attach(self.avatar_image, 3, 4, 2,
+				vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
 		self.win.add(vcard_table)
 
 class FileTransfersTooltip(BaseTooltip):
diff --git a/src/vcard.py b/src/vcard.py
index 009605b3825b41b1b7bc1c102202eeea2a070a6d..a479d382bfc883b6872e06d246f751078c31de8c 100644
--- a/src/vcard.py
+++ b/src/vcard.py
@@ -2,6 +2,7 @@
 ##
 ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
 ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published
@@ -13,16 +14,15 @@
 ## GNU General Public License for more details.
 ##
 
+# THIS FILE IS FOR **OTHERS'** PROFILE (when we VIEW their INFO)
+
 import gtk
 import gobject
 import base64
-import mimetypes
-import os
 import time
 import locale
 
 import gtkgui_helpers
-import dialogs
 
 from common import helpers
 from common import gajim
@@ -57,25 +57,49 @@ def get_avatar_pixbuf_encoded_mime(photo):
 class VcardWindow:
 	'''Class for contact's information window'''
 
-	def __init__(self, contact, account, is_fake = False):
+	def __init__(self, contact, account, gc_contact = None):
 		# the contact variable is the jid if vcard is true
 		self.xml = gtkgui_helpers.get_glade('vcard_information_window.glade')
 		self.window = self.xml.get_widget('vcard_information_window')
+		self.progressbar = self.xml.get_widget('progressbar')
 
 		self.contact = contact
 		self.account = account
-		self.is_fake = is_fake
+		self.gc_contact = gc_contact
 
 		self.avatar_mime_type = None
 		self.avatar_encoded = None
+		self.vcard_arrived = False
+		self.os_info_arrived = False
+		self.update_progressbar_timeout_id = gobject.timeout_add(100,
+			self.update_progressbar)
 
 		self.fill_jabber_page()
+		annotations = gajim.connections[self.account].annotations
+		if self.contact.jid in annotations:
+			buffer = self.xml.get_widget('textview_annotation').get_buffer()
+			buffer.set_text(annotations[self.contact.jid])
 
 		self.xml.signal_autoconnect(self)
 		self.window.show_all()
+		self.xml.get_widget('close_button').grab_focus()
+
+	def update_progressbar(self):
+		self.progressbar.pulse()
+		return True # loop forever
 
 	def on_vcard_information_window_destroy(self, widget):
+		if self.update_progressbar_timeout_id is not None:
+			gobject.source_remove(self.update_progressbar_timeout_id)
 		del gajim.interface.instances[self.account]['infos'][self.contact.jid]
+		buffer = self.xml.get_widget('textview_annotation').get_buffer()
+		annotation = buffer.get_text(buffer.get_start_iter(),
+			buffer.get_end_iter())
+		connection = gajim.connections[self.account]
+		if annotation != connection.annotations.get(self.contact.jid, ''):
+			connection.annotations[self.contact.jid] = annotation
+			connection.store_annotations()
+
 
 	def on_vcard_information_window_key_press_event(self, widget, event):
 		if event.keyval == gtk.keysyms.Escape:
@@ -104,7 +128,8 @@ class VcardWindow:
 			menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
 			menuitem.connect('activate',
 				gtkgui_helpers.on_avatar_save_as_menuitem_activate,
-				self.contact.jid, self.account, self.contact.name + '.jpeg')
+				self.contact.jid, self.account, self.contact.get_shown_name() +
+				'.jpeg')
 			menu.append(menuitem)
 			menu.connect('selection-done', lambda w:w.destroy())	
 			# show the menu
@@ -113,13 +138,24 @@ class VcardWindow:
 
 	def set_value(self, entry_name, value):
 		try:
-			self.xml.get_widget(entry_name).set_text(value)
+			if value and entry_name == 'URL_label':
+				if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
+					widget = gtk.LinkButton(value, value)
+				else:
+					widget = gtk.Label(value)
+					widget.set_selectable(True)
+				widget.show()
+				table = self.xml.get_widget('personal_info_table')
+				table.attach(widget, 1, 4, 3, 4, yoptions = 0)
+			else:
+				self.xml.get_widget(entry_name).set_text(value)
 		except AttributeError:
 			pass
 
 	def set_values(self, vcard):
 		for i in vcard.keys():
-			if i == 'PHOTO':
+			if i == 'PHOTO' and self.xml.get_widget('information_notebook').\
+			get_n_pages() > 4:
 				pixbuf, self.avatar_encoded, self.avatar_mime_type = \
 					get_avatar_pixbuf_encoded_mime(vcard[i])
 				image = self.xml.get_widget('PHOTO_image')
@@ -130,7 +166,7 @@ class VcardWindow:
 				pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
 				image.set_from_pixbuf(pixbuf)
 				continue
-			if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
+			if i in ('ADR', 'TEL', 'EMAIL'):
 				for entry in vcard[i]:
 					add_on = '_HOME'
 					if 'WORK' in entry:
@@ -144,14 +180,23 @@ class VcardWindow:
 				if i == 'DESC':
 					self.xml.get_widget('DESC_textview').get_buffer().set_text(
 						vcard[i], 0)
-				else:
+				elif i != 'jid': # Do not override jid_label
 					self.set_value(i + '_label', vcard[i])
+		self.vcard_arrived = True
+		self.test_remove_progressbar()
+
+	def test_remove_progressbar(self):
+		if self.update_progressbar_timeout_id is not None and \
+		self.vcard_arrived and self.os_info_arrived:
+			gobject.source_remove(self.update_progressbar_timeout_id)
+			self.progressbar.hide()
+			self.update_progressbar_timeout_id = None
 
 	def set_last_status_time(self):
 		self.fill_status_label()
 
 	def set_os_info(self, resource, client_info, os_info):
-		if self.xml.get_widget('information_notebook').get_n_pages() < 4:
+		if self.xml.get_widget('information_notebook').get_n_pages() < 5:
 			return
 		i = 0
 		client = ''
@@ -174,16 +219,25 @@ class VcardWindow:
 			os = Q_('?OS:Unknown')
 		self.xml.get_widget('client_name_version_label').set_text(client)
 		self.xml.get_widget('os_label').set_text(os)
+		self.os_info_arrived = True
+		self.test_remove_progressbar()
 
 	def fill_status_label(self):
-		if self.xml.get_widget('information_notebook').get_n_pages() < 4:
+		if self.xml.get_widget('information_notebook').get_n_pages() < 5:
 			return
 		contact_list = gajim.contacts.get_contact(self.account, self.contact.jid)
+		connected_contact_list = []
+		for c in contact_list:
+			if c.show not in ('offline', 'error'):
+				connected_contact_list.append(c)
+		if not connected_contact_list:
+			# no connected contact, get the offline one
+			connected_contact_list = contact_list
 		# stats holds show and status message
 		stats = ''
 		one = True # Are we adding the first line ?
-		if contact_list:
-			for c in contact_list:
+		if connected_contact_list:
+			for c in connected_contact_list:
 				if not one:
 					stats += '\n'
 				stats += helpers.get_uf_show(c.show)
@@ -212,26 +266,38 @@ class VcardWindow:
 			self.contact.get_shown_name() +
 			'</span></b>')
 		self.xml.get_widget('jid_label').set_text(self.contact.jid)
-		uf_sub = helpers.get_uf_sub(self.contact.sub)
-		self.xml.get_widget('subscription_label').set_text(uf_sub)
-		eb = self.xml.get_widget('subscription_label_eventbox')
-		if self.contact.sub == 'from':
-			tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence")
-		elif self.contact.sub == 'to':
-			tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours")
-		elif self.contact.sub == 'both':
-			tt_text = _("You and the contact are interested in each other's presence information")
-		else: # None
-			tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours")
-		tooltips.set_tip(eb, tt_text)
-
-		label = self.xml.get_widget('ask_label')
-		uf_ask = helpers.get_uf_ask(self.contact.ask)
-		label.set_text(uf_ask)
-		eb = self.xml.get_widget('ask_label_eventbox')
-		if self.contact.ask == 'subscribe':
-			tooltips.set_tip(eb,
-			_("You are waiting contact's answer about your subscription request"))
+		
+		subscription_label = self.xml.get_widget('subscription_label')
+		ask_label = self.xml.get_widget('ask_label')
+		if self.gc_contact:
+			self.xml.get_widget('subscription_title_label').set_text(_("Role:"))
+			uf_role = helpers.get_uf_role(self.gc_contact.role)
+			subscription_label.set_text(uf_role)
+
+			self.xml.get_widget('ask_title_label').set_text(_("Affiliation:"))
+			uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation)
+			ask_label.set_text(uf_affiliation)
+		else:
+			uf_sub = helpers.get_uf_sub(self.contact.sub)
+			subscription_label.set_text(uf_sub)
+			eb = self.xml.get_widget('subscription_label_eventbox')
+			if self.contact.sub == 'from':
+				tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence")
+			elif self.contact.sub == 'to':
+				tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours")
+			elif self.contact.sub == 'both':
+				tt_text = _("You and the contact are interested in each other's presence information")
+			else: # None
+				tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours")
+			tooltips.set_tip(eb, tt_text)
+
+			uf_ask = helpers.get_uf_ask(self.contact.ask)
+			ask_label.set_text(uf_ask)
+			eb = self.xml.get_widget('ask_label_eventbox')
+			if self.contact.ask == 'subscribe':
+				tooltips.set_tip(eb,
+				_("You are waiting contact's answer about your subscription request"))
+
 		log = True
 		if self.contact.jid in gajim.config.get_per('accounts', self.account,
 			'no_log_for').split(' '):
@@ -251,8 +317,12 @@ class VcardWindow:
 		gajim.connections[self.account].request_last_status_time(self.contact.jid,
 			self.contact.resource)
 
-		# Request os info in contact is connected
-		if self.contact.show not in ('offline', 'error'):
+		# do not wait for os_info if contact is not connected or has error
+		# additional check for observer is needed, as show is offline for him
+		if self.contact.show in ('offline', 'error')\
+		and not self.contact.is_observer():
+			self.os_info_arrived = True
+		else: # Request os info if contact is connected
 			gobject.idle_add(gajim.connections[self.account].request_os_info,
 				self.contact.jid, self.contact.resource)
 		self.os_info = {0: {'resource': self.contact.resource, 'client': '',
@@ -282,4 +352,160 @@ class VcardWindow:
 
 		self.fill_status_label()
 
-		gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
+		gajim.connections[self.account].request_vcard(self.contact.jid,
+			self.gc_contact is not None)
+
+	def on_close_button_clicked(self, widget):
+		self.window.destroy()
+
+
+class ZeroconfVcardWindow:
+	def __init__(self, contact, account, is_fake = False):
+		# the contact variable is the jid if vcard is true
+		self.xml = gtkgui_helpers.get_glade('zeroconf_information_window.glade')
+		self.window = self.xml.get_widget('zeroconf_information_window')
+
+		self.contact = contact
+		self.account = account
+		self.is_fake = is_fake
+
+	#	self.avatar_mime_type = None
+	#	self.avatar_encoded = None
+
+		self.fill_contact_page()
+		self.fill_personal_page()
+
+		self.xml.signal_autoconnect(self)
+		self.window.show_all()
+
+	def on_zeroconf_information_window_destroy(self, widget):
+		del gajim.interface.instances[self.account]['infos'][self.contact.jid]
+
+	def on_zeroconf_information_window_key_press_event(self, widget, event):
+		if event.keyval == gtk.keysyms.Escape:
+			self.window.destroy()
+
+	def on_log_history_checkbutton_toggled(self, widget):
+		#log conversation history?
+		oldlog = True
+		no_log_for = gajim.config.get_per('accounts', self.account,
+			'no_log_for').split()
+		if self.contact.jid in no_log_for:
+			oldlog = False
+		log = widget.get_active()
+		if not log and not self.contact.jid in no_log_for:
+			no_log_for.append(self.contact.jid)
+		if log and self.contact.jid in no_log_for:
+			no_log_for.remove(self.contact.jid)
+		if oldlog != log:
+			gajim.config.set_per('accounts', self.account, 'no_log_for',
+				' '.join(no_log_for))
+
+	def on_PHOTO_eventbox_button_press_event(self, widget, event):
+		'''If right-clicked, show popup'''
+		if event.button == 3: # right click
+			menu = gtk.Menu()
+			menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+			menuitem.connect('activate',
+				gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+				self.contact.jid, self.account, self.contact.get_shown_name() +
+				'.jpeg')
+			menu.append(menuitem)
+			menu.connect('selection-done', lambda w:w.destroy())	
+			# show the menu
+			menu.show_all()
+			menu.popup(None, None, None, event.button, event.time)
+
+	def set_value(self, entry_name, value):
+		try:
+			if value and entry_name == 'URL_label':
+				if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
+					widget = gtk.LinkButton(value, value)
+				else:
+					widget = gtk.Label(value)
+					widget.set_selectable(True)
+				table = self.xml.get_widget('personal_info_table')
+				table.attach(widget, 1, 4, 3, 4, yoptions = 0)
+			else:
+				self.xml.get_widget(entry_name).set_text(value)
+		except AttributeError:
+			pass
+
+	def fill_status_label(self):
+		if self.xml.get_widget('information_notebook').get_n_pages() < 2:
+			return
+		contact_list = gajim.contacts.get_contact(self.account, self.contact.jid)
+		# stats holds show and status message
+		stats = ''
+		one = True # Are we adding the first line ?
+		if contact_list:
+			for c in contact_list:
+				if not one:
+					stats += '\n'
+				stats += helpers.get_uf_show(c.show)
+				if c.status:
+					stats += ': ' + c.status
+				if c.last_status_time:
+					stats += '\n' + _('since %s') % time.strftime('%c',
+						c.last_status_time).decode(locale.getpreferredencoding())
+				one = False
+		else: # Maybe gc_vcard ?
+			stats = helpers.get_uf_show(self.contact.show)
+			if self.contact.status:
+				stats += ': ' + self.contact.status
+		status_label = self.xml.get_widget('status_label')
+		status_label.set_max_width_chars(15)
+		status_label.set_text(stats)
+
+		tip = gtk.Tooltips()
+		status_label_eventbox = self.xml.get_widget('status_label_eventbox')
+		tip.set_tip(status_label_eventbox, stats)
+	
+	def fill_contact_page(self):
+		tooltips = gtk.Tooltips()
+		self.xml.get_widget('nickname_label').set_markup(
+			'<b><span size="x-large">' +
+			self.contact.get_shown_name() +
+			'</span></b>')
+		self.xml.get_widget('local_jid_label').set_text(self.contact.jid)
+
+		log = True
+		if self.contact.jid in gajim.config.get_per('accounts', self.account,
+			'no_log_for').split(' '):
+			log = False
+		checkbutton = self.xml.get_widget('log_history_checkbutton')
+		checkbutton.set_active(log)
+		checkbutton.connect('toggled', self.on_log_history_checkbutton_toggled)
+		
+		resources = '%s (%s)' % (self.contact.resource, unicode(
+			self.contact.priority))
+		uf_resources = self.contact.resource + _(' resource with priority ')\
+			+ unicode(self.contact.priority)
+		if not self.contact.status:
+			self.contact.status = ''
+
+		# Request list time status
+	#	gajim.connections[self.account].request_last_status_time(self.contact.jid,
+	#		self.contact.resource)
+
+		self.xml.get_widget('resource_prio_label').set_text(resources)
+		resource_prio_label_eventbox = self.xml.get_widget(
+			'resource_prio_label_eventbox')
+		tooltips.set_tip(resource_prio_label_eventbox, uf_resources)
+
+		self.fill_status_label()
+
+	#	gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
+	
+	def fill_personal_page(self):
+		contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid)
+		for key in ('1st', 'last', 'jid', 'email'):
+			if not contact['txt_dict'].has_key(key):
+				contact['txt_dict'][key] = ''
+		self.xml.get_widget('first_name_label').set_text(contact['txt_dict']['1st'])
+		self.xml.get_widget('last_name_label').set_text(contact['txt_dict']['last'])
+		self.xml.get_widget('jabber_id_label').set_text(contact['txt_dict']['jid'])
+		self.xml.get_widget('email_label').set_text(contact['txt_dict']['email'])
+
+	def on_close_button_clicked(self, widget):
+		self.window.destroy()