roster_window.py 205 KB
Newer Older
1
# -*- coding: utf-8 -*-
2 3
##	roster_window.py
##
Yann Leboulanger's avatar
Yann Leboulanger committed
4
## Copyright (C) 2003-2007 Yann Leboulanger <asterix@lagaule.org>
5
## Copyright (C) 2005-2007 Nikos Kouremenos <kourem@gmail.com>
6
##                         Travis Shirk <travis@pobox.com>
7
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
8
## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net>
roidelapluie's avatar
roidelapluie committed
9
## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com>
10
## Copyright (C) 2007 Stephan Erb <steve-e@h3c.de>
11
##
12 13 14
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
15
## it under the terms of the GNU General Public License as published
16
## by the Free Software Foundation; version 3 only.
17
##
18
## Gajim is distributed in the hope that it will be useful,
19 20 21 22
## 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.
##
23 24 25
## You should have received a copy of the GNU General Public License
## along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
##
26 27

import gtk
28
import pango
29 30
import gobject
import os
31
import sys
32 33
import time

34
import common.sleepy
35 36
import history_window
import dialogs
nkour's avatar
nkour committed
37
import vcard
38
import config
39
import disco
dkirov's avatar
dkirov committed
40
import gtkgui_helpers
41
import cell_renderer_image
42
import tooltips
nicfit's avatar
nicfit committed
43
import message_control
Liorithiel's avatar
Liorithiel committed
44
import adhoc_commands
45
import notify
46
import features_window
47 48 49

from common import gajim
from common import helpers
50 51
from common import passwords
from common.exceptions import GajimGeneralException
52
from common import i18n
53

54
from message_window import MessageWindowMgr
55
from chat_control import ChatControl
nicfit's avatar
nicfit committed
56
from groupchat_control import GroupchatControl
57
from groupchat_control import PrivateChatControl
58

59 60
from session import ChatControlSession

61 62 63
from common import dbus_support
if dbus_support.supported:
	from music_track_listener import MusicTrackListener
64
	import dbus
65
from lastfm_track_listener import LastFMTrackListener
66

67 68 69 70
if sys.platform == 'darwin':
	from osx import syncmenu


71
#(icon, name, type, jid, account, editable, second pixbuf)
72 73 74 75
(
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
76
C_JID, # the jid of the row
77
C_ACCOUNT, # cellrenderer text that holds account name
78 79 80
C_AVATAR_PIXBUF, # avatar_pixbuf
C_PADLOCK_PIXBUF, # use for account row only
) = range(7)
81

82
class RosterWindow:
83
	'''Class for main window of the GTK+ interface'''
84 85

	def get_account_iter(self, name):
Liorithiel's avatar
Liorithiel committed
86
		''' Returns a gtk.TreeIter of accounts in roster data model or None '''
87
		model = self.tree.get_model()
nkour's avatar
nkour committed
88 89
		if model is None:
			return
90
		account_iter = model.get_iter_root()
91 92
		if self.regroup:
			return account_iter
93
		while account_iter:
94
			account_name = model[account_iter][C_ACCOUNT].decode('utf-8')
95 96
			if name == account_name:
				break
97 98
			account_iter = model.iter_next(account_iter)
		return account_iter
99 100

	def get_group_iter(self, name, account):
Liorithiel's avatar
Liorithiel committed
101
		''' Returns a gtk.TreeIter of groups in roster data model or None '''
102 103
		model = self.tree.get_model()
		root = self.get_account_iter(account)
104
		group_iter = model.iter_children(root)
105
		# C_NAME column contacts the pango escaped group name
106
		while group_iter:
107
			group_name = model[group_iter][C_JID].decode('utf-8')
108 109
			if name == group_name:
				break
110 111
			group_iter = model.iter_next(group_iter)
		return group_iter
112

113
	def get_contact_iter(self, jid, account):
114 115 116 117 118 119
		if jid == gajim.get_jid_from_account(account):
			iter = self.get_self_contact_iter(account)
			if iter:
				return [iter]
			else:
				return []
