roster_window.py 149 KB
Newer Older
dkirov's avatar
dkirov committed
1
# -*- coding: utf-8 -*-
2 3
##	roster_window.py
##
4 5 6
## 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>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
##
## 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 gtk
import gobject
import os
import time
sb's avatar
sb committed
22
import urllib
23

24
import common.sleepy
25 26
import history_window
import dialogs
nkour's avatar
nkour committed
27
import vcard
28
import config
29
import disco
dkirov's avatar
dkirov committed
30
import gtkgui_helpers
31
import cell_renderer_image
32
import tooltips
nicfit's avatar
nicfit committed
33
import message_control
sb's avatar
sb committed
34
import notify
35 36 37

from common import gajim
from common import helpers
38
from message_window import MessageWindowMgr
39
from chat_control import ChatControl
40
from groupchat_control import GroupchatControl
41
from groupchat_control import PrivateChatControl
42

dkirov's avatar
dkirov committed
43 44 45 46
import dbus_support
if dbus_support.supported:
	from music_track_listener import MusicTrackListener

47
#(icon, name, type, jid, account, editable, second pixbuf)
48 49 50 51
(
C_IMG, # image to show state (online, new message etc)
C_NAME, # cellrenderer text that holds contact nickame
C_TYPE, # account, group or contact?
nkour's avatar
nkour committed
52
C_JID, # the jid of the row
53 54
C_ACCOUNT, # cellrenderer text that holds account name
C_EDITABLE, # cellrenderer text that holds name editable or not?
55
C_SECPIXBUF, # secondary_pixbuf (holds avatar or padlock)
56 57
) = range(7)

58
class RosterWindow:
59
	'''Class for main window of gtkgui interface'''
60 61 62

	def get_account_iter(self, name):
		model = self.tree.get_model()
nkour's avatar
nkour committed
63 64
		if model is None:
			return
65
		account_iter = model.get_iter_root()
66 67
		if self.regroup:
			return account_iter
68
		while account_iter:
sb's avatar
sb committed
69
			account_name = model[account_iter][C_ACCOUNT].decode('utf-8')
70 71
			if name == account_name:
				break
72 73
			account_iter = model.iter_next(account_iter)
		return account_iter
74 75 76 77

	def get_group_iter(self, name, account):
		model = self.tree.get_model()
		root = self.get_account_iter(account)
78
		group_iter = model.iter_children(root)
79 80
		# C_NAME column contacts the pango escaped group name
		name = gtkgui_helpers.escape_for_pango_markup(name)
81 82
		while group_iter:
			group_name = model[group_iter][C_NAME].decode('utf-8')
83 84
			if name == group_name:
				break
85 86
			group_iter = model.iter_next(group_iter)
		return group_iter
87

88
	def get_contact_iter(self, jid, account):
sb's avatar
sb committed
89 90 91 92 93 94
		if jid == gajim.get_jid_from_account(account):
			iter = self.get_self_contact_iter(account)
			if iter:
				return [iter]
			else:
				return []
95 96 97
		model = self.tree.get_model()
		acct = self.get_account_iter(account)
		found = []
nkour's avatar
nkour committed
98 99
		if model is None: # when closing Gajim model can be none (async pbs?)
			return found
100 101
		group_iter = model.iter_children(acct)
		while group_iter:
102 103 104 105 106
			contact_iter = model.iter_children(group_iter)
			while contact_iter:
				if jid == model[contact_iter][C_JID].decode('utf-8') and \
					account == model[contact_iter][C_ACCOUNT].decode('utf-8'):
					found.append(contact_iter)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
				# find next contact iter
				if model.iter_has_child(contact_iter):
					# his first child if it has some
					contact_iter = model.iter_children(contact_iter)
				else:
					next_contact_iter = model.iter_next(contact_iter)
					if not next_contact_iter:
						# now we need to go up
						parent_iter = model.iter_parent(contact_iter)
						parent_type = model[parent_iter][C_TYPE]
						while parent_type != 'group':
							contact_iter = model.iter_next(parent_iter)
							if contact_iter:
								break
							else:
								parent_iter = model.iter_parent(parent_iter)
								parent_type = model[parent_iter][C_TYPE]
						else:
							# we tested all contacts in this group
							contact_iter = None
					else:
						# his brother if he has
						contact_iter = next_contact_iter
130
			group_iter = model.iter_next(group_iter)
131 132 133 134 135 136
		return found

	def add_account_to_roster(self, account):
		model = self.tree.get_model()
		if self.get_account_iter(account):
			return
137 138 139

		if self.regroup:
			show = helpers.get_global_show()
140
			model.append(None, [self.jabber_state_images['16'][show],
141
				_('Merged accounts'), 'account', '', 'all', False, None])
142
			self.draw_account(account)
143 144
			return

nkour's avatar
nkour committed
145
		show = gajim.SHOW_LIST[gajim.connections[account].connected]
146 147

		tls_pixbuf = None
148
		if gajim.con_types.has_key(account) and \
nkour's avatar
nkour committed
149
			gajim.con_types[account] in ('tls', 'ssl'):
150
			tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
151
				gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
152

153
		our_jid = gajim.get_jid_from_account(account)
154

