From 2a5b0afc44fe85b049ca4c8b59b1ecd12e2f41fc Mon Sep 17 00:00:00 2001
From: Travis Shirk <travis@pobox.com>
Date: Fri, 6 Jan 2006 06:59:55 +0000
Subject: [PATCH] More groupchat goodness

---
 src/chat_control.py      | 146 +++++++---------
 src/common/gajim.py      |   3 +-
 src/config.py            |   1 +
 src/gajim.py             | 109 ++++++------
 src/groupchat_control.py | 361 ++++++++++++++++++++++++++++++++++++++-
 src/gtkgui.glade         |   6 +-
 src/message_window.py    |   9 +-
 src/roster_window.py     |  26 ++-
 src/tooltips.py          |  14 +-
 9 files changed, 510 insertions(+), 165 deletions(-)

diff --git a/src/chat_control.py b/src/chat_control.py
index bed6d8ce16..8f79451850 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -281,18 +281,16 @@ class ChatControlBase(MessageControl):
 	def _process_command(self, message):
 		if not message:
 			return False
+
 		if message == '/clear':
 			self.conv_textview.clear() # clear conversation
-			# FIXME: Need this function
 			self.clear(self.msg_textview) # clear message textview too
 			return True
 		elif message == '/compact':
 			self.set_compact_view(not self.compact_view_current)
-			# FIXME: Need this function
 			self.clear(self.msg_textview)
 			return True
-		else:
-			return False
+		return False
 
 	def send_message(self, message, keyID = '', type = 'chat', chatstate = None):
 		'''Send the given message to the active tab'''
@@ -308,19 +306,6 @@ class ChatControlBase(MessageControl):
 		# Clear msg input
 		message_buffer = self.msg_textview.get_buffer()
 		message_buffer.set_text('') # clear message buffer (and tv of course)
-# FIXME GC ONLY
-#		if contact is None:
-#			# contact was from pm in MUC
-#			room, nick = gajim.get_room_and_nick_from_fjid(jid)
-#			gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
-#			if not gc_contact:
-#				# contact left the room, or we left the room
-#				dialogs.ErrorDialog(_('Sending private message failed'),
-#					#in second %s code replaces with nickname
-#					_('You are no longer in room "%s" or "%s" has left.') % \
-#					(room, nick)).get_response()
-#				return
-
 
 	def save_sent_message(self, message):
 		#save the message, so user can scroll though the list with key up/down
@@ -433,13 +418,6 @@ class ChatControlBase(MessageControl):
 	def update_tags(self):
 		self.conv_textview.update_tags()
 
-	def set_compact_view(self, state):
-		'''Toggle compact view. state is bool'''
-		MessageControl.set_compact_view(self, state)
-		# make the last message visible, when changing to "full view"
-		if not state:
-			gobject.idle_add(self.conv_textview.scroll_to_end_iter)
-
 	def clear(self, tv):
 		buffer = tv.get_buffer()
 		start, end = buffer.get_bounds()
@@ -559,10 +537,10 @@ class ChatControlBase(MessageControl):
 		if not self.nb_unread:
 			return
 		jid = self.contact.jid
-		if self.conv_textview.at_the_end() and self.window.is_active():
+		if self.conv_textview.at_the_end() and self.parent_win.window.is_active():
 			#we are at the end
 			self.nb_unread = self.get_specific_unread()