120 121 122
		model = self.tree.get_model()
		acct = self.get_account_iter(account)
		found = []
nkour's avatar
nkour committed
123 124
		if model is None: # when closing Gajim model can be none (async pbs?)
			return found
125 126
		group_iter = model.iter_children(acct)
		while group_iter:
127 128 129 130 131
			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)
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
				# 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
155
			group_iter = model.iter_next(group_iter)
156 157
		return found

158 159 160 161 162 163 164 165 166
	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

167 168
	def show_and_select_path(self, path, jid, account):
		'''Show contact in roster (if he is invisible for example)
169 170 171 172 173 174 175
		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])
176
		if self.dragging or not gajim.config.get('scroll_roster_to_last_message'):
177 178
			# do not change selection while DND'ing
			return
179
		# popup == False so we show awaiting event in roster
180
		# show and select contact line in roster (even if he is not in roster)
181 182 183 184 185
		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)

186
	def add_account_to_roster(self, account):
Liorithiel's avatar
Liorithiel committed
187
		''' Add an account to roster data model. '''
188 189 190
		model = self.tree.get_model()
		if self.get_account_iter(account):
			return
191

Liorithiel's avatar
Liorithiel committed
192
		# if we merge accounts...
193 194
		if self.regroup:
			show = helpers.get_global_show()
195
			model.append(None, [self.jabber_state_images['16'][show],
196
				_('Merged accounts'), 'account', '', 'all', None, None])
197
			self.draw_account(account)
198 199
			return

nkour's avatar
nkour committed
200
		show = gajim.SHOW_LIST[gajim.connections[account].connected]
201 202

		tls_pixbuf = None
203
		if gajim.account_is_securely_connected(account):
204
			tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
205
				gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
206

207
		our_jid = gajim.get_jid_from_account(account)
208

209
		model.append(None, [self.jabber_state_images['16'][show],
210
			gobject.markup_escape_text(account),
211
			'account', our_jid, account, None, tls_pixbuf])
212

213
	def draw_account(self, account):
214 215 216 217 218 219 220 221
		if account in self.draw_account_id:
			return
		self.draw_account_id[account] = gobject.timeout_add(500,
			self.really_draw_account, account)

	def really_draw_account(self, account):
		if account in self.draw_account_id:
			del self.draw_account_id[account]
222 223
		model = self.tree.get_model()
		iter = self.get_account_iter(account)
224 225
		
		num_of_accounts = gajim.get_number_of_connected_accounts()
226
		num_of_secured = gajim.get_number_of_securely_connected_accounts()
227 228 229
		
		if gajim.account_is_securely_connected(account) and not self.regroup or \
		self.regroup and num_of_secured and num_of_secured == num_of_accounts:
230 231
			tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
				gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
232
			model[iter][C_PADLOCK_PIXBUF] = tls_pixbuf
233
		else:
234
			model[iter][C_PADLOCK_PIXBUF] = None
235
		path = model.get_path(iter)
236 237
		account_name = account
		accounts = [account]
238
		if self.regroup:
239 240
			account_name = _('Merged accounts')
			accounts = []
241
		if not self.tree.row_expanded(path) and model.iter_has_child(iter):
242 243
			# account row not expanded
			account_name = '[%s]' % account_name
244 245 246
		if (gajim.account_is_connected(account) or (self.regroup and \
		gajim.get_number_of_connected_accounts())) and gajim.config.get(
		'show_contacts_number'):
247
			nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
248
				accounts = accounts)
249
			account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
250
		model[iter][C_NAME] = account_name
251

252
	def remove_newly_added(self, jid, account):
253 254
		if jid in gajim.newly_added[account]:
			gajim.newly_added[account].remove(jid)
255 256
			self.draw_contact(jid, account)

257
	def add_contact_to_roster(self, jid, account):
258
		'''Add a contact to the roster and add groups if they aren't in roster