155
		model.append(None, [self.jabber_state_images['16'][show],
156
			gtkgui_helpers.escape_for_pango_markup(account),
157
			'account', our_jid, account, False, tls_pixbuf])
158

159 160 161
	def draw_account(self, account):
		model = self.tree.get_model()
		iter = self.get_account_iter(account)
162 163 164 165 166 167 168 169 170 171 172
		if self.regroup:
			accounts = gajim.connections.keys()
		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:
173 174
			tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
				gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
175 176 177 178 179 180 181
			if num_of_secured < num_of_accounts:
				# Make it transparent
				colorspace = tls_pixbuf.get_colorspace()
				bps = tls_pixbuf.get_bits_per_sample()
				rowstride = tls_pixbuf.get_rowstride()
				pixels = tls_pixbuf.get_pixels()
				new_pixels = ''
182 183 184
				width = tls_pixbuf.get_width()
				height = tls_pixbuf.get_height()
				for i in range(0, width*height):
185 186 187 188 189 190 191
					rgb = pixels[4*i:4*i+3]
					new_pixels += rgb
					if rgb == chr(0)*3:
						new_pixels += chr(0)
					else:
						new_pixels += chr(128)
				tls_pixbuf = gtk.gdk.pixbuf_new_from_data(new_pixels, colorspace,
192
					True, bps, width, height, rowstride)
193 194 195
			model[iter][C_SECPIXBUF] = tls_pixbuf
		else:
			model[iter][C_SECPIXBUF] = None