-			self.parent_win.redraw_tab(jid)
+			self.parent_win.redraw_tab(self.contact)
 			self.parent_win.show_title()
 			if gajim.interface.systray_enabled:
 				gajim.interface.systray.remove_jid(jid, self.account,
@@ -600,6 +578,55 @@ class ChatControlBase(MessageControl):
 		color.blue = int((color.blue * p) + (mask * (1 - p)))
 		return color
 
+	def remove_possible_switch_to_menuitems(self, menu):
+		''' remove duplicate 'Switch to' if they exist and return clean menu'''
+		childs = menu.get_children()
+
+		contact = self.parent_win.get_active_contact()
+		jid = contact.jid
+		if _('not in the roster') in contact.groups: # for add_to_roster_menuitem
+			childs[5].show()
+			childs[5].set_no_show_all(False)
+		else:
+			childs[5].hide()
+			childs[5].set_no_show_all(True)
+
+		if self.type_id == message_control.TYPE_GC:
+			start_removing_from = 7 # # this is from the seperator and after
+		else:
+			start_removing_from = 6 # this is from the seperator and after
+			
+		for child in childs[start_removing_from:]:
+			menu.remove(child)
+		return menu
+
+	def set_compact_view(self, state):
+		'''Toggle compact view. state is bool'''
+		MessageControl.set_compact_view(self, state)
+		# make the last message visible, when changing to "full view"
+		if not state:
+			gobject.idle_add(self.conv_textview.scroll_to_end_iter)
+		
+		if self.type_id == message_control.TYPE_GC:
+			widgets = [
+				self.xml.get_widget('banner_eventbox'),
+				self.xml.get_widget('gc_actions_hbox'),
+				self.xml.get_widget('list_scrolledwindow'),
+				]
+		else:
+			widgets = [
+				self.xml.get_widget('banner_eventbox'),
+				self.xml.get_widget('actions_hbox'),
+				]
+
+		for widget in widgets:
+			if state:
+				widget.set_no_show_all(True)
+				widget.hide()
+			else:
+				widget.set_no_show_all(False)
+				widget.show_all()
+
 ################################################################################
 class ChatControl(ChatControlBase):
 	'''A control for standard 1-1 chat'''
@@ -1004,28 +1031,6 @@ class ChatControl(ChatControlBase):
 
 		return tab_img
 
-	def remove_possible_switch_to_menuitems(self, menu):
-		''' remove duplicate 'Switch to' if they exist and return clean menu'''
-		childs = menu.get_children()
-
-		contact = self.parent_win.get_active_contact()
-		jid = contact.jid
-		if _('not in the roster') in contact.groups: # for add_to_roster_menuitem
-			childs[5].show()
-			childs[5].set_no_show_all(False)
-		else:
-			childs[5].hide()
-			childs[5].set_no_show_all(True)
-		start_removing_from = 6 # this is from the seperator and after
-			
-# FIXME: GC only
-#		elif :
-#			start_removing_from = 7 # # this is from the seperator and after
-				
-		for child in childs[start_removing_from:]:
-			menu.remove(child)
-		return menu
-
 	def prepare_context_menu(self):
 		'''sets compact view menuitem active state
 		sets active and sensitivity state for toggle_gpg_menuitem
@@ -1051,28 +1056,6 @@ class ChatControl(ChatControlBase):
 		
 		return menu
 
-	def set_compact_view(self, state):
-		'''Toggle compact view. state is bool'''
-		ChatControlBase.set_compact_view(self, state)
-
-		widgets = [
-		self.xml.get_widget('banner_eventbox'),
-		self.xml.get_widget('actions_hbox'),
-		]
-# FIXME GC only
-#		elif self.widget_name == 'groupchat_window':
-#			widgets = [self.xmls[jid].get_widget('banner_eventbox'),
-#				self.xmls[jid].get_widget('gc_actions_hbox'),
-#				self.xmls[jid].get_widget('list_scrolledwindow'),
-#				 ]
-		for widget in widgets:
-			if state:
-				widget.set_no_show_all(True)
-				widget.hide()
-			else:
-				widget.set_no_show_all(False)
-				widget.show_all()
-
 	def send_chatstate(self, state, contact = None):
 		''' sends OUR chatstate as STANDLONE chat state message (eg. no body)
 		to contact only if new chatstate is different from the previous one
@@ -1292,9 +1275,8 @@ class ChatControl(ChatControlBase):
 		# Is it a pm ?
 		is_pm = False
 		room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
-		# FIXME: Accessing gc's, here? 
-		gcs = gajim.interface.instances[self.account]['gc']
-		if gcs.has_key(room_jid):
+		control = gajim.interface.msg_win_mgr.get_control(room_jid)
+		if control.type_id == message_control.TYPE_GC:
 			is_pm = True
 		events_to_keep = []
 		for event in l:
@@ -1309,16 +1291,16 @@ 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])
 
 			# remove from gc nb_unread if it's pm or from roster
 			if is_pm:
-				gcs[room_jid].nb_unread[room_jid] -= 1
+				control.nb_unread -= 1
 			else:
 				gajim.interface.roster.nb_unread -= 1
 
 		if is_pm:
-			gcs[room_jid].show_title()
+			control.parent_win.show_title()
 		else:
 			gajim.interface.roster.show_title()
 		# Keep only non-messages events
@@ -1327,12 +1309,10 @@ class ChatControl(ChatControlBase):
 		else:
 			del gajim.awaiting_events[self.account][jid]
 		typ = 'chat' # Is it a normal chat or a pm ?