259
		force is about force to add it, even if it is offline and show offline
260 261 262
		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'''
263
		showOffline = gajim.config.get('showoffline')
264
		model = self.tree.get_model()
265
		contact = gajim.contacts.get_first_contact_from_jid(account, jid)
266 267
		nb_events = gajim.events.get_nb_roster_events(account, contact.jid)
		# count events from all resources
268
		for contact_ in gajim.contacts.get_contacts(account, jid):
269 270 271
			if contact_.resource:
				nb_events += gajim.events.get_nb_roster_events(account,
					contact_.get_full_jid())
272
		if not contact:
273
			return
274
		# If contact already in roster, do not add it
275
		if len(self.get_contact_iter(jid, account)):
276
			return
277
		if jid == gajim.get_jid_from_account(account):
278 279
			if contact.resource != gajim.connections[account].server_resource:
				self.add_self_contact(account)
280
			return
281
		if gajim.jid_is_transport(contact.jid):
282 283 284
			# 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
285
			contact.groups = [_('Transports')]
286 287 288
		elif not showOffline and not gajim.account_is_connected(account) and \
		nb_events == 0:
			return
289

290 291 292 293 294
		# XEP-0162
		hide = contact.is_hidden_from_roster()
		if hide and contact.sub != 'from':
			return
		observer = contact.is_observer()
295
		groupchat = contact.is_groupchat()
296

297 298 299 300 301 302 303 304 305 306
		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)

307
		# family members that are in roster and belong to the same account.
308
		shown_family = []
309 310 311
		if family:
			for data in family:
				_account = data['account']
312 313
				# Metacontacts over different accounts only in merged mode
				if _account != account and not self.regroup:
314 315
					continue
				_jid = data['jid']
316

317 318
				if self.get_contact_iter(_jid, _account):
					shown_family.append(data)
319
				if _jid == jid and _account == account:
320 321 322 323 324 325
					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']
326
			if big_brother_jid != jid or big_brother_account != account:
327 328
				# We are adding a child contact
				if contact.show in ('offline', 'error') and \
329
				not showOffline and len(gajim.events.get_events(account, jid)) == 0:
330 331 332 333 334 335
					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
336 337
					model.append(i, (None, name, 'contact', jid, account, None,
						None))
338
				self.draw_contact(jid, account)
Brendan Taylor's avatar
Brendan Taylor committed
339
				self.draw_avatar(jid, account)
340
				self.draw_account(account)
341 342 343 344
				# Redraw parent to change icon
				self.draw_contact(big_brother_jid, big_brother_account)
				return

345
		if (contact.show in ('offline', 'error') or hide) and \
346 347
		not showOffline and (not _('Transports') in contact.groups or \
		gajim.connections[account].connected < 2) and \
348
		len(gajim.contacts.get_contacts(account, jid)) == 1 and nb_events == 0 and\
349
		not _('Not in Roster') in contact.groups:
350 351
			return

352
		# Remove brother contacts that are already in roster to add them
353
		# under this iter
354
		for data in shown_family:
355
			contacts = gajim.contacts.get_contacts(data['account'],
356 357 358
				data['jid'])
			for c in contacts:
				self.remove_contact(c, data['account'])
359
		groups = contact.groups
360
		if observer:
361
			groups = [_('Observers')]
362
		elif not groups:
363
			groups = [_('General')]
364
		for group in groups:
365
			self.draw_group(group, account)
366
			iterG = self.get_group_iter(group, account)
367 368
			if not iterG:
				IterAcct = self.get_account_iter(account)
369 370
				iterG = model.append(IterAcct, [
					self.jabber_state_images['16']['closed'],
371
					gobject.markup_escape_text(group), 'group',
372
					group, account, None, None])
373 374 375 376 377
				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:
378 379 380
					ishidden = False
				else:
					ishidden = True
381
				gajim.groups[account][group] = {'expand': ishidden}
382
			if not account in self.collapsed_rows:
383 384
				self.tree.expand_row((model.get_path(iterG)[0]), False)

385
			typestr = 'contact'
386
			if group == _('Transports'):
387
				typestr = 'agent'
388
			if gajim.gc_connected[account].has_key(jid):
389
				typestr = 'groupchat'
390

nicfit's avatar
nicfit committed
391
			name = contact.get_shown_name()
392
			# we add some values here. see draw_contact for more
393 394
			model.append(iterG, (None, name, typestr, contact.jid, account, None,
				None))
395

396
			if gajim.groups[account][group]['expand']:
397
				self.tree.expand_row(model.get_path(iterG), False)
398
		self.draw_contact(jid, account)
Brendan Taylor's avatar
Brendan Taylor committed
399
		self.draw_avatar(jid, account)
400
		self.draw_account(account)
401
		# put the children under this iter
402
		for data in shown_family:
403
			contacts = gajim.contacts.get_contacts(data['account'],
404 405
				data['jid'])
			self.add_contact_to_roster(data['jid'], data['account'])
406

407
	def draw_group(self, group, account):
408 409 410 411 412 413 414 415 416 417
		key = (group, account)
		if key in self.draw_group_id:
			return
		self.draw_group_id[key] = gobject.timeout_add(500,
			self.really_draw_group, group, account)

	def really_draw_group(self, group, account):
		key = (group, account)
		if key in self.draw_group_id:
			del self.draw_group_id[key]
418 419 420 421 422 423 424
		iter = self.get_group_iter(group, account)
		if not iter:
			return
		if self.regroup:
			accounts = []
		else:
			accounts = [account]
Yann Leboulanger's avatar
Yann Leboulanger committed
425 426 427
		text = gobject.markup_escape_text(group)
		if group in gajim.connections[account].blocked_groups:
			text = '<span strikethrough="true">%s</span>' % text
428 429 430 431
		if gajim.config.get('show_contacts_number'):
			nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
				accounts = accounts, groups = [group])
			text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
432
		model = self.tree.get_model()
Yann Leboulanger's avatar
Yann Leboulanger committed
433
		model.set_value(iter, 1 , text)
434

435
	def add_to_not_in_the_roster(self, account, jid, nick = '', resource = ''):
436
		''' add jid to group "not in the roster", he MUST not be in roster yet,