sb's avatar
sb committed
196 197 198 199 200 201 202
		path = model.get_path(iter)
		if self.regroup:
			account = _('Merged 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
203

204
	def remove_newly_added(self, jid, account):
205 206
		if jid in gajim.newly_added[account]:
			gajim.newly_added[account].remove(jid)
207 208
			self.draw_contact(jid, account)

209
	def add_contact_to_roster(self, jid, account):
210 211
		'''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
212 213 214
		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'''
215
		showOffline = gajim.config.get('showoffline')
216
		model = self.tree.get_model()
217 218
		contact = gajim.contacts.get_first_contact_from_jid(account, jid)
		if not contact:
219
			return
220
		# If contact already in roster, do not add it
221
		if len(self.get_contact_iter(jid, account)):
222
			return
sb's avatar
sb committed
223 224 225
		if jid == gajim.get_jid_from_account(account):
			self.add_self_contact(account)
			return
226
		if gajim.jid_is_transport(contact.jid):
227
			contact.groups = [_('Transports')]
228

229
		# JEP-0162
230
		hide = True
231
		if contact.sub in ('both', 'to'):
232
			hide = False
233
		elif contact.ask == 'subscribe':
234
			hide = False
235
		elif contact.name or len(contact.groups):
236 237
			hide = False

238
		observer = False
239 240 241 242 243
		if hide:
			if contact.sub == 'from':
				observer = True
			else:
				return
244

245 246 247 248 249 250 251 252 253 254
		if observer:
			# if he has a tag, remove it
			tag = gajim.contacts.get_metacontacts_tag(account, jid)
			if tag:
				gajim.contacts.remove_metacontact(account, jid)

		# family is [{'account': acct, 'jid': jid, 'priority': prio}, ]
		# 'priority' is optional
		family = gajim.contacts.get_metacontacts_family(account, jid)

255 256
		# family members that are in roster and belong to the same account.
		shown_family = [] 
257 258 259
		if family:
			for data in family:
				_account = data['account']
sb's avatar
sb committed
260 261
				# Metacontacts over different accounts only in merged mode
				if _account != account and not self.regroup:
262 263 264
					continue
				_jid = data['jid']
				
265 266
				if self.get_contact_iter(_jid, _account):
					shown_family.append(data)
sb's avatar
sb committed
267
				if _jid == jid and _account == account:
268 269 270 271 272 273
					our_data = data
			shown_family.append(our_data)
			big_brother_data = gajim.contacts.get_metacontacts_big_brother(
				shown_family)
			big_brother_jid = big_brother_data['jid']
			big_brother_account = big_brother_data['account']
sb's avatar
sb committed
274
			if big_brother_jid != jid or big_brother_account != account:
275 276
				# We are adding a child contact
				if contact.show in ('offline', 'error') and \
sb's avatar
sb committed
277
				not showOffline and len(gajim.events.get_events(account, jid)) == 0:
278 279 280 281 282 283 284 285 286 287 288 289 290 291
					return
				parent_iters = self.get_contact_iter(big_brother_jid,
					big_brother_account)
				name = contact.get_shown_name()
				for i in parent_iters:
					# we add some values here. see draw_contact for more
					model.append(i, (None, name, 'contact', jid, account,
						False, None))
				self.draw_contact(jid, account)
				self.draw_avatar(jid, account)
				# Redraw parent to change icon
				self.draw_contact(big_brother_jid, big_brother_account)
				return

292
		if (contact.show in ('offline', 'error') or hide) and \
293 294
			not showOffline and (not _('Transports') in contact.groups or \
			gajim.connections[account].connected < 2) and \
sb's avatar
sb committed
295 296
			len(gajim.events.get_events(account, jid)) == 0 and \
			not _('Not in Roster') in contact.groups:
297 298
			return

299
		# Remove brother contacts that are already in roster to add them
300
		# under this iter
301 302 303 304 305
		for data in shown_family:
			contacts = gajim.contacts.get_contact(data['account'],
				data['jid'])
			for c in contacts:
				self.remove_contact(c, data['account'])
306
		groups = contact.groups
307
		if observer:
308
			groups = [_('Observers')]
309
		elif not groups:
310 311
			groups = [_('General')]
		for g in groups:
312 313 314
			iterG = self.get_group_iter(g, account)
			if not iterG:
				IterAcct = self.get_account_iter(account)
315 316
				iterG = model.append(IterAcct, [
					self.jabber_state_images['16']['closed'],
Yann Leboulanger's avatar
Yann Leboulanger committed
317 318
					gtkgui_helpers.escape_for_pango_markup(g), 'group', g, account,
					False, None])
319
			if not gajim.groups[account].has_key(g): # It can probably never append
320 321 322 323
				if account + g in self.collapsed_rows:
					ishidden = False
				else:
					ishidden = True
324
				gajim.groups[account][g] = { 'expand': ishidden }
325
			if not account in self.collapsed_rows:
326 327
				self.tree.expand_row((model.get_path(iterG)[0]), False)

328
			typestr = 'contact'
329
			if g == _('Transports'):
330
				typestr = 'agent'
331

nicfit's avatar
nicfit committed
332
			name = contact.get_shown_name()
333
			# we add some values here. see draw_contact for more
334 335
			model.append(iterG, (None, name, typestr, contact.jid, account,
				False, None))
336

337
			if gajim.groups[account][g]['expand']:
338
				self.tree.expand_row(model.get_path(iterG), False)
339
		self.draw_contact(jid, account)
340
		self.draw_avatar(jid, account)
341
		# put the children under this iter
342 343 344 345
		for data in shown_family:
			contacts = gajim.contacts.get_contact(data['account'],
				data['jid'])
			self.add_contact_to_roster(data['jid'], data['account'])
346

sb's avatar
sb committed
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
	def get_self_contact_iter(self, account):
		model = self.tree.get_model()
		iterAcct = self.get_account_iter(account)
		iter = model.iter_children(iterAcct)
		if not iter:
			return None
		if model[iter][C_TYPE] == 'self_contact':
			return iter
		return None

	def add_self_contact(self, account):
		jid = gajim.get_jid_from_account(account)
		if self.get_self_contact_iter(account):
			self.draw_contact(jid, account)
			self.draw_avatar(jid, account)
			return

		contact = gajim.contacts.get_first_contact_from_jid(account, jid)
		if not contact:
			return
		showOffline = gajim.config.get('showoffline')
		if (contact.show in ('offline', 'error')) and not showOffline and \
			len(gajim.events.get_events(account, jid)) == 0:
			return

		model = self.tree.get_model()
		iterAcct = self.get_account_iter(account)
		model.append(iterAcct, (None, gajim.nicks[account], 'self_contact', jid,
			account, False, None))
		self.draw_contact(jid, account)
		self.draw_avatar(jid, account)

379 380 381 382 383 384 385 386
	def add_transport_to_roster(self, account, transport):
		c = gajim.contacts.create_contact(jid = transport, name = transport,
			groups = [_('Transports')], show = 'offline', status = 'offline',
			sub = 'from')
		gajim.contacts.add_contact(account, c)
		gajim.interface.roster.add_contact_to_roster(transport, account)

	def really_remove_contact(self, contact, account):
sb's avatar
sb committed
387 388 389
		if not gajim.interface.instances.has_key(account):
			# Account has been deleted during the timeout that called us
			return
390
		if contact.jid in gajim.newly_added[account]:
391
			return
392 393
		if contact.jid.find('@') < 1 and gajim.connections[account].connected > 1:
			# It's an agent
394
			return
395 396
		if contact.jid in gajim.to_be_removed[account]:
			gajim.to_be_removed[account].remove(contact.jid)
397 398 399 400 401 402 403 404 405 406 407 408 409
		# 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 \
sb's avatar
sb committed
410
			len(gajim.events.get_events(account, contact.jid, ['chat'])) == 0:
411 412 413
			self.remove_contact(contact, account)
		else:
			self.draw_contact(contact.jid, account)
414

415 416 417
	def remove_contact(self, contact, account):
		'''Remove a contact from the roster'''
		if contact.jid in gajim.to_be_removed[account]:
418 419
			return
		model = self.tree.get_model()
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
		iters = self.get_contact_iter(contact.jid, account)
		if not iters:
			return
		parent_iter = model.iter_parent(iters[0])
		parent_type = model[parent_iter][C_TYPE]
		# remember children to re-add them
		children = []
		child_iter = model.iter_children(iters[0])
		while child_iter:
			c_jid = model[child_iter][C_JID].decode('utf-8')
			c_account = model[child_iter][C_ACCOUNT].decode('utf-8')
			children.append((c_jid, c_account))
			child_iter = model.iter_next(child_iter)
		
		# Remove iters and group iter if they are empty
		for i in iters:
436 437
			parent_i = model.iter_parent(i)
			model.remove(i)
438 439 440 441 442 443 444 445
			if parent_type == 'group':
				group = model[parent_i][C_JID].decode('utf-8')
				if model.iter_n_children(parent_i) == 0:
					model.remove(parent_i)
					# We need to check all contacts, even offline contacts
					for jid in gajim.contacts.get_jid_list(account):
						if group in gajim.contacts.get_contact_with_highest_priority(
							account, jid).groups:
446
							break
447 448 449 450 451 452 453 454 455 456 457 458
					else:
						if gajim.groups[account].has_key(group):
							del gajim.groups[account][group]

		# re-add children
		for child in children:
			self.add_contact_to_roster(child[0], child[1])
		# redraw parent
		if parent_type == 'contact':
			parent_jid = model[parent_iter][C_JID].decode('utf-8')
			parent_account = model[parent_iter][C_ACCOUNT].decode('utf-8')
			self.draw_contact(parent_jid, parent_account)
459

460 461
	def get_appropriate_state_images(self, jid, size = '16',
		icon_name = 'online'):
462
		'''check jid and return the appropriate state images dict for
463 464 465
		the demanded size. icon_name is taken into account when jis is from
		transport: transport iconset doesn't contain all icons, so we fall back
		to jabber one'''
nkour's avatar
nkour committed
466
		transport = gajim.get_transport_name_from_jid(jid)
sb's avatar
sb committed
467 468 469
		if transport and self.transports_state_images.has_key(size) and \
		self.transports_state_images[size].has_key(transport) and icon_name in \
		self.transports_state_images[size][transport]:
470 471
			return self.transports_state_images[size][transport]
		return self.jabber_state_images[size]
472

nkour's avatar
nkour committed
473
	def draw_contact(self, jid, account, selected = False, focus = False):
474
		'''draw the correct state image, name BUT not avatar'''
nkour's avatar
nkour committed
475
		# focus is about if the roster window has toplevel-focus or not
476
		model = self.tree.get_model()
477
		iters = self.get_contact_iter(jid, account)
478 479
		if len(iters) == 0:
			return
480 481 482 483 484
		contact_instances = gajim.contacts.get_contact(account, jid)
		contact = gajim.contacts.get_highest_prio_contact_from_contacts(
			contact_instances)
		if not contact:
			return
485
		name = gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name())