-#		# reset to status image in gc if it is a pm
-#		# FIXME: New data structure
-#		gcs = gajim.interface.instances[self.account]['gc']
-#		if gcs.has_key(room_jid):
-#			gcs[room_jid].draw_all_roster()
-#			typ = 'pm'
+		# reset to status image in gc if it is a pm
+		if is_pm:
+			control.draw_widgets()
+			typ = 'pm'
 
 		gajim.interface.roster.draw_contact(jid, self.account)
 		if gajim.interface.systray_enabled:
@@ -1344,7 +1324,7 @@ class ChatControl(ChatControlBase):
 				gajim.interface.roster.really_remove_contact(self.contact,
 										self.account)
 			elif typ == 'pm':
-				gcs[room_jid].remove_contact(room_jid, nick)
+				control.remove_contact(nick)
 
 	def show_bigger_avatar(self, small_avatar):
 		'''resizes the avatar, if needed, so it has at max half the screen size
diff --git a/src/common/gajim.py b/src/common/gajim.py
index 2ae16817f9..6a01fdaa5a 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -145,8 +145,7 @@ def get_real_jid_from_fjid(account, fjid):
 	if not nick: # It's not a fake_jid, it is a real jid
 		return fjid # we return the real jid
 	real_jid = fjid
-	gcs = interface.instances[account]['gc']
-	if gcs.has_key(room_jid):
+	if interface.msg_win_mgr.get_control(room_jid):
 		# It's a pm, so if we have real jid it's in contact.jid
 		gc_contact = contacts.get_gc_contact(account, room_jid, nick)
 		if not gc_contact:
diff --git a/src/config.py b/src/config.py
index 756b8ff5e6..97704445e5 100644
--- a/src/config.py
+++ b/src/config.py
@@ -36,6 +36,7 @@ import gtkgui_helpers
 import dialogs
 import vcard
 import cell_renderer_image
+import message_control
 
 try:
 	import gtkspell
diff --git a/src/gajim.py b/src/gajim.py
index af10d4bfa2..adca48e09a 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -35,6 +35,8 @@ import pygtk
 
 import message_control
 
+from chat_control import ChatControlBase
+
 from common import exceptions
 from common import i18n
 i18n.init()
@@ -176,10 +178,9 @@ class Interface:
 		title = data[1]
 		prompt = data[2]
 		proposed_nick = data[3]
-		w = self.instances[account]['gc']
-		if w.has_key(room_jid): # user may close the window before we are here
-			w[room_jid].show_change_nick_input_dialog(title, prompt, proposed_nick,
-				room_jid)
+		gc_control = gajim.interface.msg_win_mgr.get_control(room_jid)
+		if gc_control: # user may close the window before we are here
+			gc_control.show_change_nick_input_dialog(title, prompt, proposed_nick)
 
 	def handle_event_http_auth(self, account, data):
 		#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj))
@@ -219,9 +220,9 @@ class Interface:
 					(jid_from, file_props))
 				conn.disconnect_transfer(file_props)
 				return
-		if jid_from in self.instances[account]['gc']:
-			self.instances[account]['gc'][jid_from].print_conversation(
-				'Error %s: %s' % (array[2], array[1]), jid_from)
+		ctl = gajim.interface.msg_win_mgr.get_control(jid_from)
+		if ctl and ctl.type_id == message_control.TYPE_GC:
+			ctl.print_conversation('Error %s: %s' % (array[2], array[1]))
 
 	def handle_event_con_type(self, account, con_type):
 		# ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp'
@@ -240,8 +241,9 @@ class Interface:
 			if not gajim.gc_connected.has_key(account):
 				return
 			for room_jid in gajim.gc_connected[account]:
-				if self.instances[account]['gc'].has_key(room_jid):
-					self.instances[account]['gc'][room_jid].got_disconnected(room_jid)
+				gc_control = gajim.interface.msg_win_mgr.get_control(room_jid)
+				if gc_control:
+					gc_control.got_disconnected()
 		else:
 			gobject.timeout_add(30000, self.allow_notif, account)
 			model[self.roster.status_message_menuitem_iter][3] = True # sensitivity for this menuitem
@@ -476,34 +478,33 @@ class Interface:
 		fjid = array[0]
 		jids = fjid.split('/', 1)
 		jid = jids[0]
-		gcs = self.instances[account]['gc']
-		if jid in gcs:
-			if len(jids) > 1: # it's a pm
-				nick = jids[1]
-				if not gajim.interface.msg_win_mgr.has_window(fjid):
-					gc = gcs[jid]
-					tv = gc.list_treeview[jid]
-					model = tv.get_model()
-					i = gc.get_contact_iter(jid, 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_contct(c)
-					self.roster.new_chat(c, account)
-				ctl = gajim.interface.msg_win_mgr.get_control(fjid)
-				ctl.print_conversation('Error %s: %s' % (array[1], array[2]),
-							'status')
+		gcs = gajim.interface.msg_win_mgr.get_controls(message_window.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 gajim.interface.msg_win_mgr.get_control(fjid):
+						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_contct(c)
+						self.roster.new_chat(c, account, private_chat = True)
+					ctl = gajim.interface.msg_win_mgr.get_control(fjid)
+					ctl.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
-			# FIXME
-			gcs[jid].print_conversation('Error %s: %s' % \
-				(array[1], array[2]), jid)
-			if gcs[jid].get_active_jid() == jid:
-				gcs[jid].set_subject(jid,
-					gcs[jid].subjects[jid])
-			return
+
 		if jid.find('@') <= 0:
 			jid = jid.replace('@', '')
 		self.roster.on_message(jid, _('error while sending') + \
@@ -723,12 +724,12 @@ class Interface:
 		#('GC_SUBJECT', account, (jid, subject))
 		jids = array[0].split('/', 1)
 		jid = jids[0]
-		if not self.instances[account]['gc'].has_key(jid):
+		gc_control = gajim.interface.msg_win_mgr.get_control(jid)
+		if not gc_control:
 			return
-		self.instances[account]['gc'][jid].set_subject(jid, array[1])
+		gc_control.set_subject(array[1])
 		if len(jids) > 1:
-			self.instances[account]['gc'][jid].print_conversation(
-				'%s has set the subject to %s' % (jids[1], array[1]), jid)
+			gc_control.print_conversation('%s has set the subject to %s' % (jids[1], array[1]))
 
 	def handle_event_gc_config(self, account, array):
 		#('GC_CONFIG', account, (jid, config))  config is a dict
@@ -953,14 +954,12 @@ class Interface:
 	def handle_event_signed_in(self, account, empty):
 		'''SIGNED_IN event is emitted when we sign in, so handle it'''
 		# join already open groupchats
-		for room_jid in self.instances[account]['gc']:
-			if room_jid == 'tabbed':
-				continue
+		for gc_control in gajim.interface.msg_win_mgr.get_controls(message_control.TYPE_GC):
+			room_jid = gc_control.contact.jid
 			if gajim.gc_connected[account][room_jid]:
 				continue
-			room, server = gajim.get_room_name_and_server_from_room_jid(
-				room_jid)
-			nick = self.instances[account]['gc'][room_jid].nicks[room_jid]
+			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]
@@ -1242,17 +1241,16 @@ class Interface:
 
 	def handle_event(self, account, jid, typ):
 		w = None
-		if typ == 'gc':
-			if wins['gc'].has_key(jid):
-				w = wins['gc'][jid]
-		elif typ == 'chat':
+		if typ == message_control.TYPE_GC:
+			w = gajim.interface.msg_win_mgr.get_window(jid)
+		elif typ == message_control.TYPE_CHAT:
 			if gajim.interface.msg_win_mgr.has_window(jid):
 				w = gajim.interface.msg_win_mgr.get_window(jid)
 			else:
 				contact = gajim.contacts.get_first_contact_from_jid(account, jid)
 				self.roster.new_chat(contact, account)
 				w = gajim.interface.msg_win_mgr.get_window(jid)
-		elif typ == 'pm':
+		elif typ == message_control.TYPE_PM:
 			if gajim.interface.msg_win_mgr.has_window(jid):
 				w = gajim.interface.msg_win_mgr.get_window(jid)
 			else:
@@ -1266,7 +1264,7 @@ class Interface:
 					gc_contact = gajim.contacts.create_gc_contact(room_jid = room_jid,
 						name = nick, show = show)
 				c = gajim.contacts.contact_from_gc_contct(gc_contact)
-				self.roster.new_chat(c, account)
+				self.roster.new_chat(c, account, private_chat = True)
 				w = gajim.interface.msg_win_mgr.get_window(jid)
 		elif typ in ('normal', 'file-request', 'file-request-error',
 			'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
@@ -1278,8 +1276,11 @@ class Interface:
 			w.set_active_tab(jid)
 			w.window.present()
 			w.window.window.focus()
-			tv = w.conversation_textviews[jid]
-			tv.scroll_to_end()
+			ctl = w.get_control(jid)
+			# Using isinstance here because we want to catch all derived types
+			if isinstance(ctl, ChatControlBase):
+				tv = ctl.conv_textview
+				tv.scroll_to_end()
 
 	def __init__(self):
 		gajim.interface = self
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index 42c813e1b2..9217b34d24 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -63,6 +63,28 @@ class PrivateChatControl(ChatControl):
 		self.TYPE_ID = 'pm'
 		self.display_name = _('Private chat')
 
+	def send_message(self, message):
+		'''call this function to send our message'''
+		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)
+		if contact is None:
+			# contact was from pm in MUC
+			room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
+			gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
+			if not gc_contact:
+				dialogs.ErrorDialog(
+					_('Sending private message failed'),
+					#in second %s code replaces with nickname
+					_('You are no longer in room "%s" or "%s" has left.') % \
+					(room, nick)).get_response()
+				return
+
+		ChatControl.send_message(self, message)
+
 class GroupchatControl(ChatControlBase):
 	TYPE_ID = message_control.TYPE_GC
 
@@ -255,7 +277,7 @@ class GroupchatControl(ChatControlBase):
 		menu = self.gc_popup_menu
 		childs = menu.get_children()
 		# compact_view_menuitem
-		childs[5].set_active(self.compact_view_current_state)
+		childs[5].set_active(self.compact_view_current)
 		menu = self.remove_possible_switch_to_menuitems(menu)
 		return menu
 
@@ -303,7 +325,8 @@ class GroupchatControl(ChatControlBase):
 		else:
 			gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
 			c = gajim.contacts.contact_from_gc_contact(gc_c)
-			gajim.interface.roster.new_chat(c, self.account)
+			print "creating PM chat"
+			gajim.interface.roster.new_chat(c, self.account, private_chat = True)
 		# Scroll to line
 		self.list_treeview.expand_row(path[0:1], False)
 		self.list_treeview.scroll_to_cell(path)
@@ -536,9 +559,32 @@ class GroupchatControl(ChatControlBase):
 		print "draw_roster"
 		for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
 			gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
-			self.add_contact_to_roster(self.room_jid, nick, gc_contact.show,
-				gc_contact.role, gc_contact.affiliation, gc_contact.status,
-				gc_contact.jid)
+			self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role,
+						gc_contact.affiliation, gc_contact.status,
+						gc_contact.jid)
+
+	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:
+			nick = model[iter][C_NICK].decode('utf-8')
+		fjid = gajim.construct_fjid(self.room_jid, nick) # 'fake' jid
+
+		chat_win = gajim.interface.msg_win_mgr.get_window(fjid)
+		if not chat_win:
+			gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+			c = gajim.contacts.contact_from_gc_contact(gc_c)
+			print "creating PM chat"
+			gajim.interface.roster.new_chat(c, self.account, private_chat = True)
+			chat_win = gajim.interface.msg_win_mgr.get_window(fjid)
+		chat_control = chat_win.get_control(fjid)
+
+		#make active here in case we need to send a message
+		chat_win.set_active_tab(fjid)
+
+		if msg:
+			chat_control.send_message(msg)
+		chat_win.window.present()
 
 	def draw_contact(self, nick, selected=False, focus=False):
 		iter = self.get_contact_iter(nick)
@@ -708,3 +754,308 @@ class GroupchatControl(ChatControlBase):
 		if model.iter_n_children(parent_iter) == 0:
 			model.remove(parent_iter)
 
+	def _process_command(self, message):
+		if message[0] != '/':
+			return False
+
+		# Handle common commands
+		if ChatControlBase._process_command(self, message):
+			return True
+
+		message = message[1:]
+		message_array = message.split(' ', 1)
+		command = message_array.pop(0).lower()
+		if message_array == ['']:
+			message_array = []
+		
+		if command == 'nick':
+			# example: /nick foo
+			if len(message_array):
+				nick = message_array[0]
+				gajim.connections[self.account].change_gc_nick(self.room_jid, nick)
+				self.clear(self.msg_textview)
+			else:
+				self.get_command_help(command)
+			return True
+		elif command == 'query' or command == 'chat':
+			# Open a chat window to the specified nick
+			# example: /query foo
+			if len(message_array):
+				nick = message_array.pop(0)
+				nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+				if nick in nicks:
+					self.on_send_pm(nick = nick)
+					self.clear(self.msg_textview)
+				else:
+					self.print_conversation(_('Nickname not found: %s') % nick)
+			else:
+				self.get_command_help(command)
+			return True
+		elif command == 'msg':
+			# Send a message to a nick.  Also opens a private message window.
+			# example: /msg foo Hey, what's up?
+			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)
+				if nick in room_nicks:
+					privmsg = ' '.join(message_array)
+					self.on_send_pm(nick=nick, msg=privmsg)
+					self.clear(self.msg_textview)
+				else:
+					self.print_conversation(_('Nickname not found: %s') % nick)
+			else:
+				self.get_command_help(command)
+			return True
+		elif command == 'topic':
+			# display or change the room topic
+			# example: /topic : print topic
+			# /topic foo : change topic to foo
+			if len(message_array):
+				new_topic = message_array.pop(0)
+				gajim.connections[self.account].send_gc_subject(self.room_jid,
+					new_topic)
+			else:
+				self.print_conversation(self.subject)
+			self.clear(self.msg_textview)
+			return True
+		elif command == 'invite':
+			# invite a user to a room for a reason
+			# example: /invite user@example.com reason
+			if len(message_array):
+				message_array = message_array[0].split()
+				invitee = message_array.pop(0)
+				if invitee.find('@') >= 0:
+					reason = ' '.join(message_array)
+					gajim.connections[self.account].send_invite(self.room_jid,
+						invitee, reason)
+					s = _('Invited %(contact_jid)s to %(room_jid)s.') % {
+						'contact_jid': invitee,
+						'room_jid': self.room_jid}
+					self.print_conversation(s)
+					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') % invitee
+					self.print_conversation(s)
+			else:
+				self.get_command_help(command)
+			return True
+		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)
+					else:
+						server = servernick
+						nick = ''
+					#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()
+					else:
+						try:
+							gajim.interface.instances[self.account]['join_gc'] =\
+								dialogs.JoinGroupchatWindow(self.account,
+									server = server, room = room, nick = nick)
+						except RuntimeError:
+							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
+					self.print_conversation(s)
+			else:
+				self.get_command_help(command)
+			return True
+		elif command == 'leave' or command == 'part' or command == 'close':
+			# Leave the room and close the tab or window
+			# FIXME: Sometimes this doesn't actually leave the room.  Why?
+			reason = 'offline'
+			if len(message_array):
+				reason = message_array.pop(0)
+			self.remove_tab(reason)
+			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)
+				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:
+					gajim.connections[self.account].gc_set_affiliation(self.room_jid,
+						nick, 'outcast', reason)
+					self.clear(self.msg_textview)
+				else:
+					self.print_conversation(_('Nickname not found: %s') % nick)
+			else:
+				self.get_command_help(command)
+			return True
+		elif command == 'kick':
+			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)
+				reason = ' '.join(message_array)
+				if nick in room_nicks:
+					gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+						'none', reason)
+					self.clear(self.msg_textview)
+				else:
+					self.print_conversation(_('Nickname not found: %s') % nick)
+			else:
+				self.get_command_help(command)
+			return True
+		elif command == 'help':
+			if len(message_array):
+				subcommand = message_array.pop(0)
+				self.get_command_help(subcommand)
+			else:
+				self.get_command_help(command)
+			self.clear(self.msg_textview)
+			return True
+		elif command == 'say':
+			if len(message_array):
+				gajim.connections[self.account].send_gc_message(self.room_jid,
+										message[4:])
+				self.clear(self.msg_textview)
+			else:
+				self.get_command_help(command)
+			return True
+		else:
+			self.print_conversation(_('No such command: /%s (if you want to send this, '
+						'prefix it with /say)') % command)
+			return True
+
+		return False
+
+	def send_message(self, message):
+		'''call this function to send our message'''
+		if not message:
+			return
+
+		if message != '' or message != '\n':
+			self.save_sent_message(message)
+
+			if not self._process_command(message):
+				# Send the message
+				gajim.connections[self.account].send_gc_message(self.room_jid, message)
+				self.msg_textview.get_buffer().set_text('')
+				self.msg_textview.grab_focus()
+
+	def get_command_help(self, command):
+		if command == 'help':
+			self.print_conversation(_('Commands: %s') % self.muc_cmds)
+		elif command == 'ban':
+			s = _('Usage: /%s <nickname|JID> [reason], bans the JID from the room.'
+			' 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
+			self.print_conversation(s)
+		elif command == 'chat' or command == 'query':
+			self.print_conversation(_('Usage: /%s <nickname>, opens a private chat '
+						'window to the specified occupant.') % command)
+		elif command == 'clear':
+			self.print_conversation(_('Usage: /%s, clears the text window.') % command)
+		elif command == 'close' or command == 'leave' or command == 'part':
+			self.print_conversation(_('Usage: /%s [reason], closes the current window '
+						'or tab, displaying reason if specified.') % command)
+		elif command == 'compact':
+			self.print_conversation(_('Usage: /%s, sets the groupchat window to compact '
+						'mode.') % command)
+		elif command == 'invite':
+			self.print_conversation(_('Usage: /%s <JID> [reason], invites JID to the '
+						'current room, optionally providing a reason.') % command)
+		elif command == 'join':
+			self.print_conversation(_('Usage: /%s <room>@<server>[/nickname], offers to '
+						'join room@server optionally using specified '
+						'nickname.') % command)
+		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)
+		elif command == 'me':
+			self.print_conversation(_('Usage: /%s <action>, sends action to the current '
+						'room. Use third person. (e.g. /%s explodes.)') %\
+						(command, command))
+		elif command == 'msg':
+			s = _('Usage: /%s <nickname> [message], opens a private message window and '
+				'sends message to the occupant specified by nickname.') % command
+			self.print_conversation(s)
+		elif command == 'nick':
+			s = _('Usage: /%s <nickname>, changes your nickname in current room.') % command
+			self.print_conversation(s)
+		elif command == 'topic':
+			self.print_conversation(_('Usage: /%s [topic], displays or updates the current '
+						'room topic.') % command)
+		elif command == 'say':
+			self.print_conversation(_('Usage: /%s <message>, sends a message without '
+						'looking for other commands.') % command)
+		else:
+			self.print_conversation(_('No help info for /%s') % command)
+
+	def get_role(self, nick):
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		if gc_contact:
+			return gc_contact.role
+		else:
+			return 'visitor'
+
+	def show_change_nick_input_dialog(self, title, prompt, proposed_nick = None):
+		'''asks user for new nick and on ok it sets it on room'''
+		instance = dialogs.InputDialog(title, prompt, proposed_nick)
+		response = instance.get_response()
+		if response == gtk.RESPONSE_OK:
+			nick = instance.input_entry.get_text().decode('utf-8')
+			self.nick = nick
+			gajim.connections[self.account].change_gc_nick(self.room_jid, nick)
+
+	def shutdown(self):
+		gajim.connections[self.account].send_gc_status(self.nick, self.room_jid,
+							show='offline', status=reason)
+		# They can already be removed by the destroy function
+		if self.room_jid in gajim.contacts.get_gc_list(self.account):
+			gajim.contacts.remove_room(self.account, self.room_jid)
+			del gajim.gc_connected[self.account][self.room_jid]
+
+	def allow_shutdown(self):
+		# FIXME:
+		# whether to ask for comfirmation before closing muc
+		if gajim.config.get('confirm_close_muc'):
+			names = []
+			if not room_jid:
+				for r_jid in self.xmls:
+					if gajim.gc_connected[self.account][r_jid]:
+						names.append(gajim.get_nick_from_jid(r_jid))
+			else:
+				names = [room_jid]
+
+			rooms_no = len(names)
+			if rooms_no >= 2: # if we are in many rooms
+				pritext = _('Are you sure you want to leave rooms "%s"?') % ', '.join(names)
+				sectext = _('If you close this window, you will be disconnected from these rooms.')
+
+			elif rooms_no == 1: # just in one room
+				pritext = _('Are you sure you want to leave room "%s"?') % names[0]
+				sectext = _('If you close this window, you will be disconnected from this room.')
+
+			if rooms_no > 0:
+				dialog = dialogs.ConfirmationDialogCheck(pritext, sectext,
+					_('Do _not ask me again'))
+
+				if dialog.is_checked():
+					gajim.config.set('confirm_close_muc', False)
+					dialog.destroy()
+
+				if dialog.get_response() != gtk.RESPONSE_OK:
+					return False
+		return True
diff --git a/src/gtkgui.glade b/src/gtkgui.glade
index 3c68130a42..0db888e164 100644
--- a/src/gtkgui.glade
+++ b/src/gtkgui.glade
@@ -19054,7 +19054,7 @@ Maybe I'll refactor later</property>
 	  <property name="show_tabs">True</property>
 	  <property name="show_border">True</property>
 	  <property name="tab_pos">GTK_POS_TOP</property>
-	  <property name="scrollable">False</property>
+	  <property name="scrollable">True</property>
 	  <property name="enable_popup">False</property>
 
 	  <child>
@@ -19875,7 +19875,7 @@ topic</property>
 		  </child>
 
 		  <child>
-		    <widget class="GtkScrolledWindow" id="scrolledwindow48">
+		    <widget class="GtkScrolledWindow" id="list_scrolledwindow">
 		      <property name="width_request">100</property>
 		      <property name="visible">True</property>
 		      <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
@@ -20108,7 +20108,7 @@ topic</property>
 			      <property name="right_padding">0</property>
 
 			      <child>
-				<widget class="GtkHBox" id="hbox3015">
+				<widget class="GtkHBox" id="gc_actions_hbox">
 				  <property name="visible">True</property>
 				  <property name="homogeneous">False</property>
 				  <property name="spacing">2</property>
diff --git a/src/message_window.py b/src/message_window.py
index 565b443ded..6cca9b3b65 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -558,12 +558,19 @@ class MessageWindowMgr:
 		return win
 
 	def get_control(self, jid):
-		'''Amonst all windows, return the MessageControl for jid'''
+		'''Amongst all windows, return the MessageControl for jid'''
 		win = self.get_window(jid)
 		if win:
 			return win.get_control(jid)
 		return None
 
+	def get_controls(self, type):
+		ctls = []
+		for c in self.controls():
+			if c.type_id == type:
+				ctls.append(c)
+		return ctls
+
 	def windows(self):
 		for w in self._windows.values():
 			yield w
diff --git a/src/roster_window.py b/src/roster_window.py
index 318a6bebcd..2f65bf045a 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -1529,11 +1529,9 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 														passphrase)
 					gajim.connections[account].gpg_passphrase(passphrase)
 
-		for room_jid in gajim.interface.instances[account]['gc']:
-			if room_jid != 'tabbed':
-				nick = gajim.interface.instances[account]['gc'][room_jid].nicks[room_jid]
-				gajim.connections[account].send_gc_status(nick, room_jid, status,
-					txt)
+		for gc_control in gajim.interface.msg_win_mgr.get_controls(message_control.TYPE_GC):
+			gajim.connections[account].send_gc_status(gc_control.nick, gc_control.room_jid,
+								status, txt)
 		gajim.connections[account].change_status(status, txt, sync, auto)
 		if status == 'online' and gajim.interface.sleeper.getState() != \
 			common.sleepy.STATE_UNKNOWN:
@@ -1672,14 +1670,24 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		self.update_status_combobox()
 		self.make_menu()
 
-	def new_chat(self, contact, account):
+	def new_chat(self, contact, account, private_chat = False):
 		# Get target window, create a control, and associate it with the window
+		if not private_chat:
+			type = message_control.TYPE_CHAT
+		else:
+			type = message_control.TYPE_PM
+
 		mw = gajim.interface.msg_win_mgr.get_window(contact.jid)
 		if not mw:
-			mw = gajim.interface.msg_win_mgr.create_window(contact, account,
-								ChatControl.TYPE_ID)
-		chat_control = ChatControl(mw, contact, account)
+			mw = gajim.interface.msg_win_mgr.create_window(contact, account, type)
+
+		if not private_chat:
+			chat_control = ChatControl(mw, contact, account)
+		else:
+			chat_control = PrivateChatControl(mw, contact, account)
+
 		mw.new_tab(chat_control)
+
 		if gajim.awaiting_events[account].has_key(contact.jid):
 			# We call this here to avoid race conditions with widget validation
 			chat_control.read_queue()
diff --git a/src/tooltips.py b/src/tooltips.py
index 068c2ddcb9..7a6577f22d 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -250,15 +250,13 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
 				else:
 					unread_pm += ctl.nb_unread
 
-			# FIXME
 			# we count unread gc/pm messages
-			gc_wins = gajim.interface.instances[acct]['gc']
-			for jid in gc_wins:
-				if jid != 'tabbed':
-					pm_msgs = gc_wins[jid].get_specific_unread(jid)
-					unread_gc += gc_wins[jid].nb_unread[jid]
-					unread_gc -= pm_msgs
-					unread_pm += pm_msgs
+			chat_t = message_control.TYPE_GC
+			for gc_control in gajim.interface.msg_win_mgr.get_controls(chat_t):
+				pm_msgs = gc_control.get_specific_unread()
+				unread_gc += gc_control.nb_unread
+				unread_gc -= pm_msgs
+				unread_pm += pm_msgs
 
 		if unread_chat or unread_single_chat or unread_gc or unread_pm:
 			text = ''
-- 
GitLab