437
		return contact '''
438 439 440 441 442
		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]
443 444 445
		contact = gajim.contacts.create_contact(jid = jid, name = nick,
			groups = [_('Not in Roster')], show = 'not in roster', status = '',
			sub = 'none', resource = resource, keyID = keyID)
446 447 448 449
		gajim.contacts.add_contact(account, contact)
		self.add_contact_to_roster(contact.jid, account)
		return contact

450 451 452 453 454 455
	def add_groupchat_to_roster(self, account, jid, nick = '', resource = '',
		status = ''):
		''' add groupchat to roster '''
		contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
		if contact == None:
			contact = gajim.contacts.create_contact(jid = jid, name = jid,
456
				groups = [_('Groupchats')], show = 'online',
457 458 459 460
				status = status, sub = 'none',
				resource = resource)
			gajim.contacts.add_contact(account, contact)
			self.add_contact_to_roster(jid, account)
461 462 463 464 465 466 467
			self.draw_group(_('Groupchats'), account)
		else:
			contact.show = 'online'
			self.draw_contact(jid, account)
			self.add_contact_to_roster(jid, account)
			for group in contact.groups:
				self.draw_group(group, account)
468
		self.draw_account(account)
469 470
		return contact

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
	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
487 488 489 490 491 492 493 494 495

		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

496 497 498
		model = self.tree.get_model()
		iterAcct = self.get_account_iter(account)
		model.append(iterAcct, (None, gajim.nicks[account], 'self_contact', jid,
499
			account, None, None))
500 501 502
		self.draw_contact(jid, account)
		self.draw_avatar(jid, account)

503 504 505 506 507
	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)
Yann Leboulanger's avatar
Yann Leboulanger committed
508
		self.add_contact_to_roster(transport, account)
509 510

	def really_remove_contact(self, contact, account):
511 512 513
		if not gajim.interface.instances.has_key(account):
			# Account has been deleted during the timeout that called us
			return
514
		if contact.jid in gajim.newly_added[account]:
515
			return
516 517 518
		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
519
			return
520 521
		if contact.jid in gajim.to_be_removed[account]:
			gajim.to_be_removed[account].remove(contact.jid)
522 523


524 525 526 527
		hide = contact.is_hidden_from_roster()

		show_offline = gajim.config.get('showoffline')
		show_transports = gajim.config.get('show_transports_group')
528 529 530 531 532 533 534 535 536

		nb_events = 0
		jid_list = [contact.jid]
		if contact.get_full_jid() != contact.jid:
			jid_list.append(contact.get_full_jid())
		for jid in jid_list:
			# dont't count printed_chat messages
			nb_events += gajim.events.get_nb_roster_events(account, jid, ['chat'])

537 538 539
		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 \
540
		gajim.account_is_disconnected(account))) and nb_events == 0:
541 542
			self.remove_contact(contact, account)
		else:
543 544 545 546 547 548 549 550 551
			# If it's a metacontact, big brother may have changed, so remove and
			# re-add
			model = self.tree.get_model()
			iters = self.get_contact_iter(contact.jid, account)
			if iters and model.iter_has_child(iters[0]):
				self.remove_contact(contact, account)
				self.add_contact_to_roster(contact.jid, account)
			else:
				self.draw_contact(contact.jid, account)
552

553 554 555
	def remove_contact(self, contact, account):
		'''Remove a contact from the roster'''
		if contact.jid in gajim.to_be_removed[account]:
556 557
			return
		model = self.tree.get_model()
558 559 560 561 562 563 564 565 566 567 568 569 570
		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)
571

572 573
		# Remove iters and group iter if they are empty
		for i in iters:
574 575
			parent_i = model.iter_parent(i)
			model.remove(i)
576 577 578 579 580 581 582 583
			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:
584
							break
585 586 587
					else:
						if gajim.groups[account].has_key(group):
							del gajim.groups[account][group]
588 589
				else:
					self.draw_group(group, account)
590 591 592 593 594 595 596 597 598

		# 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)
599

600 601
	def get_appropriate_state_images(self, jid, size = '16',
		icon_name = 'online'):
602
		'''check jid and return the appropriate state images dict for