486

nkour's avatar
nkour committed
487
		if len(contact_instances) > 1:
488
			name += ' (' + unicode(len(contact_instances)) + ')'
489

490
		# show (account_name) if there are 2 contact with same jid in merged mode
Yann Leboulanger's avatar
Yann Leboulanger committed
491 492 493 494
		if self.regroup:
			add_acct = False
			# look through all contacts of all accounts
			for a in gajim.connections:
495
				for j in gajim.contacts.get_jid_list(a):
Yann Leboulanger's avatar
Yann Leboulanger committed
496
					# [0] cause it'fster than highest_prio
497
					c = gajim.contacts.get_first_contact_from_jid(a, j)
Yann Leboulanger's avatar
Yann Leboulanger committed
498 499 500 501 502 503 504 505
					if c.name == contact.name and (j, a) != (jid, account):
						add_acct = True
						break
				if add_acct:
					# No need to continue in other account if we already found one
					break
			if add_acct:
				name += ' (' + account + ')'
506

507 508 509 510
		# add status msg, if not empty, under contact name in the treeview
		if contact.status and gajim.config.get('show_status_msgs_in_roster'):
			status = contact.status.strip()
			if status != '':
511
				status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 1)
nkour's avatar
nkour committed
512
				# escape markup entities and make them small italic and fg color
513
				color = gtkgui_helpers._get_fade_color(self.tree, selected, focus)
514
				colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue)
nkour's avatar
nkour committed
515 516
				name += '\n<span size="small" style="italic" foreground="%s">%s</span>'\
					% (colorstring, gtkgui_helpers.escape_for_pango_markup(status))
517

518 519
		iter = iters[0] # choose the icon with the first iter
		icon_name = helpers.get_icon_name_to_show(contact, account)
sb's avatar
sb committed
520
		# look if another resource has awaiting events
521 522 523 524 525
		for c in contact_instances:
			c_icon_name = helpers.get_icon_name_to_show(c, account)
			if c_icon_name == 'message':
				icon_name = c_icon_name
				break
526 527 528 529 530 531 532
		path = model.get_path(iter)
		if model.iter_has_child(iter):
			if not self.tree.row_expanded(path) and icon_name != 'message':
				child_iter = model.iter_children(iter)
				if icon_name in ('error', 'offline'):
					# get the icon from the first child as they are sorted by show
					child_jid = model[child_iter][C_JID].decode('utf-8')
sb's avatar
sb committed
533
					child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
534
					child_contact = gajim.contacts.get_contact_with_highest_priority(
sb's avatar
sb committed
535 536 537
						child_account, child_jid)
					child_icon_name = helpers.get_icon_name_to_show(child_contact,
						child_account)
538 539
					if child_icon_name not in ('error', 'not in roster'):
						icon_name = child_icon_name
540 541 542
				while child_iter:
					# a child has awaiting messages ?
					child_jid = model[child_iter][C_JID].decode('utf-8')
sb's avatar
sb committed
543 544
					child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
					if len(gajim.events.get_events(child_account, child_jid)):
545 546 547 548 549
						icon_name = 'message'
						break
					child_iter = model.iter_next(child_iter)
			if self.tree.row_expanded(path):
				state_images = self.get_appropriate_state_images(jid,
550
					size = 'opened', icon_name = icon_name)