603
		the demanded size. icon_name is taken into account when jid is from
604 605
		transport: transport iconset doesn't contain all icons, so we fall back
		to jabber one'''
nkour's avatar
nkour committed
606
		transport = gajim.get_transport_name_from_jid(jid)
607 608 609
		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]:
610 611
			return self.transports_state_images[size][transport]
		return self.jabber_state_images[size]
612

nkour's avatar
nkour committed
613
	def draw_contact(self, jid, account, selected = False, focus = False):
614
		'''draw the correct state image, name BUT not avatar'''
nkour's avatar
nkour committed
615
		# focus is about if the roster window has toplevel-focus or not
616
		model = self.tree.get_model()
617
		iters = self.get_contact_iter(jid, account)
618 619
		if len(iters) == 0:
			return
620
		contact_instances = gajim.contacts.get_contacts(account, jid)
621 622 623 624
		contact = gajim.contacts.get_highest_prio_contact_from_contacts(
			contact_instances)
		if not contact:
			return
625
		name = gobject.markup_escape_text(contact.get_shown_name())
626 627

		# gets number of unread gc marked messages
628
		if jid in gajim.interface.minimized_controls[account]:
629 630 631 632 633 634 635 636 637
			nb_unread = len(gajim.events.get_events(account, jid,
				['printed_marked_gc_msg']))
			nb_unread += \
				gajim.interface.minimized_controls[account][jid].get_nb_unread_pm()

			if nb_unread == 1:
				name = '%s *' % name
			elif nb_unread > 1:
				name = '%s [%s]' % (name, str(nb_unread))
638

639
		strike = False
640
		if jid in gajim.connections[account].blocked_contacts:
641 642 643 644 645 646 647 648 649 650 651 652
			strike = True
		else:
			groups = contact.groups
			if contact.is_observer():
				groups = [_('Observers')]
			elif not groups:
				groups = [_('General')]
			for group in groups:
				if group in gajim.connections[account].blocked_groups:
					strike = True
					break
		if strike:
653
			name = '<span strikethrough="true">%s</span>' % name
654

655 656 657 658 659 660
		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) + ')'
661

662
		# show (account_name) if there are 2 contact with same jid in merged mode
Yann Leboulanger's avatar
Yann Leboulanger committed
663 664 665
		if self.regroup:
			add_acct = False
			# look through all contacts of all accounts
666 667 668 669
			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):
Yann Leboulanger's avatar
Yann Leboulanger committed
670
					# [0] cause it'fster than highest_prio
671 672 673 674 675
					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):
Yann Leboulanger's avatar
Yann Leboulanger committed
676 677 678 679 680 681 682
						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 + ')'
683

684 685 686 687
		# 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 != '':
688
				status = helpers.reduce_chars_newlines(status, max_lines = 1)
nkour's avatar
nkour committed
689
				# escape markup entities and make them small italic and fg color
690
				color = gtkgui_helpers._get_fade_color(self.tree, selected, focus)
691
				colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue)
692 693
				name += \
					'\n<span size="small" style="italic" foreground="%s">%s</span>' \
694
					% (colorstring, gobject.markup_escape_text(status))
695

696
		iter = iters[0] # choose the icon with the first iter
697 698 699 700 701

		if gajim.gc_connected[account].has_key(jid):
			contact.show = 'online'
			model[iter][C_TYPE] = 'groupchat'

702
		icon_name = helpers.get_icon_name_to_show(contact, account)
703
		# look if another resource has awaiting events
704 705
		for c in contact_instances:
			c_icon_name = helpers.get_icon_name_to_show(c, account)
706
			if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
707 708
				icon_name = c_icon_name
				break
709 710
		path = model.get_path(iter)
		if model.iter_has_child(iter):
711
			if not self.tree.row_expanded(path) and \
712
			icon_name not in ('event', 'muc_active', 'muc_inactive'):
713 714 715 716
				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')
717
					child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
718
					child_contact = gajim.contacts.get_contact_with_highest_priority(
719 720 721
						child_account, child_jid)
					child_icon_name = helpers.get_icon_name_to_show(child_contact,
						child_account)
722 723
					if child_icon_name not in ('error', 'not in roster'):
						icon_name = child_icon_name
724 725 726
				while child_iter:
					# a child has awaiting messages ?
					child_jid = model[child_iter][C_JID].decode('utf-8')
727
					child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
728
					if len(gajim.events.get_events(child_account, child_jid)):
729
						icon_name = 'event'
730 731 732 733
						break
					child_iter = model.iter_next(child_iter)
			if self.tree.row_expanded(path):
				state_images = self.get_appropriate_state_images(jid,
734
					size = 'opened', icon_name = icon_name)
735
			else:
736
				state_images = self.get_appropriate_state_images(jid,
737
					size = 'closed', icon_name = icon_name)
738
		else:
739
			# redraw parent
740
			self.draw_parent_contact(jid, account)
741 742
			state_images = self.get_appropriate_state_images(jid,
				icon_name = icon_name)
743

744
		img = state_images[icon_name]
745

746
		for iter in iters:
747 748 749
			model[iter][C_IMG] = img
			model[iter][C_NAME] = name

750 751 752 753 754 755 756 757 758 759
	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')
760 761
		parent_account = model[parent_iter][C_ACCOUNT].decode('utf-8')
		self.draw_contact(parent_jid, parent_account)
762

763 764
	def draw_avatar(self, jid, account):
		'''draw the avatar'''
Brendan Taylor's avatar
Brendan Taylor committed
765 766
		if not gajim.config.get('show_avatars_in_roster'):
			return
767 768
		model = self.tree.get_model()
		iters = self.get_contact_iter(jid, account)
769
		if gajim.config.get('show_avatars_in_roster'):
770
			pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
771 772 773 774 775 776
			if pixbuf in ('ask', None):
				scaled_pixbuf = None
			else:
				scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
		else:
			scaled_pixbuf = None
777
		for iter in iters:
778
			model[iter][C_AVATAR_PIXBUF] = scaled_pixbuf
779

780 781
	def join_gc_room(self, account, room_jid, nick, password, minimize=False,
		is_continued=False):
Brendan Taylor's avatar
Brendan Taylor committed
782
		'''joins the room immediately'''
783 784
		if gajim.interface.msg_win_mgr.has_window(room_jid, account) and \
				gajim.gc_connected[account][room_jid]:
785
			win = gajim.interface.msg_win_mgr.get_window(room_jid, account)
Brendan Taylor's avatar
Brendan Taylor committed
786
			ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account)
787
			win.window.present()
Brendan Taylor's avatar
Brendan Taylor committed
788
			win.set_active_tab(ctrl)
789
			dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid)
Yann Leboulanger's avatar
Yann Leboulanger committed
790
			return
791 792 793
		minimized_control_exists = False
		if room_jid in gajim.interface.minimized_controls[account]:
			minimized_control_exists = True
794 795
		invisible_show = gajim.SHOW_LIST.index('invisible')
		if gajim.connections[account].connected == invisible_show:
Yann Leboulanger's avatar
Yann Leboulanger committed
796 797
			dialogs.ErrorDialog(
				_('You cannot join a group chat while you are invisible'))
798
			return
799 800
		if minimize and not minimized_control_exists and \
		not gajim.interface.msg_win_mgr.has_window(room_jid, account):
801 802
			contact = gajim.contacts.create_contact(jid = room_jid, name = nick)
			gc_control = GroupchatControl(None, contact, account)
803
			gajim.interface.minimized_controls[account][room_jid] = gc_control
804 805 806
			gajim.connections[account].join_gc(nick, room_jid, password)
			if password:
				gajim.gc_passwords[room_jid] = password
807
			self.add_groupchat_to_roster(account, room_jid)
808
			return
809 810
		if not minimized_control_exists and \
			not gajim.interface.msg_win_mgr.has_window(room_jid, account):
811
			self.new_room(room_jid, nick, account, is_continued=is_continued)
812 813
		if not minimized_control_exists:
			gc_win = gajim.interface.msg_win_mgr.get_window(room_jid, account)
Brendan Taylor's avatar
Brendan Taylor committed
814 815
			gc_control = gc_win.get_gc_control(room_jid, account)
			gc_win.set_active_tab(gc_control)
816
			gc_win.window.present()
817
		gajim.connections[account].join_gc(nick, room_jid, password)
818 819
		if password:
			gajim.gc_passwords[room_jid] = password
820 821 822 823
		contact = gajim.contacts.get_contact_with_highest_priority(account, \
			room_jid)
		if contact or minimized_control_exists:
			self.add_groupchat_to_roster(account, room_jid)
824

825 826
	def on_actions_menuitem_activate(self, widget):
		self.make_menu()
827

828
	def on_edit_menuitem_activate(self, widget):
jimpp's avatar
jimpp committed
829
		'''need to call make_menu to build profile, avatar item'''
830
		self.make_menu()
831

832
	def on_bookmark_menuitem_activate(self, widget, account, bookmark):
Yann Leboulanger's avatar
Yann Leboulanger committed
833 834
		self.join_gc_room(account, bookmark['jid'], bookmark['nick'],
			bookmark['password'])
835

836
	def on_send_server_message_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
837 838
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/online'
839
		dialogs.SingleMessageWindow(account, server, 'send')
nkour's avatar
nkour committed
840 841

	def on_xml_console_menuitem_activate(self, widget, account):
842 843
		if gajim.interface.instances[account].has_key('xml_console'):
			gajim.interface.instances[account]['xml_console'].window.present()
nkour's avatar
nkour committed
844
		else:
845 846
			gajim.interface.instances[account]['xml_console'] = \
				dialogs.XMLConsoleWindow(account)
847

848 849 850 851 852 853
	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)
854

855
	def on_set_motd_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
856 857
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/motd'
858
		dialogs.SingleMessageWindow(account, server, 'send')
859 860

	def on_update_motd_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
861 862
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/motd/update'
863
		dialogs.SingleMessageWindow(account, server, 'send')
864 865

	def on_delete_motd_menuitem_activate(self, widget, account):
nkour's avatar
nkour committed
866 867
		server = gajim.config.get_per('accounts', account, 'hostname')
		server += '/announce/motd/delete'
868 869
		gajim.connections[account].send_motd(server)

870
	def on_history_manager_menuitem_activate(self, widget):
nkour's avatar
nkour committed
871 872
		if os.name == 'nt':
			if os.path.exists('history_manager.exe'): # user is running stable
873
				helpers.exec_command('history_manager.exe')
nkour's avatar
nkour committed
874
			else: # user is running svn
Yann Leboulanger's avatar
Yann Leboulanger committed
875
				helpers.exec_command('python history_manager.py')
876
		else: # Unix user
Yann Leboulanger's avatar
Yann Leboulanger committed
877
			helpers.exec_command('python history_manager.py &')
nkour's avatar
nkour committed
878 879

	def get_and_connect_advanced_menuitem_menu(self, account):
880
		'''adds FOR ACCOUNT options'''
881
		xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade')
nkour's avatar
nkour committed
882
		advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu')
883

nkour's avatar
nkour committed
884
		xml_console_menuitem = xml.get_widget('xml_console_menuitem')
885
		privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem')
nkour's avatar
nkour committed
886 887 888 889 890 891
		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')
892

nkour's avatar
nkour committed
893 894 895
		xml_console_menuitem.connect('activate',
			self.on_xml_console_menuitem_activate, account)

Yann Leboulanger's avatar
Yann Leboulanger committed
896 897
		if gajim.connections[account] and gajim.connections[account].\
		privacy_rules_supported:
898 899 900 901 902 903 904 905 906 907 908 909 910 911
			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:
			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_server_message_menuitem.connect('activate',
				self.on_send_server_message_menuitem_activate, account)
nkour's avatar
nkour committed
912

913 914
			set_motd_menuitem.connect('activate',
				self.on_set_motd_menuitem_activate, account)
nkour's avatar
nkour committed
915

916 917
			update_motd_menuitem.connect('activate',
				self.on_update_motd_menuitem_activate, account)
918

919 920
			delete_motd_menuitem.connect('activate',
				self.on_delete_motd_menuitem_activate, account)
921

nkour's avatar
nkour committed
922
		advanced_menuitem_menu.show_all()
923

nkour's avatar
nkour committed
924
		return advanced_menuitem_menu
925

926 927 928 929 930 931 932 933 934 935 936
	def set_actions_menu_needs_rebuild(self):
		self.actions_menu_needs_rebuild = True
		# Force the rebuild now since the on_activates on the menu itself does
		# not work with the os/x top level menubar
		if sys.platform == 'darwin':
			self.make_menu(force = True)
		return

	def make_menu(self, force = False):
		'''create the main window\'s menus'''
		if not force and not self.actions_menu_needs_rebuild:
937
			return
938
		new_chat_menuitem = self.xml.get_widget('new_chat_menuitem')
939
		single_message_menuitem = self.xml.get_widget('send_single_message_menuitem')
940
		join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')