551
			else:
552
				state_images = self.get_appropriate_state_images(jid,
553
					size = 'closed', icon_name = icon_name)
554
		else:
555
			# redraw parent
556
			self.draw_parent_contact(jid, account)
557 558
			state_images = self.get_appropriate_state_images(jid,
				icon_name = icon_name)
559 560
	
		img = state_images[icon_name]
561

562
		for iter in iters:
563 564 565
			model[iter][C_IMG] = img
			model[iter][C_NAME] = name

566 567 568 569 570 571 572 573 574 575
	def draw_parent_contact(self, jid, account):
		model = self.tree.get_model()
		iters = self.get_contact_iter(jid, account)
		if not len(iters):
			return
		parent_iter = model.iter_parent(iters[0])
		if model[parent_iter][C_TYPE] != 'contact':
			# parent is not a contact
			return
		parent_jid = model[parent_iter][C_JID].decode('utf-8')
sb's avatar
sb committed
576 577
		parent_account = model[parent_iter][C_ACCOUNT].decode('utf-8')
		self.draw_contact(parent_jid, parent_account)
578

579 580 581 582
	def draw_avatar(self, jid, account):
		'''draw the avatar'''
		model = self.tree.get_model()
		iters = self.get_contact_iter(jid, account)
583
		if gajim.config.get('show_avatars_in_roster'):
584
			pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
585 586 587 588 589 590
			if pixbuf in ('ask', None):
				scaled_pixbuf = None
			else:
				scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
		else:
			scaled_pixbuf = None
591
		for iter in iters:
592
			model[iter][C_SECPIXBUF] = scaled_pixbuf
593

Yann Leboulanger's avatar
Yann Leboulanger committed
594
	def join_gc_room(self, account, room_jid, nick, password):
595
		'''joins the room immediatelly'''
596 597
		if gajim.interface.msg_win_mgr.has_window(room_jid, account) and \
				gajim.gc_connected[account][room_jid]:
598 599 600
			win = gajim.interface.msg_win_mgr.get_window(room_jid,  account)
			win.window.present()
			win.set_active_tab(room_jid,  account)
601
			dialogs.ErrorDialog(_('You are already in room %s') % room_jid)
Yann Leboulanger's avatar
Yann Leboulanger committed
602
			return
603 604
		invisible_show = gajim.SHOW_LIST.index('invisible')
		if gajim.connections[account].connected == invisible_show:
605
			dialogs.ErrorDialog(_('You cannot join a room while you are invisible')
606
				)
607
			return
Yann Leboulanger's avatar
Yann Leboulanger committed
608
		room, server = room_jid.split('@')
609
		if not gajim.interface.msg_win_mgr.has_window(room_jid, account):
610
			self.new_room(room_jid, nick, account)
611 612
		gc_win = gajim.interface.msg_win_mgr.get_window(room_jid, account)
		gc_win.set_active_tab(room_jid, account)
nicfit's avatar
nicfit committed
613
		gc_win.window.present()
Yann Leboulanger's avatar
Yann Leboulanger committed
614
		gajim.connections[account].join_gc(nick, room, server, password)
615 616
		if password:
			gajim.gc_passwords[room_jid] = password
617

618 619
	def on_actions_menuitem_activate(self, widget):
		self.make_menu()
620 621
	
	def on_edit_menuitem_activate(self, widget):
jimpp's avatar
jimpp committed
622
		'''need to call make_menu to build profile, avatar item'''
623 624
		self.make_menu()
		
625
	def on_bookmark_menuitem_activate(self, widget, account, bookmark):
Yann Leboulanger's avatar
Yann Leboulanger committed
626 627
		self.join_gc_room(account, bookmark['jid'], bookmark['nick'],
			bookmark['password'])
628

629
	def on_send_server_message_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
630 631
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/online'
632
		dialogs.SingleMessageWindow(account, server, 'send')
nkour's avatar
nkour committed
633 634

	def on_xml_console_menuitem_activate(self, widget, account):
635 636
		if gajim.interface.instances[account].has_key('xml_console'):
			gajim.interface.instances[account]['xml_console'].window.present()
nkour's avatar
nkour committed
637
		else:
638 639
			gajim.interface.instances[account]['xml_console'] = \
				dialogs.XMLConsoleWindow(account)
640

sb's avatar
sb committed
641 642 643 644 645 646 647
	def on_privacy_lists_menuitem_activate(self, widget, account):
		if gajim.interface.instances[account].has_key('privacy_lists'):
			gajim.interface.instances[account]['privacy_lists'].window.present()
		else:
			gajim.interface.instances[account]['privacy_lists'] = \
				dialogs.PrivacyListsWindow(account)

648
	def on_set_motd_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
649 650
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/motd'
651
		dialogs.SingleMessageWindow(account, server, 'send')
652 653

	def on_update_motd_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
654 655
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/motd/update'
656
		dialogs.SingleMessageWindow(account, server, 'send')
657 658

	def on_delete_motd_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
659 660
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/motd/delete'
661 662
		gajim.connections[account].send_motd(server)

663
	def on_history_manager_menuitem_activate(self, widget):
nkour's avatar
nkour committed
664 665
		if os.name == 'nt':
			if os.path.exists('history_manager.exe'): # user is running stable
sb's avatar
sb committed
666
				helpers.exec_command('history_manager.exe')
nkour's avatar
nkour committed
667
			else: # user is running svn
sb's avatar
sb committed
668
				helpers.exec_command('python history_manager.py')
669
		else: # Unix user
sb's avatar
sb committed
670
			helpers.exec_command('python history_manager.py &')
nkour's avatar
nkour committed
671 672

	def get_and_connect_advanced_menuitem_menu(self, account):
673
		'''adds FOR ACCOUNT options'''
674
		xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade')
nkour's avatar
nkour committed
675
		advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu')
676

nkour's avatar
nkour committed
677 678 679
		send_single_message_menuitem = xml.get_widget(
			'send_single_message_menuitem')
		xml_console_menuitem = xml.get_widget('xml_console_menuitem')
sb's avatar
sb committed
680
		privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem')
nkour's avatar
nkour committed
681 682 683 684 685 686
		administrator_menuitem = xml.get_widget('administrator_menuitem')
		send_server_message_menuitem = xml.get_widget(
			'send_server_message_menuitem')
		set_motd_menuitem = xml.get_widget('set_motd_menuitem')
		update_motd_menuitem = xml.get_widget('update_motd_menuitem')
		delete_motd_menuitem = xml.get_widget('delete_motd_menuitem')
687

nkour's avatar
nkour committed
688 689 690 691 692 693
		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)

694 695 696 697 698
		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)
sb's avatar
sb committed
699

nkour's avatar
nkour committed
700 701 702 703 704 705 706 707
		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)

		update_motd_menuitem.connect('activate',
			self.on_update_motd_menuitem_activate, account)
708

nkour's avatar
nkour committed
709 710
		delete_motd_menuitem.connect('activate',
			self.on_delete_motd_menuitem_activate, account)
711

nkour's avatar
nkour committed
712
		advanced_menuitem_menu.show_all()
713

nkour's avatar
nkour committed
714
		return advanced_menuitem_menu
715

716
	def make_menu(self):
717
		'''create the main window's menus'''
Yann Leboulanger's avatar
Yann Leboulanger committed
718
		if not self.actions_menu_needs_rebuild:
719
			return
720
		new_chat_menuitem = self.xml.get_widget('new_chat_menuitem')
721
		join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
dkirov's avatar
dkirov committed
722 723 724 725 726
		iconset = gajim.config.get('iconset') 
		path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') 
		state_images = self.load_iconset(path) 
		if state_images.has_key('muc_active'): 
			join_gc_menuitem.set_image(state_images['muc_active'])
727 728
		add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem')
		service_disco_menuitem = self.xml.get_widget('service_disco_menuitem')
729 730 731
		advanced_menuitem = self.xml.get_widget('advanced_menuitem')
		show_offline_contacts_menuitem = self.xml.get_widget(
			'show_offline_contacts_menuitem')
732
		profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem')	
733

734 735 736 737
		# destroy old advanced menus
		for m in self.advanced_menus:
			m.destroy()

738 739
		# make it sensitive. it is insensitive only if no accounts are *available*
		advanced_menuitem.set_sensitive(True)
nkour's avatar
nkour committed
740

741
		if self.add_new_contact_handler_id:
742 743
			add_new_contact_menuitem.handler_disconnect(
				self.add_new_contact_handler_id)
744
			self.add_new_contact_handler_id = None
745

746 747
		if self.service_disco_handler_id:
			service_disco_menuitem.handler_disconnect(
748
				self.service_disco_handler_id)
749
			self.service_disco_handler_id = None
750

751 752 753 754
		if self.new_chat_menuitem_handler_id:
			new_chat_menuitem.handler_disconnect(
				self.new_chat_menuitem_handler_id)
			self.new_chat_menuitem_handler_id = None
755

756 757 758 759 760 761
		if self.profile_avatar_menuitem_handler_id:
			profile_avatar_menuitem.handler_disconnect(
				self.profile_avatar_menuitem_handler_id)
			self.profile_avatar_menuitem_handler_id = None


762
		# remove the existing submenus
763 764 765
		add_new_contact_menuitem.remove_submenu()
		service_disco_menuitem.remove_submenu()
		join_gc_menuitem.remove_submenu()
766
		new_chat_menuitem.remove_submenu()
767
		advanced_menuitem.remove_submenu()
768
		profile_avatar_menuitem.remove_submenu()
769

770
		# remove the existing accelerator
771
		if self.have_new_chat_accel:
772
			ag = gtk.accel_groups_from_object(self.window)[0]
773
			new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n,
nkour's avatar
nkour committed
774
				gtk.gdk.CONTROL_MASK)
775
			self.have_new_chat_accel = False
776

777 778 779
		gc_sub_menu = gtk.Menu() # gc is always a submenu
		join_gc_menuitem.set_submenu(gc_sub_menu)
		
780
		connected_accounts = gajim.get_number_of_connected_accounts()
781 782 783
		if connected_accounts > 1: # 2 or more accounts? make submenus
			add_sub_menu = gtk.Menu()
			disco_sub_menu = gtk.Menu()
784
			new_chat_sub_menu = gtk.Menu()
sb's avatar
sb committed
785

786
			for account in gajim.connections:
787 788
				if gajim.connections[account].connected <= 1:
					# if offline or connecting
789 790 791
					continue
				
				# join gc
792 793
				label = gtk.Label()
				label.set_markup('<u>' + account.upper() +'</u>')
794
				label.set_use_underline(False)
795 796
				gc_item = gtk.MenuItem()
				gc_item.add(label)
dkirov's avatar
dkirov committed
797
				gc_item.connect('state-changed', gtkgui_helpers.on_bm_header_changed_state)
798 799
				gc_sub_menu.append(gc_item)
				
jimpp's avatar
jimpp committed
800
				self.add_bookmarks_list(gc_sub_menu, account)
801

802 803
				# the 'manage gc bookmarks' item is showed
				# below to avoid duplicate code
804 805 806 807 808 809 810 811 812

				# add
				add_item = gtk.MenuItem(_('to %s account') % account, False)
				add_sub_menu.append(add_item)
				add_item.connect('activate', self.on_add_new_contact, account)

				# disco
				disco_item = gtk.MenuItem(_('using %s account') % account, False)
				disco_sub_menu.append(disco_item)
813 814
				disco_item.connect('activate',
					self.on_service_disco_menuitem_activate, account)
815

816 817
				# new chat
				new_chat_item = gtk.MenuItem(_('using account %s') % account,
818
					False)
819 820 821
				new_chat_sub_menu.append(new_chat_item)
				new_chat_item.connect('activate',
					self.on_new_chat_menuitem_activate,	account)
sb's avatar
sb committed
822 823 824 825 826 827 828

			add_new_contact_menuitem.set_submenu(add_sub_menu)
			add_sub_menu.show_all()
			service_disco_menuitem.set_submenu(disco_sub_menu)
			disco_sub_menu.show_all()
			new_chat_menuitem.set_submenu(new_chat_sub_menu)
			new_chat_sub_menu.show_all()
829 830 831 832 833

		elif connected_accounts == 1: # user has only one account
			for account in gajim.connections:
				if gajim.connections[account].connected > 1: # THE connected account
					# gc
jimpp's avatar
jimpp committed
834
					self.add_bookmarks_list(gc_sub_menu, account)
835 836
					# add
					if not self.add_new_contact_handler_id:
837 838
						self.add_new_contact_handler_id =\
							add_new_contact_menuitem.connect(
839 840 841 842 843
							'activate', self.on_add_new_contact, account)
					# disco
					if not self.service_disco_handler_id:
						self.service_disco_handler_id = service_disco_menuitem.connect(
							'activate', self.on_service_disco_menuitem_activate, account)
844 845 846 847
					# new chat
					if not self.new_chat_menuitem_handler_id:
						self.new_chat_menuitem_handler_id = new_chat_menuitem.\
							connect('activate', self.on_new_chat_menuitem_activate,
848
							account)
849 850
					# new chat accel
					if not self.have_new_chat_accel:
851
						ag = gtk.accel_groups_from_object(self.window)[0]
852
						new_chat_menuitem.add_accelerator('activate', ag,
853
							gtk.keysyms.n,	gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
854
						self.have_new_chat_accel = True
855
					
856 857
					break # No other account connected
		
858 859
		if connected_accounts == 0:
			# no connected accounts, make the menuitems insensitive
860
			new_chat_menuitem.set_sensitive(False)
861 862 863 864
			join_gc_menuitem.set_sensitive(False)
			add_new_contact_menuitem.set_sensitive(False)
			service_disco_menuitem.set_sensitive(False)
		else: # we have one or more connected accounts
865
			new_chat_menuitem.set_sensitive(True)
866 867 868
			join_gc_menuitem.set_sensitive(True)
			add_new_contact_menuitem.set_sensitive(True)
			service_disco_menuitem.set_sensitive(True)
869
			# show the 'manage gc bookmarks' item
870
			newitem = gtk.SeparatorMenuItem() # separator
871
			gc_sub_menu.append(newitem)
872

sb's avatar
sb committed
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
		connected_accounts_with_vcard = []
		for account in gajim.connections:
			if gajim.connections[account].connected > 1 and \
			gajim.connections[account].vcard_supported:
				connected_accounts_with_vcard.append(account)
		if len(connected_accounts_with_vcard) > 1:
			# 2 or more accounts? make submenus
			profile_avatar_sub_menu = gtk.Menu()
			for account in connected_accounts_with_vcard:
				# profile, avatar
				profile_avatar_item = gtk.MenuItem(_('of account %s') % account, 
					 False)
				profile_avatar_sub_menu.append(profile_avatar_item)
				profile_avatar_item.connect('activate', 
					self.on_profile_avatar_menuitem_activate, account)
			profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
			profile_avatar_sub_menu.show_all()
		elif len(connected_accounts_with_vcard) == 1: # user has only one account
			account = connected_accounts_with_vcard[0]
			# profile, avatar
			if not self.profile_avatar_menuitem_handler_id:
				self.profile_avatar_menuitem_handler_id = \
				profile_avatar_menuitem.connect('activate', self.\
				on_profile_avatar_menuitem_activate, account)

		if len(connected_accounts_with_vcard) == 0:
			profile_avatar_menuitem.set_sensitive(False)
		else:
			profile_avatar_menuitem.set_sensitive(True)

903 904 905 906
			newitem = gtk.ImageMenuItem(_('Manage Bookmarks...'))
			img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
				gtk.ICON_SIZE_MENU)
			newitem.set_image(img)
nkour's avatar
nkour committed
907
			newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
908 909 910
			gc_sub_menu.append(newitem)
			gc_sub_menu.show_all()
			
911
		# Advanced Actions
912 913
		if len(gajim.connections) == 0: # user has no accounts
			advanced_menuitem.set_sensitive(False)
914 915
		elif len(gajim.connections) == 1: # we have one acccount
			account = gajim.connections.keys()[0]
dkirov's avatar
dkirov committed
916
			advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
917
				account)
918
			self.advanced_menus.append(advanced_menuitem_menu)
919 920 921 922 923
		
			self._add_history_manager_menuitem(advanced_menuitem_menu)

			advanced_menuitem.set_submenu(advanced_menuitem_menu)
			advanced_menuitem_menu.show_all()
924 925
		else: # user has *more* than one account : build advanced submenus
			advanced_sub_menu = gtk.Menu()
926
			for account in gajim.connections:
927 928
				advanced_item = gtk.MenuItem(_('for account %s') % account, False)
				advanced_sub_menu.append(advanced_item)
nkour's avatar
nkour committed
929 930
				advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
					account)
931
				self.advanced_menus.append(advanced_menuitem_menu)
932
				advanced_item.set_submenu(advanced_menuitem_menu)
933
			
934
			self._add_history_manager_menuitem(advanced_sub_menu)
935
			
936 937
			advanced_menuitem.set_submenu(advanced_sub_menu)
			advanced_sub_menu.show_all()
938

nkour's avatar
nkour committed
939
		self.actions_menu_needs_rebuild = False
940

941 942 943 944 945 946 947 948 949 950 951 952 953
	def _add_history_manager_menuitem(self, menu):
		'''adds a seperator and History Manager menuitem BELOW for account 
		menuitems'''
		item = gtk.SeparatorMenuItem() # separator
		menu.append(item)
		
		# History manager
		item = gtk.ImageMenuItem(_('History Manager'))
		icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
			gtk.ICON_SIZE_MENU)
		item.set_image(icon)
		menu.append(item)
		item.connect('activate', self.on_history_manager_menuitem_activate)
954
		
jimpp's avatar
jimpp committed
955
	def add_bookmarks_list(self, gc_sub_menu, account):
956 957 958 959 960 961 962 963 964 965
		'''Print join new room item and bookmarks list for an account'''
		item = gtk.MenuItem(_('_Join New Room'))
		item.connect('activate', self.on_join_gc_activate, account)
		gc_sub_menu.append(item)

		for bookmark in gajim.connections[account].bookmarks:
			item = gtk.MenuItem(bookmark['name'], False) # Do not use underline
			item.connect('activate', self.on_bookmark_menuitem_activate,
				account, bookmark)
			gc_sub_menu.append(item)
966

dkirov's avatar
dkirov committed
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
	def _change_style(self, model, path, iter, option):
		if option is None:
			model[iter][C_NAME] = model[iter][C_NAME]
		elif model[iter][C_TYPE] == 'account':
			if option == 'account':
				model[iter][C_NAME] = model[iter][C_NAME]
		elif model[iter][C_TYPE] == 'group':
			if option == 'group':
				model[iter][C_NAME] = model[iter][C_NAME]
		elif model[iter][C_TYPE] == 'contact':
			if option == 'contact':
				model[iter][C_NAME] = model[iter][C_NAME]

	def change_roster_style(self, option):
		model = self.tree.get_model()
		model.foreach(self._change_style, option)
nicfit's avatar
nicfit committed
983 984
		for win in gajim.interface.msg_win_mgr.windows():
			win.repaint_themed_widgets()
985
		# update gc's roster
986 987 988
		for ctrl in gajim.interface.msg_win_mgr.controls():
			if ctrl.type_id == message_control.TYPE_GC:
				ctrl.update_ui()
989

990
	def draw_roster(self):
991
		'''clear and draw roster'''
992
		# clear the model, only if it is not empty
993 994 995
		model = self.tree.get_model()
		if model:
			model.clear()
996 997
		for acct in gajim.connections:
			self.add_account_to_roster(acct)
998
			self.add_account_contacts(acct)
999

1000
	def add_account_contacts(self, account):
1001
		'''adds contacts of group to roster treeview'''
1002 1003
		for jid in gajim.contacts.get_jid_list(account):
			self.add_contact_to_roster(jid, account)
sb's avatar
sb committed
1004
		self.draw_account(account)
1005

1006 1007 1008
	def fire_up_unread_messages_events(self, account):
		'''reads from db the unread messages, and fire them up'''
		for jid in gajim.contacts.get_jid_list(account):
1009
			results = gajim.logger.get_unread_msgs_for_jid(jid)
dkirov's avatar
dkirov committed
1010 1011
			for result in results:
				tim = time.localtime(float(result[2]))
1012 1013 1014
				self.on_message(jid, result[1], tim, account, msg_type = 'chat',
					msg_id = result[0])

1015
	def fill_contacts_and_groups_dicts(self, array, account):
Yann Leboulanger's avatar