[kepi] ability to use subkeys. Fixes #6051
[gajim.git] / src / roster_window.py
blob001cb3be1a3dddd29981cd91a2949a33fe993126
1 # -*- coding: utf-8 -*-
2 ## src/roster_window.py
3 ##
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
6 ## Stéphan Kochen <stephan AT kochen.nl>
7 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
8 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
9 ## Nikos Kouremenos <kourem AT gmail.com>
10 ## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
11 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
12 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
13 ## James Newton <redshodan AT gmail.com>
14 ## Tomasz Melcer <liori AT exroot.org>
15 ## Julien Pivotto <roidelapluie AT gmail.com>
16 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
17 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
18 ## Jonathan Schleifer <js-gajim AT webkeks.org>
20 ## This file is part of Gajim.
22 ## Gajim is free software; you can redistribute it and/or modify
23 ## it under the terms of the GNU General Public License as published
24 ## by the Free Software Foundation; version 3 only.
26 ## Gajim is distributed in the hope that it will be useful,
27 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
28 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 ## GNU General Public License for more details.
31 ## You should have received a copy of the GNU General Public License
32 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
35 import gtk
36 import pango
37 import gobject
38 import os
39 import sys
40 import time
41 import locale
43 import common.sleepy
44 import history_window
45 import dialogs
46 import vcard
47 import config
48 import disco
49 import gtkgui_helpers
50 import gui_menu_builder
51 import cell_renderer_image
52 import tooltips
53 import message_control
54 import adhoc_commands
55 import features_window
56 import plugins
57 import plugins.gui
59 from common import gajim
60 from common import helpers
61 from common.exceptions import GajimGeneralException
62 from common import i18n
63 from common import pep
64 from common import location_listener
65 from common import ged
67 from message_window import MessageWindowMgr
69 from common import dbus_support
70 if dbus_support.supported:
71 import dbus
73 from common.xmpp.protocol import NS_FILE
74 from common.pep import MOODS, ACTIVITIES
76 #(icon, name, type, jid, account, editable, second pixbuf)
78 C_IMG, # image to show state (online, new message etc)
79 C_NAME, # cellrenderer text that holds contact nickame
80 C_TYPE, # account, group or contact?
81 C_JID, # the jid of the row
82 C_ACCOUNT, # cellrenderer text that holds account name
83 C_MOOD_PIXBUF,
84 C_ACTIVITY_PIXBUF,
85 C_TUNE_PIXBUF,
86 C_LOCATION_PIXBUF,
87 C_AVATAR_PIXBUF, # avatar_pixbuf
88 C_PADLOCK_PIXBUF, # use for account row only
89 ) = range(11)
91 class RosterWindow:
92 """
93 Class for main window of the GTK+ interface
94 """
96 def _get_account_iter(self, name, model=None):
97 """
98 Return the gtk.TreeIter of the given account or None if not found
100 Keyword arguments:
101 name -- the account name
102 model -- the data model (default TreeFilterModel)
104 if model is None:
105 model = self.modelfilter
106 if model is None:
107 return
109 if self.regroup:
110 name = 'MERGED'
111 it = self._iters[name]['account']
113 if model == self.model or it is None:
114 return it
115 try:
116 return self.modelfilter.convert_child_iter_to_iter(it)
117 except RuntimeError:
118 return None
121 def _get_group_iter(self, name, account, model=None):
123 Return the gtk.TreeIter of the given group or None if not found
125 Keyword arguments:
126 name -- the group name
127 account -- the account name
128 model -- the data model (default TreeFilterModel)
130 if model is None:
131 model = self.modelfilter
132 if model is None:
133 return
135 if self.regroup:
136 account = 'MERGED'
138 if name not in self._iters[account]['groups']:
139 return None
141 it = self._iters[account]['groups'][name]
142 if model == self.model or it is None:
143 return it
144 try:
145 return self.modelfilter.convert_child_iter_to_iter(it)
146 except RuntimeError:
147 return None
150 def _get_self_contact_iter(self, account, model=None):
152 Return the gtk.TreeIter of SelfContact or None if not found
154 Keyword arguments:
155 account -- the account of SelfContact
156 model -- the data model (default TreeFilterModel)
158 jid = gajim.get_jid_from_account(account)
159 its = self._get_contact_iter(jid, account, model=model)
160 if its:
161 return its[0]
162 return None
165 def _get_contact_iter(self, jid, account, contact=None, model=None):
167 Return a list of gtk.TreeIter of the given contact
169 Keyword arguments:
170 jid -- the jid without resource
171 account -- the account
172 contact -- the contact (default None)
173 model -- the data model (default TreeFilterModel)
175 if model is None:
176 model = self.modelfilter
177 # when closing Gajim model can be none (async pbs?)
178 if model is None:
179 return []
181 if not contact:
182 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
183 if not contact:
184 # We don't know this contact
185 return []
187 if account not in self._iters:
188 return []
190 if jid not in self._iters[account]['contacts']:
191 return []
193 its = self._iters[account]['contacts'][jid]
195 if not its:
196 return []
198 if model == self.model:
199 return its
201 its2 = []
202 for it in its:
203 try:
204 its2.append(self.modelfilter.convert_child_iter_to_iter(it))
205 except RuntimeError:
206 pass
207 return its2
210 def _iter_is_separator(self, model, titer):
212 Return True if the given iter is a separator
214 Keyword arguments:
215 model -- the data model
216 iter -- the gtk.TreeIter to test
218 if model[titer][0] == 'SEPARATOR':
219 return True
220 return False
223 #############################################################################
224 ### Methods for adding and removing roster window items
225 #############################################################################
227 def add_account(self, account):
229 Add account to roster and draw it. Do nothing if it is already in
231 if self._get_account_iter(account):
232 # Will happen on reconnect or for merged accounts
233 return
235 if self.regroup:
236 # Merged accounts view
237 show = helpers.get_global_show()
238 it = self.model.append(None, [
239 gajim.interface.jabber_state_images['16'][show],
240 _('Merged accounts'), 'account', '', 'all', None, None, None,
241 None, None, None] + [None] * self.nb_ext_renderers)
242 self._iters['MERGED']['account'] = it
243 else:
244 show = gajim.SHOW_LIST[gajim.connections[account].connected]
245 our_jid = gajim.get_jid_from_account(account)
247 tls_pixbuf = None
248 if gajim.account_is_securely_connected(account):
249 # the only way to create a pixbuf from stock
250 tls_pixbuf = self.window.render_icon(
251 gtk.STOCK_DIALOG_AUTHENTICATION,
252 gtk.ICON_SIZE_MENU)
254 it = self.model.append(None, [
255 gajim.interface.jabber_state_images['16'][show],
256 gobject.markup_escape_text(account), 'account', our_jid,
257 account, None, None, None, None, None, tls_pixbuf] +
258 [None] * self.nb_ext_renderers)
259 self._iters[account]['account'] = it
261 self.draw_account(account)
264 def add_account_contacts(self, account):
266 Add all contacts and groups of the given account to roster, draw them
267 and account
269 self.starting = True
270 jids = gajim.contacts.get_jid_list(account)
272 for jid in jids:
273 self.add_contact(jid, account)
275 # Do not freeze the GUI when drawing the contacts
276 if jids:
277 # Overhead is big, only invoke when needed
278 self._idle_draw_jids_of_account(jids, account)
280 # Draw all known groups
281 for group in gajim.groups[account]:
282 self.draw_group(group, account)
283 self.draw_account(account)
285 self.starting = False
287 def _add_group_iter(self, account, group):
289 Add a group iter in roster and return the newly created iter
291 if self.regroup:
292 account_group = 'MERGED'
293 else:
294 account_group = account
295 delimiter = gajim.connections[account].nested_group_delimiter
296 group_splited = group.split(delimiter)
297 parent_group = delimiter.join(group_splited[:-1])
298 if parent_group in self._iters[account_group]['groups']:
299 iter_parent = self._iters[account_group]['groups'][parent_group]
300 elif parent_group:
301 iter_parent = self._add_group_iter(account, parent_group)
302 if parent_group not in gajim.groups[account]:
303 if account + parent_group in self.collapsed_rows:
304 is_expanded = False
305 else:
306 is_expanded = True
307 gajim.groups[account][parent_group] = {'expand': is_expanded}
308 else:
309 iter_parent = self._get_account_iter(account, self.model)
310 iter_group = self.model.append(iter_parent,
311 [gajim.interface.jabber_state_images['16']['closed'],
312 gobject.markup_escape_text(group), 'group', group, account, None,
313 None, None, None, None, None] + [None] * self.nb_ext_renderers)
314 self.draw_group(group, account)
315 self._iters[account_group]['groups'][group] = iter_group
316 return iter_group
318 def _add_entity(self, contact, account, groups=None,
319 big_brother_contact=None, big_brother_account=None):
321 Add the given contact to roster data model
323 Contact is added regardless if he is already in roster or not. Return
324 list of newly added iters.
326 Keyword arguments:
327 contact -- the contact to add
328 account -- the contacts account
329 groups -- list of groups to add the contact to.
330 (default groups in contact.get_shown_groups()).
331 Parameter ignored when big_brother_contact is specified.
332 big_brother_contact -- if specified contact is added as child
333 big_brother_contact. (default None)
335 added_iters = []
336 if big_brother_contact:
337 # Add contact under big brother
339 parent_iters = self._get_contact_iter(
340 big_brother_contact.jid, big_brother_account,
341 big_brother_contact, self.model)
342 assert len(parent_iters) > 0, 'Big brother is not yet in roster!'
344 # Do not confuse get_contact_iter: Sync groups of family members
345 contact.groups = big_brother_contact.get_shown_groups()[:]
347 for child_iter in parent_iters:
348 it = self.model.append(child_iter, [None,
349 contact.get_shown_name(), 'contact', contact.jid, account,
350 None, None, None, None, None, None] + \
351 [None] * self.nb_ext_renderers)
352 added_iters.append(it)
353 if contact.jid in self._iters[account]['contacts']:
354 self._iters[account]['contacts'][contact.jid].append(it)
355 else:
356 self._iters[account]['contacts'][contact.jid] = [it]
357 else:
358 # We are a normal contact. Add us to our groups.
359 if not groups:
360 groups = contact.get_shown_groups()
361 for group in groups:
362 child_iterG = self._get_group_iter(group, account,
363 model=self.model)
364 if not child_iterG:
365 # Group is not yet in roster, add it!
366 child_iterG = self._add_group_iter(account, group)
368 if contact.is_transport():
369 typestr = 'agent'
370 elif contact.is_groupchat():
371 typestr = 'groupchat'
372 else:
373 typestr = 'contact'
375 # we add some values here. see draw_contact
376 # for more
377 i_ = self.model.append(child_iterG, [None,
378 contact.get_shown_name(), typestr, contact.jid, account,
379 None, None, None, None, None, None] + \
380 [None] * self.nb_ext_renderers)
381 added_iters.append(i_)
382 if contact.jid in self._iters[account]['contacts']:
383 self._iters[account]['contacts'][contact.jid].append(i_)
384 else:
385 self._iters[account]['contacts'][contact.jid] = [i_]
387 # Restore the group expand state
388 if account + group in self.collapsed_rows:
389 is_expanded = False
390 else:
391 is_expanded = True
392 if group not in gajim.groups[account]:
393 gajim.groups[account][group] = {'expand': is_expanded}
395 assert len(added_iters), '%s has not been added to roster!' % \
396 contact.jid
397 return added_iters
399 def _remove_entity(self, contact, account, groups=None):
401 Remove the given contact from roster data model
403 Empty groups after contact removal are removed too.
404 Return False if contact still has children and deletion was
405 not performed.
406 Return True on success.
408 Keyword arguments:
409 contact -- the contact to add
410 account -- the contacts account
411 groups -- list of groups to remove the contact from.
413 iters = self._get_contact_iter(contact.jid, account, contact,
414 self.model)
415 assert iters, '%s shall be removed but is not in roster' % contact.jid
417 parent_iter = self.model.iter_parent(iters[0])
418 parent_type = self.model[parent_iter][C_TYPE]
420 if groups:
421 # Only remove from specified groups
422 all_iters = iters[:]
423 group_iters = [self._get_group_iter(group, account)
424 for group in groups]
425 iters = [titer for titer in all_iters
426 if self.model.iter_parent(titer) in group_iters]
428 iter_children = self.model.iter_children(iters[0])
430 if iter_children:
431 # We have children. We cannot be removed!
432 return False
433 # Remove us and empty groups from the model
434 for i in iters:
435 assert self.model[i][C_JID] == contact.jid and \
436 self.model[i][C_ACCOUNT] == account, \
437 "Invalidated iters of %s" % contact.jid
439 parent_i = self.model.iter_parent(i)
440 parent_type = self.model[parent_i][C_TYPE]
442 to_be_removed = i
443 while parent_type == 'group' and \
444 self.model.iter_n_children(parent_i) == 1:
445 if self.regroup:
446 account_group = 'MERGED'
447 else:
448 account_group = account
449 group = self.model[parent_i][C_JID].decode('utf-8')
450 if group in gajim.groups[account]:
451 del gajim.groups[account][group]
452 to_be_removed = parent_i
453 del self._iters[account_group]['groups'][group]
454 parent_i = self.model.iter_parent(parent_i)
455 parent_type = self.model[parent_i][C_TYPE]
456 self.model.remove(to_be_removed)
458 del self._iters[account]['contacts'][contact.jid]
459 return True
461 def _add_metacontact_family(self, family, account):
463 Add the give Metacontact family to roster data model
465 Add Big Brother to his groups and all others under him.
466 Return list of all added (contact, account) tuples with
467 Big Brother as first element.
469 Keyword arguments:
470 family -- the family, see Contacts.get_metacontacts_family()
473 nearby_family, big_brother_jid, big_brother_account = \
474 self._get_nearby_family_and_big_brother(family, account)
475 big_brother_contact = gajim.contacts.get_first_contact_from_jid(
476 big_brother_account, big_brother_jid)
478 assert len(self._get_contact_iter(big_brother_jid,
479 big_brother_account, big_brother_contact, self.model)) == 0, \
480 'Big brother %s already in roster\n Family: %s' \
481 % (big_brother_jid, family)
482 self._add_entity(big_brother_contact, big_brother_account)
484 brothers = []
485 # Filter family members
486 for data in nearby_family:
487 _account = data['account']
488 _jid = data['jid']
489 _contact = gajim.contacts.get_first_contact_from_jid(
490 _account, _jid)
492 if not _contact or _contact == big_brother_contact:
493 # Corresponding account is not connected
494 # or brother already added
495 continue
497 assert len(self._get_contact_iter(_jid, _account,
498 _contact, self.model)) == 0, \
499 "%s already in roster.\n Family: %s" % (_jid, nearby_family)
500 self._add_entity(_contact, _account,
501 big_brother_contact = big_brother_contact,
502 big_brother_account = big_brother_account)
503 brothers.append((_contact, _account))
505 brothers.insert(0, (big_brother_contact, big_brother_account))
506 return brothers
508 def _remove_metacontact_family(self, family, account):
510 Remove the given Metacontact family from roster data model
512 See Contacts.get_metacontacts_family() and
513 RosterWindow._remove_entity()
515 nearby_family = self._get_nearby_family_and_big_brother(
516 family, account)[0]
518 # Family might has changed (actual big brother not on top).
519 # Remove childs first then big brother
520 family_in_roster = False
521 for data in nearby_family:
522 _account = data['account']
523 _jid = data['jid']
524 _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
526 iters = self._get_contact_iter(_jid, _account, _contact, self.model)
527 if not iters or not _contact:
528 # Family might not be up to date.
529 # Only try to remove what is actually in the roster
530 continue
531 assert iters, '%s shall be removed but is not in roster \
532 \n Family: %s' % (_jid, family)
534 family_in_roster = True
536 parent_iter = self.model.iter_parent(iters[0])
537 parent_type = self.model[parent_iter][C_TYPE]
539 if parent_type != 'contact':
540 # The contact on top
541 old_big_account = _account
542 old_big_contact = _contact
543 old_big_jid = _jid
544 continue
546 ok = self._remove_entity(_contact, _account)
547 assert ok, '%s was not removed' % _jid
548 assert len(self._get_contact_iter(_jid, _account, _contact,
549 self.model)) == 0, '%s is removed but still in roster' % _jid
551 if not family_in_roster:
552 return False
554 assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \
555 (nearby_family, family)
556 iters = self._get_contact_iter(old_big_jid, old_big_account,
557 old_big_contact, self.model)
558 assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
559 old_big_jid
560 assert not self.model.iter_children(iters[0]), \
561 'Old Big Brother %s still has children' % old_big_jid
563 ok = self._remove_entity(old_big_contact, old_big_account)
564 assert ok, "Old Big Brother %s not removed" % old_big_jid
565 assert len(self._get_contact_iter(old_big_jid, old_big_account,
566 old_big_contact, self.model)) == 0, \
567 'Old Big Brother %s is removed but still in roster' % old_big_jid
569 return True
571 def _recalibrate_metacontact_family(self, family, account):
573 Regroup metacontact family if necessary
576 brothers = []
577 nearby_family, big_brother_jid, big_brother_account = \
578 self._get_nearby_family_and_big_brother(family, account)
579 big_brother_contact = gajim.contacts.get_contact(big_brother_account,
580 big_brother_jid)
581 child_iters = self._get_contact_iter(big_brother_jid,
582 big_brother_account, model=self.model)
583 if child_iters:
584 parent_iter = self.model.iter_parent(child_iters[0])
585 parent_type = self.model[parent_iter][C_TYPE]
587 # Check if the current BigBrother has even been before.
588 if parent_type == 'contact':
589 for data in nearby_family:
590 # recalibrate after remove to keep highlight
591 if data['jid'] in gajim.to_be_removed[data['account']]:
592 return
594 self._remove_metacontact_family(family, account)
595 brothers = self._add_metacontact_family(family, account)
597 for c, acc in brothers:
598 self.draw_completely(c.jid, acc)
600 # Check is small brothers are under the big brother
601 for child in nearby_family:
602 _jid = child['jid']
603 _account = child['account']
604 if _account == big_brother_account and _jid == big_brother_jid:
605 continue
606 child_iters = self._get_contact_iter(_jid, _account,
607 model=self.model)
608 if not child_iters:
609 continue
610 parent_iter = self.model.iter_parent(child_iters[0])
611 parent_type = self.model[parent_iter][C_TYPE]
612 if parent_type != 'contact':
613 _contact = gajim.contacts.get_contact(_account, _jid)
614 self._remove_entity(_contact, _account)
615 self._add_entity(_contact, _account, groups=None,
616 big_brother_contact=big_brother_contact,
617 big_brother_account=big_brother_account)
619 def _get_nearby_family_and_big_brother(self, family, account):
620 return gajim.contacts.get_nearby_family_and_big_brother(family, account)
622 def _add_self_contact(self, account):
624 Add account's SelfContact to roster and draw it and the account
626 Return the SelfContact contact instance
628 jid = gajim.get_jid_from_account(account)
629 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
631 assert len(self._get_contact_iter(jid, account, contact,
632 self.model)) == 0, 'Self contact %s already in roster' % jid
634 child_iterA = self._get_account_iter(account, self.model)
635 self._iters[account]['contacts'][jid] = [self.model.append(child_iterA,
636 [None, gajim.nicks[account], 'self_contact', jid, account, None,
637 None, None, None, None, None] + [None] * self.nb_ext_renderers)]
639 self.draw_completely(jid, account)
640 self.draw_account(account)
642 return contact
644 def redraw_metacontacts(self, account):
645 for family in gajim.contacts.iter_metacontacts_families(account):
646 self._recalibrate_metacontact_family(family, account)
648 def add_contact(self, jid, account):
650 Add contact to roster and draw him
652 Add contact to all its group and redraw the groups, the contact and the
653 account. If it's a Metacontact, add and draw the whole family.
654 Do nothing if the contact is already in roster.
656 Return the added contact instance. If it is a Metacontact return
657 Big Brother.
659 Keyword arguments:
660 jid -- the contact's jid or SelfJid to add SelfContact
661 account -- the corresponding account.
663 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
664 if len(self._get_contact_iter(jid, account, contact, self.model)):
665 # If contact already in roster, do nothing
666 return
668 if jid == gajim.get_jid_from_account(account):
669 show_self_contact = gajim.config.get('show_self_contact')
670 if show_self_contact == 'never':
671 return
672 if (contact.resource != gajim.connections[account].server_resource \
673 and show_self_contact == 'when_other_resource') or \
674 show_self_contact == 'always':
675 return self._add_self_contact(account)
676 return
678 is_observer = contact.is_observer()
679 if is_observer:
680 # if he has a tag, remove it
681 gajim.contacts.remove_metacontact(account, jid)
683 # Add contact to roster
684 family = gajim.contacts.get_metacontacts_family(account, jid)
685 contacts = []
686 if family:
687 # We have a family. So we are a metacontact.
688 # Add all family members that we shall be grouped with
689 if self.regroup:
690 # remove existing family members to regroup them
691 self._remove_metacontact_family(family, account)
692 contacts = self._add_metacontact_family(family, account)
693 else:
694 # We are a normal contact
695 contacts = [(contact, account), ]
696 self._add_entity(contact, account)
698 # Draw the contact and its groups contact
699 if not self.starting:
700 for c, acc in contacts:
701 self.draw_completely(c.jid, acc)
702 for group in contact.get_shown_groups():
703 self.draw_group(group, account)
704 self._adjust_group_expand_collapse_state(group, account)
705 self.draw_account(account)
707 return contacts[0][0] # it's contact/big brother with highest priority
709 def remove_contact(self, jid, account, force=False, backend=False):
711 Remove contact from roster
713 Remove contact from all its group. Remove empty groups or redraw
714 otherwise.
715 Draw the account.
716 If it's a Metacontact, remove the whole family.
717 Do nothing if the contact is not in roster.
719 Keyword arguments:
720 jid -- the contact's jid or SelfJid to remove SelfContact
721 account -- the corresponding account.
722 force -- remove contact even it has pending evens (Default False)
723 backend -- also remove contact instance (Default False)
725 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
726 if not contact:
727 return
729 if not force and (self.contact_has_pending_roster_events(contact,
730 account) or gajim.interface.msg_win_mgr.get_control(jid, account)):
731 # Contact has pending events or window
732 #TODO: or single message windows? Bur they are not listed for the
733 # moment
734 key = (jid, account)
735 if not key in self.contacts_to_be_removed:
736 self.contacts_to_be_removed[key] = {'backend': backend}
737 # if more pending event, don't remove from roster
738 if self.contact_has_pending_roster_events(contact, account):
739 return False
741 iters = self._get_contact_iter(jid, account, contact, self.model)
742 if iters:
743 # no more pending events
744 # Remove contact from roster directly
745 family = gajim.contacts.get_metacontacts_family(account, jid)
746 if family:
747 # We have a family. So we are a metacontact.
748 self._remove_metacontact_family(family, account)
749 else:
750 self._remove_entity(contact, account)
752 if backend and (not gajim.interface.msg_win_mgr.get_control(jid,
753 account) or force):
754 # If a window is still opened: don't remove contact instance
755 # Remove contact before redrawing, otherwise the old
756 # numbers will still be show
757 gajim.contacts.remove_jid(account, jid, remove_meta=True)
758 if iters:
759 rest_of_family = [data for data in family
760 if account != data['account'] or jid != data['jid']]
761 if rest_of_family:
762 # reshow the rest of the family
763 brothers = self._add_metacontact_family(rest_of_family,
764 account)
765 for c, acc in brothers:
766 self.draw_completely(c.jid, acc)
768 if iters:
769 # Draw all groups of the contact
770 for group in contact.get_shown_groups():
771 self.draw_group(group, account)
772 self.draw_account(account)
774 return True
776 def rename_self_contact(self, old_jid, new_jid, account):
778 Rename the self_contact jid
780 Keyword arguments:
781 old_jid -- our old jid
782 new_jid -- our new jid
783 account -- the corresponding account.
785 gajim.contacts.change_contact_jid(old_jid, new_jid, account)
786 self_iter = self._get_self_contact_iter(account, model=self.model)
787 if not self_iter:
788 return
789 self.model[self_iter][C_JID] = new_jid
790 self.draw_contact(new_jid, account)
792 def add_groupchat(self, jid, account, status=''):
794 Add groupchat to roster and draw it. Return the added contact instance
796 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
797 # Do not show gc if we are disconnected and minimize it
798 if gajim.account_is_connected(account):
799 show = 'online'
800 else:
801 show = 'offline'
802 status = ''
804 if contact is None:
805 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
806 account)
807 if gc_control:
808 # there is a window that we can minimize
809 gajim.interface.minimized_controls[account][jid] = gc_control
810 name = gc_control.name
811 elif jid in gajim.interface.minimized_controls[account]:
812 name = gajim.interface.minimized_controls[account][jid].name
813 else:
814 name = jid.split('@')[0]
815 # New groupchat
816 contact = gajim.contacts.create_contact(jid=jid, account=account,
817 name=name, groups=[_('Groupchats')], show=show, status=status,
818 sub='none')
819 gajim.contacts.add_contact(account, contact)
820 self.add_contact(jid, account)
821 else:
822 if jid not in gajim.interface.minimized_controls[account]:
823 # there is a window that we can minimize
824 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
825 account)
826 gajim.interface.minimized_controls[account][jid] = gc_control
827 contact.show = show
828 contact.status = status
829 self.adjust_and_draw_contact_context(jid, account)
831 return contact
834 def remove_groupchat(self, jid, account):
836 Remove groupchat from roster and redraw account and group
838 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
839 if contact.is_groupchat():
840 if jid in gajim.interface.minimized_controls[account]:
841 del gajim.interface.minimized_controls[account][jid]
842 self.remove_contact(jid, account, force=True, backend=True)
843 return True
844 else:
845 return False
848 # FIXME: This function is yet unused! Port to new API
849 def add_transport(self, jid, account):
851 Add transport to roster and draw it. Return the added contact instance
853 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
854 if contact is None:
855 contact = gajim.contacts.create_contact(jid=jid, account=account,
856 name=jid, groups=[_('Transports')], show='offline',
857 status='offline', sub='from')
858 gajim.contacts.add_contact(account, contact)
859 self.add_contact(jid, account)
860 return contact
862 def remove_transport(self, jid, account):
864 Remove transport from roster and redraw account and group
866 self.remove_contact(jid, account, force=True, backend=True)
867 return True
869 def rename_group(self, old_name, new_name, account):
871 Rename a roster group
873 if old_name == new_name:
874 return
876 # Groups may not change name from or to a special groups
877 for g in helpers.special_groups:
878 if g in (new_name, old_name):
879 return
881 # update all contacts in the given group
882 if self.regroup:
883 accounts = gajim.connections.keys()
884 else:
885 accounts = [account, ]
887 for acc in accounts:
888 changed_contacts = []
889 for jid in gajim.contacts.get_jid_list(acc):
890 contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
891 if old_name not in contact.groups:
892 continue
894 self.remove_contact(jid, acc, force=True)
896 contact.groups.remove(old_name)
897 if new_name not in contact.groups:
898 contact.groups.append(new_name)
900 changed_contacts.append({'jid': jid, 'name': contact.name,
901 'groups':contact.groups})
903 gajim.connections[acc].update_contacts(changed_contacts)
905 for c in changed_contacts:
906 self.add_contact(c['jid'], acc)
908 self._adjust_group_expand_collapse_state(new_name, acc)
910 self.draw_group(old_name, acc)
911 self.draw_group(new_name, acc)
914 def add_contact_to_groups(self, jid, account, groups, update=True):
916 Add contact to given groups and redraw them
918 Contact on server is updated too. When the contact has a family,
919 the action will be performed for all members.
921 Keyword Arguments:
922 jid -- the jid
923 account -- the corresponding account
924 groups -- list of Groups to add the contact to.
925 update -- update contact on the server
927 self.remove_contact(jid, account, force=True)
928 for contact in gajim.contacts.get_contacts(account, jid):
929 for group in groups:
930 if group not in contact.groups:
931 # we might be dropped from meta to group
932 contact.groups.append(group)
933 if update:
934 gajim.connections[account].update_contact(jid, contact.name,
935 contact.groups)
937 self.add_contact(jid, account)
939 for group in groups:
940 self._adjust_group_expand_collapse_state(group, account)
942 def remove_contact_from_groups(self, jid, account, groups, update=True):
944 Remove contact from given groups and redraw them
946 Contact on server is updated too. When the contact has a family,
947 the action will be performed for all members.
949 Keyword Arguments:
950 jid -- the jid
951 account -- the corresponding account
952 groups -- list of Groups to remove the contact from
953 update -- update contact on the server
955 self.remove_contact(jid, account, force=True)
956 for contact in gajim.contacts.get_contacts(account, jid):
957 for group in groups:
958 if group in contact.groups:
959 # Needed when we remove from "General" or "Observers"
960 contact.groups.remove(group)
961 if update:
962 gajim.connections[account].update_contact(jid, contact.name,
963 contact.groups)
964 self.add_contact(jid, account)
966 # Also redraw old groups
967 for group in groups:
968 self.draw_group(group, account)
970 # FIXME: maybe move to gajim.py
971 def remove_newly_added(self, jid, account):
972 if jid in gajim.newly_added[account]:
973 gajim.newly_added[account].remove(jid)
974 self.draw_contact(jid, account)
976 # FIXME: maybe move to gajim.py
977 def remove_to_be_removed(self, jid, account):
978 if account not in gajim.interface.instances:
979 # Account has been deleted during the timeout that called us
980 return
981 if jid in gajim.newly_added[account]:
982 return
983 if jid in gajim.to_be_removed[account]:
984 gajim.to_be_removed[account].remove(jid)
985 family = gajim.contacts.get_metacontacts_family(account, jid)
986 if family:
987 # Peform delayed recalibration
988 self._recalibrate_metacontact_family(family, account)
989 self.draw_contact(jid, account)
991 # FIXME: integrate into add_contact()
992 def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
993 keyID = ''
994 attached_keys = gajim.config.get_per('accounts', account,
995 'attached_gpg_keys').split()
996 if jid in attached_keys:
997 keyID = attached_keys[attached_keys.index(jid) + 1]
998 contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
999 account=account, resource=resource, name=nick, keyID=keyID)
1000 gajim.contacts.add_contact(account, contact)
1001 self.add_contact(contact.jid, account)
1002 return contact
1005 ################################################################################
1006 ### Methods for adding and removing roster window items
1007 ################################################################################
1009 def draw_account(self, account):
1010 child_iter = self._get_account_iter(account, self.model)
1011 if not child_iter:
1012 assert False, 'Account iter of %s could not be found.' % account
1013 return
1015 num_of_accounts = gajim.get_number_of_connected_accounts()
1016 num_of_secured = gajim.get_number_of_securely_connected_accounts()
1018 if gajim.account_is_securely_connected(account) and not self.regroup or\
1019 self.regroup and num_of_secured and num_of_secured == num_of_accounts:
1020 # the only way to create a pixbuf from stock
1021 tls_pixbuf = self.window.render_icon(
1022 gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
1023 self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf
1024 else:
1025 self.model[child_iter][C_PADLOCK_PIXBUF] = None
1027 if self.regroup:
1028 account_name = _('Merged accounts')
1029 accounts = []
1030 else:
1031 account_name = account
1032 accounts = [account]
1034 if account in self.collapsed_rows and \
1035 self.model.iter_has_child(child_iter):
1036 account_name = '[%s]' % account_name
1038 if (gajim.account_is_connected(account) or (self.regroup and \
1039 gajim.get_number_of_connected_accounts())) and gajim.config.get(
1040 'show_contacts_number'):
1041 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
1042 accounts = accounts)
1043 account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
1045 self.model[child_iter][C_NAME] = account_name
1047 pep_dict = gajim.connections[account].pep
1048 if gajim.config.get('show_mood_in_roster') and 'mood' in pep_dict:
1049 self.model[child_iter][C_MOOD_PIXBUF] = pep_dict['mood'].\
1050 asPixbufIcon()
1051 else:
1052 self.model[child_iter][C_MOOD_PIXBUF] = None
1054 if gajim.config.get('show_activity_in_roster') and 'activity' in \
1055 pep_dict:
1056 self.model[child_iter][C_ACTIVITY_PIXBUF] = pep_dict['activity'].\
1057 asPixbufIcon()
1058 else:
1059 self.model[child_iter][C_ACTIVITY_PIXBUF] = None
1061 if gajim.config.get('show_tunes_in_roster') and 'tune' in pep_dict:
1062 self.model[child_iter][C_TUNE_PIXBUF] = pep_dict['tune'].\
1063 asPixbufIcon()
1064 else:
1065 self.model[child_iter][C_TUNE_PIXBUF] = None
1067 if gajim.config.get('show_location_in_roster') and 'location' in \
1068 pep_dict:
1069 self.model[child_iter][C_LOCATION_PIXBUF] = pep_dict['location'].\
1070 asPixbufIcon()
1071 else:
1072 self.model[child_iter][C_LOCATION_PIXBUF] = None
1073 return False
1075 def draw_group(self, group, account):
1076 child_iter = self._get_group_iter(group, account, model=self.model)
1077 if not child_iter:
1078 # Eg. We redraw groups after we removed a entitiy
1079 # and its empty groups
1080 return
1081 if self.regroup:
1082 accounts = []
1083 else:
1084 accounts = [account]
1085 text = gobject.markup_escape_text(group)
1086 if helpers.group_is_blocked(account, group):
1087 text = '<span strikethrough="true">%s</span>' % text
1088 if gajim.config.get('show_contacts_number'):
1089 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
1090 accounts = accounts, groups = [group])
1091 text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
1093 self.model[child_iter][C_NAME] = text
1094 return False
1096 def draw_parent_contact(self, jid, account):
1097 child_iters = self._get_contact_iter(jid, account, model=self.model)
1098 if not child_iters:
1099 return False
1100 parent_iter = self.model.iter_parent(child_iters[0])
1101 if self.model[parent_iter][C_TYPE] != 'contact':
1102 # parent is not a contact
1103 return
1104 parent_jid = self.model[parent_iter][C_JID].decode('utf-8')
1105 parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8')
1106 self.draw_contact(parent_jid, parent_account)
1107 return False
1109 def draw_contact(self, jid, account, selected=False, focus=False):
1111 Draw the correct state image, name BUT not avatar
1113 # focus is about if the roster window has toplevel-focus or not
1114 # FIXME: We really need a custom cell_renderer
1116 contact_instances = gajim.contacts.get_contacts(account, jid)
1117 contact = gajim.contacts.get_highest_prio_contact_from_contacts(
1118 contact_instances)
1119 if not contact:
1120 return False
1122 child_iters = self._get_contact_iter(jid, account, contact, self.model)
1123 if not child_iters:
1124 return False
1126 name = gobject.markup_escape_text(contact.get_shown_name())
1128 # gets number of unread gc marked messages
1129 if jid in gajim.interface.minimized_controls[account] and \
1130 gajim.interface.minimized_controls[account][jid]:
1131 nb_unread = len(gajim.events.get_events(account, jid,
1132 ['printed_marked_gc_msg']))
1133 nb_unread += gajim.interface.minimized_controls \
1134 [account][jid].get_nb_unread_pm()
1136 if nb_unread == 1:
1137 name = '%s *' % name
1138 elif nb_unread > 1:
1139 name = '%s [%s]' % (name, str(nb_unread))
1141 # Strike name if blocked
1142 strike = False
1143 if helpers.jid_is_blocked(account, jid):
1144 strike = True
1145 else:
1146 for group in contact.get_shown_groups():
1147 if helpers.group_is_blocked(account, group):
1148 strike = True
1149 break
1150 if strike:
1151 name = '<span strikethrough="true">%s</span>' % name
1153 # Show resource counter
1154 nb_connected_contact = 0
1155 for c in contact_instances:
1156 if c.show not in ('error', 'offline'):
1157 nb_connected_contact += 1
1158 if nb_connected_contact > 1:
1159 # switch back to default writing direction
1160 name += i18n.paragraph_direction_mark(unicode(name))
1161 name += u' (%d)' % nb_connected_contact
1163 # show (account_name) if there are 2 contact with same jid
1164 # in merged mode
1165 if self.regroup:
1166 add_acct = False
1167 # look through all contacts of all accounts
1168 for account_ in gajim.connections:
1169 # useless to add account name
1170 if account_ == account:
1171 continue
1172 for jid_ in gajim.contacts.get_jid_list(account_):
1173 contact_ = gajim.contacts.get_first_contact_from_jid(
1174 account_, jid_)
1175 if contact_.get_shown_name() == contact.get_shown_name() \
1176 and (jid_, account_) != (jid, account):
1177 add_acct = True
1178 break
1179 if add_acct:
1180 # No need to continue in other account
1181 # if we already found one
1182 break
1183 if add_acct:
1184 name += ' (' + account + ')'
1186 # add status msg, if not empty, under contact name in
1187 # the treeview
1188 if contact.status and gajim.config.get('show_status_msgs_in_roster'):
1189 status = contact.status.strip()
1190 if status != '':
1191 status = helpers.reduce_chars_newlines(status,
1192 max_lines = 1)
1193 # escape markup entities and make them small
1194 # italic and fg color color is calcuted to be
1195 # always readable
1196 color = gtkgui_helpers.get_fade_color(self.tree, selected,
1197 focus)
1198 colorstring = '#%04x%04x%04x' % (color.red, color.green,
1199 color.blue)
1200 name += '\n<span size="small" style="italic" ' \
1201 'foreground="%s">%s</span>' % (colorstring,
1202 gobject.markup_escape_text(status))
1204 icon_name = helpers.get_icon_name_to_show(contact, account)
1205 # look if another resource has awaiting events
1206 for c in contact_instances:
1207 c_icon_name = helpers.get_icon_name_to_show(c, account)
1208 if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
1209 icon_name = c_icon_name
1210 break
1212 # Check for events of collapsed (hidden) brothers
1213 family = gajim.contacts.get_metacontacts_family(account, jid)
1214 is_big_brother = False
1215 have_visible_children = False
1216 if family:
1217 bb_jid, bb_account = \
1218 self._get_nearby_family_and_big_brother(family, account)[1:]
1219 is_big_brother = (jid, account) == (bb_jid, bb_account)
1220 iters = self._get_contact_iter(jid, account)
1221 have_visible_children = iters and \
1222 self.modelfilter.iter_has_child(iters[0])
1224 if have_visible_children:
1225 # We are the big brother and have a visible family
1226 for child_iter in child_iters:
1227 child_path = self.model.get_path(child_iter)
1228 path = self.modelfilter.convert_child_path_to_path(child_path)
1230 if not path:
1231 continue
1233 if not self.tree.row_expanded(path) and icon_name != 'event':
1234 iterC = self.model.iter_children(child_iter)
1235 while iterC:
1236 # a child has awaiting messages?
1237 jidC = self.model[iterC][C_JID].decode('utf-8')
1238 accountC = self.model[iterC][C_ACCOUNT].decode('utf-8')
1239 if len(gajim.events.get_events(accountC, jidC)):
1240 icon_name = 'event'
1241 break
1242 iterC = self.model.iter_next(iterC)
1244 if self.tree.row_expanded(path):
1245 state_images = self.get_appropriate_state_images(
1246 jid, size = 'opened',
1247 icon_name = icon_name)
1248 else:
1249 state_images = self.get_appropriate_state_images(
1250 jid, size = 'closed',
1251 icon_name = icon_name)
1253 # Expand/collapse icon might differ per iter
1254 # (group)
1255 img = state_images[icon_name]
1256 self.model[child_iter][C_IMG] = img
1257 self.model[child_iter][C_NAME] = name
1258 else:
1259 # A normal contact or little brother
1260 state_images = self.get_appropriate_state_images(jid,
1261 icon_name = icon_name)
1263 # All iters have the same icon (no expand/collapse)
1264 img = state_images[icon_name]
1265 for child_iter in child_iters:
1266 self.model[child_iter][C_IMG] = img
1267 self.model[child_iter][C_NAME] = name
1269 # We are a little brother
1270 if family and not is_big_brother and not self.starting:
1271 self.draw_parent_contact(jid, account)
1273 delimiter = gajim.connections[account].nested_group_delimiter
1274 for group in contact.get_shown_groups():
1275 # We need to make sure that _visible_func is called for
1276 # our groups otherwise we might not be shown
1277 group_splited = group.split(delimiter)
1278 i = 1
1279 while i < len(group_splited) + 1:
1280 g = delimiter.join(group_splited[:i])
1281 iterG = self._get_group_iter(g, account, model=self.model)
1282 if iterG:
1283 # it's not self contact
1284 self.model[iterG][C_JID] = self.model[iterG][C_JID]
1285 i += 1
1287 gajim.plugin_manager.gui_extension_point('roster_draw_contact', self,
1288 jid, account, contact)
1290 return False
1292 def _is_pep_shown_in_roster(self, pep_type):
1293 if pep_type == 'mood':
1294 return gajim.config.get('show_mood_in_roster')
1295 elif pep_type == 'activity':
1296 return gajim.config.get('show_activity_in_roster')
1297 elif pep_type == 'tune':
1298 return gajim.config.get('show_tunes_in_roster')
1299 elif pep_type == 'location':
1300 return gajim.config.get('show_location_in_roster')
1301 else:
1302 return False
1304 def draw_all_pep_types(self, jid, account):
1305 for pep_type in self._pep_type_to_model_column:
1306 self.draw_pep(jid, account, pep_type)
1308 def draw_pep(self, jid, account, pep_type):
1309 if pep_type not in self._pep_type_to_model_column:
1310 return
1311 if not self._is_pep_shown_in_roster(pep_type):
1312 return
1314 model_column = self._pep_type_to_model_column[pep_type]
1315 iters = self._get_contact_iter(jid, account, model=self.model)
1316 if not iters:
1317 return
1318 contact = gajim.contacts.get_contact(account, jid)
1319 if pep_type in contact.pep:
1320 pixbuf = contact.pep[pep_type].asPixbufIcon()
1321 else:
1322 pixbuf = None
1323 for child_iter in iters:
1324 self.model[child_iter][model_column] = pixbuf
1326 def draw_avatar(self, jid, account):
1327 iters = self._get_contact_iter(jid, account, model=self.model)
1328 if not iters or not gajim.config.get('show_avatars_in_roster'):
1329 return
1330 jid = self.model[iters[0]][C_JID].decode('utf-8')
1331 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
1332 if pixbuf in (None, 'ask'):
1333 scaled_pixbuf = None
1334 else:
1335 scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
1336 for child_iter in iters:
1337 self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf
1338 return False
1340 def draw_completely(self, jid, account):
1341 self.draw_contact(jid, account)
1342 self.draw_all_pep_types(jid, account)
1343 self.draw_avatar(jid, account)
1345 def adjust_and_draw_contact_context(self, jid, account):
1347 Draw contact, account and groups of given jid Show contact if it has
1348 pending events
1350 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
1351 if not contact:
1352 # idle draw or just removed SelfContact
1353 return
1355 family = gajim.contacts.get_metacontacts_family(account, jid)
1356 if family:
1357 # There might be a new big brother
1358 self._recalibrate_metacontact_family(family, account)
1359 self.draw_contact(jid, account)
1360 self.draw_account(account)
1362 for group in contact.get_shown_groups():
1363 self.draw_group(group, account)
1364 self._adjust_group_expand_collapse_state(group, account)
1366 def _idle_draw_jids_of_account(self, jids, account):
1368 Draw given contacts and their avatars in a lazy fashion
1370 Keyword arguments:
1371 jids -- a list of jids to draw
1372 account -- the corresponding account
1374 def _draw_all_contacts(jids, account):
1375 for jid in jids:
1376 family = gajim.contacts.get_metacontacts_family(account, jid)
1377 if family:
1378 # For metacontacts over several accounts:
1379 # When we connect a new account existing brothers
1380 # must be redrawn (got removed and readded)
1381 for data in family:
1382 self.draw_completely(data['jid'], data['account'])
1383 else:
1384 self.draw_completely(jid, account)
1385 yield True
1386 self.refilter_shown_roster_items()
1387 yield False
1389 task = _draw_all_contacts(jids, account)
1390 gobject.idle_add(task.next)
1392 def _before_fill(self):
1393 self.tree.freeze_child_notify()
1394 self.tree.set_model(None)
1395 # disable sorting
1396 self.model.set_sort_column_id(-2, gtk.SORT_ASCENDING)
1398 def _after_fill(self):
1399 self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
1400 self.tree.set_model(self.modelfilter)
1401 self.tree.thaw_child_notify()
1403 def setup_and_draw_roster(self):
1405 Create new empty model and draw roster
1407 self.modelfilter = None
1408 self.model = gtk.TreeStore(*self.columns)
1410 self.model.set_sort_func(1, self._compareIters)
1411 self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
1412 self.modelfilter = self.model.filter_new()
1413 self.modelfilter.set_visible_func(self._visible_func)
1414 self.modelfilter.connect('row-has-child-toggled',
1415 self.on_modelfilter_row_has_child_toggled)
1416 self.tree.set_model(self.modelfilter)
1418 self._iters = {}
1419 # for merged mode
1420 self._iters['MERGED'] = {'account': None, 'groups': {}}
1422 for acct in gajim.contacts.get_accounts():
1423 self._iters[acct] = {'account': None, 'groups': {}, 'contacts': {}}
1424 self.add_account(acct)
1425 self.add_account_contacts(acct)
1427 # Recalculate column width for ellipsizing
1428 self.tree.columns_autosize()
1431 def select_contact(self, jid, account):
1433 Select contact in roster. If contact is hidden but has events, show him
1435 # Refiltering SHOULD NOT be needed:
1436 # When a contact gets a new event he will be redrawn and his
1437 # icon changes, so _visible_func WILL be called on him anyway
1438 iters = self._get_contact_iter(jid, account)
1439 if not iters:
1440 # Not visible in roster
1441 return
1442 path = self.modelfilter.get_path(iters[0])
1443 if self.dragging or not gajim.config.get(
1444 'scroll_roster_to_last_message'):
1445 # do not change selection while DND'ing
1446 return
1447 # Expand his parent, so this path is visible, don't expand it.
1448 self.tree.expand_to_path(path[:-1])
1449 self.tree.scroll_to_cell(path)
1450 self.tree.set_cursor(path)
1453 def _adjust_account_expand_collapse_state(self, account):
1455 Expand/collapse account row based on self.collapsed_rows
1457 if not self.tree.get_model():
1458 return
1459 iterA = self._get_account_iter(account)
1460 if not iterA:
1461 # thank you modelfilter
1462 return
1463 path = self.modelfilter.get_path(iterA)
1464 if account in self.collapsed_rows:
1465 self.tree.collapse_row(path)
1466 else:
1467 self.tree.expand_row(path, False)
1468 return False
1471 def _adjust_group_expand_collapse_state(self, group, account):
1473 Expand/collapse group row based on self.collapsed_rows
1475 if not self.tree.get_model():
1476 return
1477 delimiter = gajim.connections[account].nested_group_delimiter
1478 group_splited = group.split(delimiter)
1479 i = 1
1480 while i < len(group_splited) + 1:
1481 g = delimiter.join(group_splited[:i])
1482 iterG = self._get_group_iter(g, account)
1483 if not iterG:
1484 # Group not visible
1485 return
1486 path = self.modelfilter.get_path(iterG)
1487 if account + g in self.collapsed_rows:
1488 self.tree.collapse_row(path)
1489 else:
1490 self.tree.expand_row(path, False)
1491 i += 1
1493 ##############################################################################
1494 ### Roster and Modelfilter handling
1495 ##############################################################################
1497 def _search_roster_func(self, model, column, key, titer):
1498 key = key.decode('utf-8').lower()
1499 name = model[titer][C_NAME].decode('utf-8').lower()
1500 return not (key in name)
1502 def refilter_shown_roster_items(self):
1503 self.filtering = True
1504 self.modelfilter.refilter()
1505 self.filtering = False
1507 def contact_has_pending_roster_events(self, contact, account):
1509 Return True if the contact or one if it resources has pending events
1511 # jid has pending events
1512 if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
1513 return True
1514 # check events of all resources
1515 for contact_ in gajim.contacts.get_contacts(account, contact.jid):
1516 if contact_.resource and gajim.events.get_nb_roster_events(account,
1517 contact_.get_full_jid()) > 0:
1518 return True
1519 return False
1521 def contact_is_visible(self, contact, account):
1522 if self.contact_has_pending_roster_events(contact, account):
1523 return True
1525 if contact.show in ('offline', 'error'):
1526 if contact.jid in gajim.to_be_removed[account]:
1527 return True
1528 return False
1529 if gajim.config.get('show_only_chat_and_online') and contact.show in (
1530 'away', 'xa', 'busy'):
1531 return False
1532 return True
1534 def _visible_func(self, model, titer):
1536 Determine whether iter should be visible in the treeview
1538 type_ = model[titer][C_TYPE]
1539 if not type_:
1540 return False
1541 if type_ == 'account':
1542 # Always show account
1543 return True
1545 account = model[titer][C_ACCOUNT]
1546 if not account:
1547 return False
1549 account = account.decode('utf-8')
1550 jid = model[titer][C_JID]
1551 if not jid:
1552 return False
1553 jid = jid.decode('utf-8')
1554 if type_ == 'group':
1555 group = jid
1556 if group == _('Transports'):
1557 if self.regroup:
1558 accounts = gajim.contacts.get_accounts()
1559 else:
1560 accounts = [account]
1561 for _acc in accounts:
1562 for contact in gajim.contacts.iter_contacts(_acc):
1563 if group in contact.get_shown_groups() and \
1564 self.contact_has_pending_roster_events(contact, _acc):
1565 return True
1566 return gajim.config.get('show_transports_group') and \
1567 (gajim.account_is_connected(account) or \
1568 gajim.config.get('showoffline'))
1569 if gajim.config.get('showoffline'):
1570 return True
1572 if self.regroup:
1573 # C_ACCOUNT for groups depends on the order
1574 # accounts were connected
1575 # Check all accounts for online group contacts
1576 accounts = gajim.contacts.get_accounts()
1577 else:
1578 accounts = [account]
1579 for _acc in accounts:
1580 delimiter = gajim.connections[_acc].nested_group_delimiter
1581 for contact in gajim.contacts.iter_contacts(_acc):
1582 if not self.contact_is_visible(contact, _acc):
1583 continue
1584 # Is this contact in this group?
1585 for grp in contact.get_shown_groups():
1586 while grp:
1587 if group == grp:
1588 return True
1589 grp = delimiter.join(grp.split(delimiter)[:-1])
1590 return False
1591 if type_ == 'contact':
1592 if gajim.config.get('showoffline'):
1593 return True
1594 bb_jid = None
1595 bb_account = None
1596 family = gajim.contacts.get_metacontacts_family(account, jid)
1597 if family:
1598 nearby_family, bb_jid, bb_account = \
1599 self._get_nearby_family_and_big_brother(family, account)
1600 if (bb_jid, bb_account) == (jid, account):
1601 # Show the big brother if a child has pending events
1602 for data in nearby_family:
1603 jid = data['jid']
1604 account = data['account']
1605 contact = gajim.contacts.get_contact_with_highest_priority(
1606 account, jid)
1607 if contact and self.contact_is_visible(contact, account):
1608 return True
1609 return False
1610 else:
1611 contact = gajim.contacts.get_contact_with_highest_priority(
1612 account, jid)
1613 return self.contact_is_visible(contact, account)
1614 if type_ == 'agent':
1615 contact = gajim.contacts.get_contact_with_highest_priority(account,
1616 jid)
1617 return self.contact_has_pending_roster_events(contact, account) or \
1618 (gajim.config.get('show_transports_group') and \
1619 (gajim.account_is_connected(account) or \
1620 gajim.config.get('showoffline')))
1621 return True
1623 def _compareIters(self, model, iter1, iter2, data=None):
1625 Compare two iters to sort them
1627 name1 = model[iter1][C_NAME]
1628 name2 = model[iter2][C_NAME]
1629 if not name1 or not name2:
1630 return 0
1631 name1 = name1.decode('utf-8')
1632 name2 = name2.decode('utf-8')
1633 type1 = model[iter1][C_TYPE]
1634 type2 = model[iter2][C_TYPE]
1635 if type1 == 'self_contact':
1636 return -1
1637 if type2 == 'self_contact':
1638 return 1
1639 if type1 == 'group':
1640 name1 = model[iter1][C_JID]
1641 name2 = model[iter2][C_JID]
1642 if name1 == _('Transports'):
1643 return 1
1644 if name2 == _('Transports'):
1645 return -1
1646 if name1 == _('Not in Roster'):
1647 return 1
1648 if name2 == _('Not in Roster'):
1649 return -1
1650 if name1 == _('Groupchats'):
1651 return 1
1652 if name2 == _('Groupchats'):
1653 return -1
1654 account1 = model[iter1][C_ACCOUNT]
1655 account2 = model[iter2][C_ACCOUNT]
1656 if not account1 or not account2:
1657 return 0
1658 account1 = account1.decode('utf-8')
1659 account2 = account2.decode('utf-8')
1660 if type1 == 'account':
1661 return locale.strcoll(account1, account2)
1662 jid1 = model[iter1][C_JID].decode('utf-8')
1663 jid2 = model[iter2][C_JID].decode('utf-8')
1664 if type1 == 'contact':
1665 lcontact1 = gajim.contacts.get_contacts(account1, jid1)
1666 contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
1667 if not contact1:
1668 return 0
1669 name1 = contact1.get_shown_name()
1670 if type2 == 'contact':
1671 lcontact2 = gajim.contacts.get_contacts(account2, jid2)
1672 contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
1673 if not contact2:
1674 return 0
1675 name2 = contact2.get_shown_name()
1676 # We first compare by show if sort_by_show_in_roster is True or if it's
1677 # a child contact
1678 if type1 == 'contact' and type2 == 'contact' and \
1679 gajim.config.get('sort_by_show_in_roster'):
1680 cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
1681 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
1682 s = self.get_show(lcontact1)
1683 show1 = cshow.get(s, 9)
1684 s = self.get_show(lcontact2)
1685 show2 = cshow.get(s, 9)
1686 removing1 = False
1687 removing2 = False
1688 if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
1689 removing1 = True
1690 if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
1691 removing2 = True
1692 if removing1 and not removing2:
1693 return 1
1694 if removing2 and not removing1:
1695 return -1
1696 sub1 = contact1.sub
1697 sub2 = contact2.sub
1698 # none and from goes after
1699 if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
1700 return -1
1701 if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
1702 return 1
1703 if show1 < show2:
1704 return -1
1705 elif show1 > show2:
1706 return 1
1707 # We compare names
1708 cmp_result = locale.strcoll(name1.lower(), name2.lower())
1709 if cmp_result < 0:
1710 return -1
1711 if cmp_result > 0:
1712 return 1
1713 if type1 == 'contact' and type2 == 'contact':
1714 # We compare account names
1715 cmp_result = locale.strcoll(account1.lower(), account2.lower())
1716 if cmp_result < 0:
1717 return -1
1718 if cmp_result > 0:
1719 return 1
1720 # We compare jids
1721 cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
1722 if cmp_result < 0:
1723 return -1
1724 if cmp_result > 0:
1725 return 1
1726 return 0
1728 ################################################################################
1729 ### FIXME: Methods that don't belong to roster window...
1730 ### ... atleast not in there current form
1731 ################################################################################
1733 def fire_up_unread_messages_events(self, account):
1735 Read from db the unread messages, and fire them up, and if we find very
1736 old unread messages, delete them from unread table
1738 results = gajim.logger.get_unread_msgs()
1739 for result in results:
1740 jid = result[4]
1741 shown = result[5]
1742 if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
1743 shown:
1744 # We have this jid in our contacts list
1745 # XXX unread messages should probably have their session saved
1746 # with them
1747 session = gajim.connections[account].make_new_session(jid)
1749 tim = time.localtime(float(result[2]))
1750 session.roster_message(jid, result[1], tim, msg_type='chat',
1751 msg_id=result[0])
1752 gajim.logger.set_shown_unread_msgs(result[0])
1754 elif (time.time() - result[2]) > 2592000:
1755 # ok, here we see that we have a message in unread messages
1756 # table that is older than a month. It is probably from someone
1757 # not in our roster for accounts we usually launch, so we will
1758 # delete this id from unread message tables.
1759 gajim.logger.set_read_messages([result[0]])
1761 def fill_contacts_and_groups_dicts(self, array, account):
1763 Fill gajim.contacts and gajim.groups
1765 # FIXME: This function needs to be splitted
1766 # Most of the logic SHOULD NOT be done at GUI level
1767 if account not in gajim.contacts.get_accounts():
1768 gajim.contacts.add_account(account)
1769 if not account in self._iters:
1770 self._iters[account] = {'account': None, 'groups': {},
1771 'contacts': {}}
1772 if account not in gajim.groups:
1773 gajim.groups[account] = {}
1774 if gajim.config.get('show_self_contact') == 'always':
1775 self_jid = gajim.get_jid_from_account(account)
1776 if gajim.connections[account].server_resource:
1777 self_jid += '/' + gajim.connections[account].server_resource
1778 array[self_jid] = {'name': gajim.nicks[account],
1779 'groups': ['self_contact'], 'subscription': 'both',
1780 'ask': 'none'}
1781 # .keys() is needed
1782 for jid in array.keys():
1783 # Remove the contact in roster. It might has changed
1784 self.remove_contact(jid, account, force=True)
1785 # Remove old Contact instances
1786 gajim.contacts.remove_jid(account, jid, remove_meta=False)
1787 jids = jid.split('/')
1788 # get jid
1789 ji = jids[0]
1790 # get resource
1791 resource = ''
1792 if len(jids) > 1:
1793 resource = '/'.join(jids[1:])
1794 # get name
1795 name = array[jid]['name'] or ''
1796 show = 'offline' # show is offline by default
1797 status = '' # no status message by default
1799 keyID = ''
1800 attached_keys = gajim.config.get_per('accounts', account,
1801 'attached_gpg_keys').split()
1802 if jid in attached_keys:
1803 keyID = attached_keys[attached_keys.index(jid) + 1]
1805 if gajim.jid_is_transport(jid):
1806 array[jid]['groups'] = [_('Transports')]
1807 #TRANSP - potential
1808 contact1 = gajim.contacts.create_contact(jid=ji, account=account,
1809 name=name, groups=array[jid]['groups'], show=show,
1810 status=status, sub=array[jid]['subscription'],
1811 ask=array[jid]['ask'], resource=resource, keyID=keyID)
1812 gajim.contacts.add_contact(account, contact1)
1814 if gajim.config.get('ask_avatars_on_startup'):
1815 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
1816 if pixbuf == 'ask':
1817 transport = gajim.get_transport_name_from_jid(contact1.jid)
1818 if not transport or gajim.jid_is_transport(contact1.jid):
1819 jid_with_resource = contact1.jid
1820 if contact1.resource:
1821 jid_with_resource += '/' + contact1.resource
1822 gajim.connections[account].request_vcard(
1823 jid_with_resource)
1824 else:
1825 host = gajim.get_server_from_jid(contact1.jid)
1826 if host not in gajim.transport_avatar[account]:
1827 gajim.transport_avatar[account][host] = \
1828 [contact1.jid]
1829 else:
1830 gajim.transport_avatar[account][host].append(
1831 contact1.jid)
1833 # If we already have chat windows opened, update them with new
1834 # contact instance
1835 chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
1836 if chat_control:
1837 chat_control.contact = contact1
1839 def connected_rooms(self, account):
1840 if account in gajim.gc_connected[account].values():
1841 return True
1842 return False
1844 def on_event_removed(self, event_list):
1846 Remove contacts on last events removed
1848 Only performed if removal was requested before but the contact still had
1849 pending events
1851 contact_list = ((event.jid.split('/')[0], event.account) for event in \
1852 event_list)
1854 for jid, account in contact_list:
1855 self.draw_contact(jid, account)
1856 # Remove contacts in roster if removal was requested
1857 key = (jid, account)
1858 if key in self.contacts_to_be_removed.keys():
1859 backend = self.contacts_to_be_removed[key]['backend']
1860 del self.contacts_to_be_removed[key]
1861 # Remove contact will delay removal if there are more events
1862 # pending
1863 self.remove_contact(jid, account, backend=backend)
1864 self.show_title()
1866 def open_event(self, account, jid, event):
1868 If an event was handled, return True, else return False
1870 data = event.parameters
1871 ft = gajim.interface.instances['file_transfers']
1872 event = gajim.events.get_first_event(account, jid, event.type_)
1873 if event.type_ == 'normal':
1874 dialogs.SingleMessageWindow(account, jid,
1875 action='receive', from_whom=jid, subject=data[1],
1876 message=data[0], resource=data[5], session=data[8],
1877 form_node=data[9])
1878 gajim.events.remove_events(account, jid, event)
1879 return True
1880 elif event.type_ == 'file-request':
1881 contact = gajim.contacts.get_contact_with_highest_priority(account,
1882 jid)
1883 ft.show_file_request(account, contact, data)
1884 gajim.events.remove_events(account, jid, event)
1885 return True
1886 elif event.type_ in ('file-request-error', 'file-send-error'):
1887 ft.show_send_error(data)
1888 gajim.events.remove_events(account, jid, event)
1889 return True
1890 elif event.type_ in ('file-error', 'file-stopped'):
1891 msg_err = ''
1892 if data['error'] == -1:
1893 msg_err = _('Remote contact stopped transfer')
1894 elif data['error'] == -6:
1895 msg_err = _('Error opening file')
1896 ft.show_stopped(jid, data, error_msg=msg_err)
1897 gajim.events.remove_events(account, jid, event)
1898 return True
1899 elif event.type_ == 'file-completed':
1900 ft.show_completed(jid, data)
1901 gajim.events.remove_events(account, jid, event)
1902 return True
1903 elif event.type_ == 'gc-invitation':
1904 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1905 data[1])
1906 gajim.events.remove_events(account, jid, event)
1907 return True
1908 elif event.type_ == 'subscription_request':
1909 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1910 gajim.events.remove_events(account, jid, event)
1911 return True
1912 elif event.type_ == 'unsubscribed':
1913 gajim.interface.show_unsubscribed_dialog(account, data)
1914 gajim.events.remove_events(account, jid, event)
1915 return True
1916 elif event.type_ == 'jingle-incoming':
1917 peerjid, sid, content_types = data
1918 dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
1919 gajim.events.remove_events(account, jid, event)
1920 return True
1921 return False
1923 ################################################################################
1924 ### This and that... random.
1925 ################################################################################
1927 def show_roster_vbox(self, active):
1928 vb = self.xml.get_object('roster_vbox2')
1929 if active:
1930 vb.set_no_show_all(False)
1931 vb.show()
1932 else:
1933 vb.hide()
1934 vb.set_no_show_all(True)
1936 def show_tooltip(self, contact):
1937 pointer = self.tree.get_pointer()
1938 props = self.tree.get_path_at_pos(pointer[0], pointer[1])
1939 # check if the current pointer is at the same path
1940 # as it was before setting the timeout
1941 if props and self.tooltip.id == props[0]:
1942 # bounding rectangle of coordinates for the cell within the treeview
1943 rect = self.tree.get_cell_area(props[0], props[1])
1945 # position of the treeview on the screen
1946 position = self.tree.window.get_origin()
1947 self.tooltip.show_tooltip(contact, rect.height, position[1] + \
1948 rect.y)
1949 else:
1950 self.tooltip.hide_tooltip()
1953 def authorize(self, widget, jid, account):
1955 Authorize a contact (by re-sending auth menuitem)
1957 gajim.connections[account].send_authorization(jid)
1958 dialogs.InformationDialog(_('Authorization has been sent'),
1959 _('Now "%s" will know your status.') %jid)
1961 def req_sub(self, widget, jid, txt, account, groups=None, nickname=None,
1962 auto_auth=False):
1964 Request subscription to a contact
1966 groups_list = groups or []
1967 gajim.connections[account].request_subscription(jid, txt, nickname,
1968 groups_list, auto_auth, gajim.nicks[account])
1969 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
1970 if not contact:
1971 keyID = ''
1972 attached_keys = gajim.config.get_per('accounts', account,
1973 'attached_gpg_keys').split()
1974 if jid in attached_keys:
1975 keyID = attached_keys[attached_keys.index(jid) + 1]
1976 contact = gajim.contacts.create_contact(jid=jid, account=account,
1977 name=nickname, groups=groups_list, show='requested', status='',
1978 ask='none', sub='subscribe', keyID=keyID)
1979 gajim.contacts.add_contact(account, contact)
1980 else:
1981 if not _('Not in Roster') in contact.get_shown_groups():
1982 dialogs.InformationDialog(_('Subscription request has been '
1983 'sent'), _('If "%s" accepts this request you will know his '
1984 'or her status.') % jid)
1985 return
1986 self.remove_contact(contact.jid, account, force=True)
1987 contact.groups = groups_list
1988 if nickname:
1989 contact.name = nickname
1990 self.add_contact(jid, account)
1992 def revoke_auth(self, widget, jid, account):
1994 Revoke a contact's authorization
1996 gajim.connections[account].refuse_authorization(jid)
1997 dialogs.InformationDialog(_('Authorization has been removed'),
1998 _('Now "%s" will always see you as offline.') %jid)
2000 def set_state(self, account, state):
2001 child_iterA = self._get_account_iter(account, self.model)
2002 if child_iterA:
2003 self.model[child_iterA][0] = \
2004 gajim.interface.jabber_state_images['16'][state]
2005 if gajim.interface.systray_enabled:
2006 gajim.interface.systray.change_status(state)
2008 def set_connecting_state(self, account):
2009 self.set_state(account, 'connecting')
2011 def send_status(self, account, status, txt, auto=False, to=None):
2012 if status != 'offline':
2013 if to is None:
2014 if status == gajim.connections[account].get_status() and \
2015 txt == gajim.connections[account].status:
2016 return
2017 gajim.config.set_per('accounts', account, 'last_status', status)
2018 gajim.config.set_per('accounts', account, 'last_status_msg',
2019 helpers.to_one_line(txt))
2020 if gajim.connections[account].connected < 2:
2021 self.set_connecting_state(account)
2023 keyid = gajim.config.get_per('accounts', account, 'keyid')
2024 if keyid and not gajim.connections[account].gpg:
2025 dialogs.WarningDialog(_('GPG is not usable'),
2026 _('You will be connected to %s without OpenPGP.') % \
2027 account)
2029 self.send_status_continue(account, status, txt, auto, to)
2031 def send_pep(self, account, pep_dict):
2032 connection = gajim.connections[account]
2034 if 'activity' in pep_dict:
2035 activity = pep_dict['activity']
2036 subactivity = pep_dict.get('subactivity', None)
2037 activity_text = pep_dict.get('activity_text', None)
2038 connection.send_activity(activity, subactivity, activity_text)
2039 else:
2040 connection.retract_activity()
2042 if 'mood' in pep_dict:
2043 mood = pep_dict['mood']
2044 mood_text = pep_dict.get('mood_text', None)
2045 connection.send_mood(mood, mood_text)
2046 else:
2047 connection.retract_mood()
2049 def delete_pep(self, jid, account):
2050 if jid == gajim.get_jid_from_account(account):
2051 gajim.connections[account].pep = {}
2052 self.draw_account(account)
2054 for contact in gajim.contacts.get_contacts(account, jid):
2055 contact.pep = {}
2057 self.draw_all_pep_types(jid, account)
2058 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
2059 if ctrl:
2060 ctrl.update_all_pep_types()
2062 def send_status_continue(self, account, status, txt, auto, to):
2063 if gajim.account_is_connected(account) and not to:
2064 if status == 'online' and gajim.interface.sleeper.getState() != \
2065 common.sleepy.STATE_UNKNOWN:
2066 gajim.sleeper_state[account] = 'online'
2067 elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \
2068 status == 'offline':
2069 gajim.sleeper_state[account] = 'off'
2071 if to:
2072 gajim.connections[account].send_custom_status(status, txt, to)
2073 else:
2074 if status in ('invisible', 'offline'):
2075 self.delete_pep(gajim.get_jid_from_account(account), account)
2076 was_invisible = gajim.connections[account].connected == \
2077 gajim.SHOW_LIST.index('invisible')
2078 gajim.connections[account].change_status(status, txt, auto)
2080 if account in gajim.interface.status_sent_to_users:
2081 gajim.interface.status_sent_to_users[account] = {}
2082 if account in gajim.interface.status_sent_to_groups:
2083 gajim.interface.status_sent_to_groups[account] = {}
2084 for gc_control in gajim.interface.msg_win_mgr.get_controls(
2085 message_control.TYPE_GC) + \
2086 gajim.interface.minimized_controls[account].values():
2087 if gc_control.account == account:
2088 if gajim.gc_connected[account][gc_control.room_jid]:
2089 gajim.connections[account].send_gc_status(
2090 gc_control.nick, gc_control.room_jid, status, txt)
2091 else:
2092 # for some reason, we are not connected to the room even
2093 # if tab is opened, send initial join_gc()
2094 gajim.connections[account].join_gc(gc_control.nick,
2095 gc_control.room_jid, None)
2096 if was_invisible and status != 'offline':
2097 # We come back from invisible, join bookmarks
2098 gajim.interface.auto_join_bookmarks(account)
2101 def chg_contact_status(self, contact, show, status, account):
2103 When a contact changes his or her status
2105 contact_instances = gajim.contacts.get_contacts(account, contact.jid)
2106 contact.show = show
2107 contact.status = status
2108 # name is to show in conversation window
2109 name = contact.get_shown_name()
2110 fjid = contact.get_full_jid()
2112 # The contact has several resources
2113 if len(contact_instances) > 1:
2114 if contact.resource != '':
2115 name += '/' + contact.resource
2117 # Remove resource when going offline
2118 if show in ('offline', 'error') and \
2119 not self.contact_has_pending_roster_events(contact, account):
2120 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2121 if ctrl:
2122 ctrl.update_ui()
2123 ctrl.parent_win.redraw_tab(ctrl)
2124 # keep the contact around, since it's
2125 # already attached to the control
2126 else:
2127 gajim.contacts.remove_contact(account, contact)
2129 elif contact.jid == gajim.get_jid_from_account(account) and \
2130 show in ('offline', 'error'):
2131 if gajim.config.get('show_self_contact') != 'never':
2132 # SelfContact went offline. Remove him when last pending
2133 # message was read
2134 self.remove_contact(contact.jid, account, backend=True)
2136 uf_show = helpers.get_uf_show(show)
2138 # print status in chat window and update status/GPG image
2139 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
2140 if ctrl and ctrl.type_id != message_control.TYPE_GC:
2141 ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
2142 account, contact.jid)
2143 ctrl.update_status_display(name, uf_show, status)
2145 if contact.resource:
2146 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2147 if ctrl:
2148 ctrl.update_status_display(name, uf_show, status)
2150 # Delete pep if needed
2151 keep_pep = any(c.show not in ('error', 'offline') for c in
2152 contact_instances)
2153 if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
2154 and not contact.is_groupchat():
2155 self.delete_pep(contact.jid, account)
2157 # Redraw everything and select the sender
2158 self.adjust_and_draw_contact_context(contact.jid, account)
2161 def on_status_changed(self, account, show):
2163 The core tells us that our status has changed
2165 if account not in gajim.contacts.get_accounts():
2166 return
2167 child_iterA = self._get_account_iter(account, self.model)
2168 if gajim.config.get('show_self_contact') == 'always':
2169 self_resource = gajim.connections[account].server_resource
2170 self_contact = gajim.contacts.get_contact(account,
2171 gajim.get_jid_from_account(account), resource=self_resource)
2172 if self_contact:
2173 status = gajim.connections[account].status
2174 self.chg_contact_status(self_contact, show, status, account)
2175 self.set_account_status_icon(account)
2176 if show == 'offline':
2177 if self.quit_on_next_offline > -1:
2178 # we want to quit, we are waiting for all accounts to be offline
2179 self.quit_on_next_offline -= 1
2180 if self.quit_on_next_offline < 1:
2181 # all accounts offline, quit
2182 self.quit_gtkgui_interface()
2183 else:
2184 # No need to redraw contacts if we're quitting
2185 if child_iterA:
2186 self.model[child_iterA][C_AVATAR_PIXBUF] = None
2187 if account in gajim.con_types:
2188 gajim.con_types[account] = None
2189 for jid in gajim.contacts.get_jid_list(account):
2190 lcontact = gajim.contacts.get_contacts(account, jid)
2191 ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid,
2192 account)
2193 for contact in [c for c in lcontact if (
2194 (c.show != 'offline' or c.is_transport()) and not ctrl)]:
2195 self.chg_contact_status(contact, 'offline', '', account)
2196 self.set_actions_menu_needs_rebuild()
2197 self.update_status_combobox()
2199 def get_status_message(self, show, on_response, show_pep=True,
2200 always_ask=False):
2202 Get the status message by:
2204 1/ looking in default status message
2205 2/ asking to user if needed depending on ask_on(ff)line_status and
2206 always_ask
2207 show_pep can be False to hide pep things from status message or True
2209 empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
2210 'mood': '', 'mood_text': ''}
2211 if show in gajim.config.get_per('defaultstatusmsg'):
2212 if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
2213 msg = gajim.config.get_per('defaultstatusmsg', show, 'message')
2214 msg = helpers.from_one_line(msg)
2215 on_response(msg, empty_pep)
2216 return
2217 if not always_ask and ((show == 'online' and not gajim.config.get(
2218 'ask_online_status')) or (show in ('offline', 'invisible') and not \
2219 gajim.config.get('ask_offline_status'))):
2220 on_response('', empty_pep)
2221 return
2223 dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
2224 dlg.dialog.present() # show it on current workspace
2226 def change_status(self, widget, account, status):
2227 def change(account, status):
2228 def on_response(message, pep_dict):
2229 if message is None:
2230 # user pressed Cancel to change status message dialog
2231 return
2232 self.send_status(account, status, message)
2233 self.send_pep(account, pep_dict)
2234 self.get_status_message(status, on_response)
2236 if status == 'invisible' and self.connected_rooms(account):
2237 dialogs.ConfirmationDialog(
2238 _('You are participating in one or more group chats'),
2239 _('Changing your status to invisible will result in '
2240 'disconnection from those group chats. Are you sure you want '
2241 'to go invisible?'), on_response_ok = (change, account, status))
2242 else:
2243 change(account, status)
2245 def update_status_combobox(self):
2246 # table to change index in connection.connected to index in combobox
2247 table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
2248 'xa':3, 'dnd':4, 'invisible':5}
2250 # we check if there are more options in the combobox that it should
2251 # if yes, we remove the first ones
2252 while len(self.status_combobox.get_model()) > len(table)+2:
2253 self.status_combobox.remove_text(0)
2255 show = helpers.get_global_show()
2256 # temporarily block signal in order not to send status that we show
2257 # in the combobox
2258 self.combobox_callback_active = False
2259 if helpers.statuses_unified():
2260 self.status_combobox.set_active(table[show])
2261 else:
2262 uf_show = helpers.get_uf_show(show)
2263 liststore = self.status_combobox.get_model()
2264 liststore.prepend(['SEPARATOR', None, '', True])
2265 status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
2266 liststore.prepend([status_combobox_text,
2267 gajim.interface.jabber_state_images['16'][show], show, False])
2268 self.status_combobox.set_active(0)
2269 gajim.interface.change_awn_icon_status(show)
2270 self.combobox_callback_active = True
2271 if gajim.interface.systray_enabled:
2272 gajim.interface.systray.change_status(show)
2274 def get_show(self, lcontact):
2275 prio = lcontact[0].priority
2276 show = lcontact[0].show
2277 for u in lcontact:
2278 if u.priority > prio:
2279 prio = u.priority
2280 show = u.show
2281 return show
2283 def on_message_window_delete(self, win_mgr, msg_win):
2284 if gajim.config.get('one_message_window') == 'always_with_roster':
2285 self.show_roster_vbox(True)
2286 gtkgui_helpers.resize_window(self.window,
2287 gajim.config.get('roster_width'),
2288 gajim.config.get('roster_height'))
2290 def close_all_from_dict(self, dic):
2292 Close all the windows in the given dictionary
2294 for w in dic.values():
2295 if isinstance(w, dict):
2296 self.close_all_from_dict(w)
2297 else:
2298 w.window.destroy()
2300 def close_all(self, account, force=False):
2302 Close all the windows from an account. If force is True, do not ask
2303 confirmation before closing chat/gc windows
2305 if account in gajim.interface.instances:
2306 self.close_all_from_dict(gajim.interface.instances[account])
2307 for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
2308 ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
2309 force = force)
2311 def on_roster_window_delete_event(self, widget, event):
2313 Main window X button was clicked
2315 if gajim.interface.systray_enabled and not gajim.config.get(
2316 'quit_on_roster_x_button') and gajim.config.get('trayicon') != \
2317 'on_event':
2318 self.tooltip.hide_tooltip()
2319 self.window.hide()
2320 elif gajim.config.get('quit_on_roster_x_button'):
2321 self.on_quit_request()
2322 else:
2323 def on_ok(checked):
2324 if checked:
2325 gajim.config.set('quit_on_roster_x_button', True)
2326 self.on_quit_request()
2327 dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'),
2328 _('Are you sure you want to quit Gajim?'),
2329 _('Always close Gajim'), on_response_ok=on_ok)
2330 return True # do NOT destroy the window
2332 def prepare_quit(self):
2333 msgwin_width_adjust = 0
2335 # in case show_roster_on_start is False and roster is never shown
2336 # window.window is None
2337 if self.window.window is not None:
2338 x, y = self.window.window.get_root_origin()
2339 gajim.config.set('roster_x-position', x)
2340 gajim.config.set('roster_y-position', y)
2341 width, height = self.window.get_size()
2342 # For the width use the size of the vbox containing the tree and
2343 # status combo, this will cancel out any hpaned width
2344 width = self.xml.get_object('roster_vbox2').allocation.width
2345 gajim.config.set('roster_width', width)
2346 gajim.config.set('roster_height', height)
2347 if not self.xml.get_object('roster_vbox2').get_property('visible'):
2348 # The roster vbox is hidden, so the message window is larger
2349 # then we want to save (i.e. the window will grow every startup)
2350 # so adjust.
2351 msgwin_width_adjust = -1 * width
2352 gajim.config.set('last_roster_visible',
2353 self.window.get_property('visible'))
2354 gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust)
2356 gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows))
2357 gajim.interface.save_config()
2358 for account in gajim.connections:
2359 gajim.connections[account].quit(True)
2360 self.close_all(account)
2361 if gajim.interface.systray_enabled:
2362 gajim.interface.hide_systray()
2364 def quit_gtkgui_interface(self):
2366 When we quit the gtk interface - exit gtk
2368 self.prepare_quit()
2369 gtk.main_quit()
2371 def on_quit_request(self, widget=None):
2373 User wants to quit. Check if he should be warned about messages pending.
2374 Terminate all sessions and send offline to all connected account. We do
2375 NOT really quit gajim here
2377 accounts = gajim.connections.keys()
2378 get_msg = False
2379 for acct in accounts:
2380 if gajim.connections[acct].connected:
2381 get_msg = True
2382 break
2384 def on_continue3(message, pep_dict):
2385 self.quit_on_next_offline = 0
2386 accounts_to_disconnect = []
2387 for acct in accounts:
2388 if gajim.connections[acct].connected > 1:
2389 self.quit_on_next_offline += 1
2390 accounts_to_disconnect.append(acct)
2392 for acct in accounts_to_disconnect:
2393 self.send_status(acct, 'offline', message)
2394 self.send_pep(acct, pep_dict)
2396 if not self.quit_on_next_offline:
2397 self.quit_gtkgui_interface()
2399 def on_continue2(message, pep_dict):
2400 # check if there is an active file transfer
2401 from common.protocol.bytestream import (is_transfer_active)
2402 files_props = gajim.interface.instances['file_transfers'].\
2403 files_props
2404 transfer_active = False
2405 for x in files_props:
2406 for y in files_props[x]:
2407 if is_transfer_active(files_props[x][y]):
2408 transfer_active = True
2409 break
2411 if transfer_active:
2412 dialogs.ConfirmationDialog(_('You have running file transfers'),
2413 _('If you quit now, the file(s) being transferred will '
2414 'be stopped. Do you still want to quit?'),
2415 on_response_ok=(on_continue3, message, pep_dict))
2416 return
2417 on_continue3(message, pep_dict)
2419 def on_continue(message, pep_dict):
2420 if message is None:
2421 # user pressed Cancel to change status message dialog
2422 return
2423 # check if we have unread messages
2424 unread = gajim.events.get_nb_events()
2425 if not gajim.config.get('notify_on_all_muc_messages'):
2426 unread_not_to_notify = gajim.events.get_nb_events(
2427 ['printed_gc_msg'])
2428 unread -= unread_not_to_notify
2430 # check if we have recent messages
2431 recent = False
2432 for win in gajim.interface.msg_win_mgr.windows():
2433 for ctrl in win.controls():
2434 fjid = ctrl.get_full_jid()
2435 if fjid in gajim.last_message_time[ctrl.account]:
2436 if time.time() - gajim.last_message_time[ctrl.account][
2437 fjid] < 2:
2438 recent = True
2439 break
2440 if recent:
2441 break
2443 if unread or recent:
2444 dialogs.ConfirmationDialog(_('You have unread messages'),
2445 _('Messages will only be available for reading them later '
2446 'if you have history enabled and contact is in your '
2447 'roster.'), on_response_ok=(on_continue2,
2448 message, pep_dict))
2449 return
2450 on_continue2(message, pep_dict)
2452 if get_msg:
2453 self.get_status_message('offline', on_continue, show_pep=False)
2454 else:
2455 on_continue('', None)
2457 def _nec_presence_received(self, obj):
2458 account = obj.conn.name
2459 jid = obj.jid
2461 if obj.need_add_in_roster:
2462 self.add_contact(jid, account)
2464 jid_list = gajim.contacts.get_jid_list(account)
2465 if jid in jid_list or jid == gajim.get_jid_from_account(account):
2466 if not gajim.jid_is_transport(jid) and len(obj.contact_list) == 1:
2467 if obj.old_show == 0 and obj.new_show > 1:
2468 gobject.timeout_add_seconds(5, self.remove_newly_added, jid,
2469 account)
2470 elif obj.old_show > 1 and obj.new_show == 0 and \
2471 obj.conn.connected > 1:
2472 gobject.timeout_add_seconds(5, self.remove_to_be_removed,
2473 jid, account)
2475 if obj.need_redraw:
2476 self.draw_contact(jid, account)
2478 if gajim.jid_is_transport(jid) and jid in jid_list:
2479 # It must be an agent
2480 # Update existing iter and group counting
2481 self.draw_contact(jid, account)
2482 self.draw_group(_('Transports'), account)
2483 if obj.new_show > 1 and jid in gajim.transport_avatar[account]:
2484 # transport just signed in.
2485 # request avatars
2486 for jid_ in gajim.transport_avatar[account][jid]:
2487 obj.conn.request_vcard(jid_)
2489 if obj.contact:
2490 self.chg_contact_status(obj.contact, obj.show, obj.status, account)
2492 def _nec_gc_presence_received(self, obj):
2493 account = obj.conn.name
2494 if obj.room_jid in gajim.interface.minimized_controls[account]:
2495 gc_ctrl = gajim.interface.minimized_controls[account][obj.room_jid]
2496 else:
2497 return
2499 contact = gajim.contacts.get_contact_with_highest_priority(account,
2500 obj.room_jid)
2501 if contact:
2502 contact.show = obj.show
2503 self.draw_contact(obj.room_jid, account)
2504 self.draw_group(_('Groupchats'), account)
2506 def _nec_roster_received(self, obj):
2507 if obj.received_from_server:
2508 self.fill_contacts_and_groups_dicts(obj.roster, obj.conn.name)
2509 self.add_account_contacts(obj.conn.name)
2510 self.fire_up_unread_messages_events(obj.conn.name)
2511 else:
2512 gobject.idle_add(self.refilter_shown_roster_items)
2514 def _nec_anonymous_auth(self, obj):
2516 This event is raised when our JID changed (most probably because we use
2517 anonymous account. We update contact and roster entry in this case
2519 self.rename_self_contact(obj.old_jid, obj.new_jid, obj.conn.name)
2521 def _nec_our_show(self, obj):
2522 model = self.status_combobox.get_model()
2523 if obj.show == 'offline':
2524 # sensitivity for this menuitem
2525 if gajim.get_number_of_connected_accounts() == 0:
2526 model[self.status_message_menuitem_iter][3] = False
2527 else:
2528 # sensitivity for this menuitem
2529 model[self.status_message_menuitem_iter][3] = True
2530 self.on_status_changed(obj.conn.name, obj.show)
2532 def _nec_connection_type(self, obj):
2533 self.draw_account(obj.conn.name)
2535 def _nec_agent_removed(self, obj):
2536 for jid in obj.jid_list:
2537 self.remove_contact(jid, obj.conn.name, backend=True)
2539 def _nec_pep_received(self, obj):
2540 if obj.jid == common.gajim.get_jid_from_account(obj.conn.name):
2541 self.draw_account(obj.conn.name)
2543 if obj.pep_type == 'nickname':
2544 self.draw_contact(obj.jid, obj.conn.name)
2545 else:
2546 self.draw_pep(obj.jid, obj.conn.name, obj.pep_type)
2548 def _nec_vcard_received(self, obj):
2549 if obj.resource:
2550 # it's a muc occupant vcard
2551 return
2552 self.draw_avatar(obj.jid, obj.conn.name)
2554 def _nec_gc_subject_received(self, obj):
2555 contact = gajim.contacts.get_contact_with_highest_priority(
2556 obj.conn.name, obj.room_jid)
2557 if contact:
2558 contact.status = obj.subject
2559 self.draw_contact(obj.room_jid, obj.conn.name)
2561 def _nec_metacontacts_received(self, obj):
2562 self.redraw_metacontacts(obj.conn.name)
2564 def _nec_signed_in(self, obj):
2565 self.set_actions_menu_needs_rebuild()
2566 self.draw_account(obj.conn.name)
2568 ################################################################################
2569 ### Menu and GUI callbacks
2570 ### FIXME: order callbacks in itself...
2571 ################################################################################
2573 def on_actions_menuitem_activate(self, widget):
2574 self.make_menu()
2576 def on_edit_menuitem_activate(self, widget):
2578 Need to call make_menu to build profile, avatar item
2580 self.make_menu()
2582 def on_bookmark_menuitem_activate(self, widget, account, bookmark):
2583 gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'],
2584 bookmark['password'])
2586 def on_send_server_message_menuitem_activate(self, widget, account):
2587 server = gajim.config.get_per('accounts', account, 'hostname')
2588 server += '/announce/online'
2589 dialogs.SingleMessageWindow(account, server, 'send')
2591 def on_xml_console_menuitem_activate(self, widget, account):
2592 if 'xml_console' in gajim.interface.instances[account]:
2593 gajim.interface.instances[account]['xml_console'].window.present()
2594 else:
2595 gajim.interface.instances[account]['xml_console'] = \
2596 dialogs.XMLConsoleWindow(account)
2598 def on_archiving_preferences_menuitem_activate(self, widget, account):
2599 if 'archiving_preferences' in gajim.interface.instances[account]:
2600 gajim.interface.instances[account]['archiving_preferences'].window.\
2601 present()
2602 else:
2603 gajim.interface.instances[account]['archiving_preferences'] = \
2604 dialogs.ArchivingPreferencesWindow(account)
2606 def on_privacy_lists_menuitem_activate(self, widget, account):
2607 if 'privacy_lists' in gajim.interface.instances[account]:
2608 gajim.interface.instances[account]['privacy_lists'].window.present()
2609 else:
2610 gajim.interface.instances[account]['privacy_lists'] = \
2611 dialogs.PrivacyListsWindow(account)
2613 def on_set_motd_menuitem_activate(self, widget, account):
2614 server = gajim.config.get_per('accounts', account, 'hostname')
2615 server += '/announce/motd'
2616 dialogs.SingleMessageWindow(account, server, 'send')
2618 def on_update_motd_menuitem_activate(self, widget, account):
2619 server = gajim.config.get_per('accounts', account, 'hostname')
2620 server += '/announce/motd/update'
2621 dialogs.SingleMessageWindow(account, server, 'send')
2623 def on_delete_motd_menuitem_activate(self, widget, account):
2624 server = gajim.config.get_per('accounts', account, 'hostname')
2625 server += '/announce/motd/delete'
2626 gajim.connections[account].send_motd(server)
2628 def on_history_manager_menuitem_activate(self, widget):
2629 if os.name == 'nt':
2630 if os.path.exists('history_manager.exe'): # user is running stable
2631 helpers.exec_command('history_manager.exe')
2632 else: # user is running svn
2633 helpers.exec_command('%s history_manager.py' % sys.executable)
2634 else: # Unix user
2635 helpers.exec_command('%s history_manager.py' % sys.executable)
2637 def on_info(self, widget, contact, account):
2639 Call vcard_information_window class to display contact's information
2641 if gajim.connections[account].is_zeroconf:
2642 self.on_info_zeroconf(widget, contact, account)
2643 return
2645 info = gajim.interface.instances[account]['infos']
2646 if contact.jid in info:
2647 info[contact.jid].window.present()
2648 else:
2649 info[contact.jid] = vcard.VcardWindow(contact, account)
2651 def on_info_zeroconf(self, widget, contact, account):
2652 info = gajim.interface.instances[account]['infos']
2653 if contact.jid in info:
2654 info[contact.jid].window.present()
2655 else:
2656 contact = gajim.contacts.get_first_contact_from_jid(account,
2657 contact.jid)
2658 if contact.show in ('offline', 'error'):
2659 # don't show info on offline contacts
2660 return
2661 info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
2663 def on_roster_treeview_leave_notify_event(self, widget, event):
2664 props = widget.get_path_at_pos(int(event.x), int(event.y))
2665 if self.tooltip.timeout > 0:
2666 if not props or self.tooltip.id == props[0]:
2667 self.tooltip.hide_tooltip()
2669 def on_roster_treeview_motion_notify_event(self, widget, event):
2670 model = widget.get_model()
2671 props = widget.get_path_at_pos(int(event.x), int(event.y))
2672 if self.tooltip.timeout > 0:
2673 if not props or self.tooltip.id != props[0]:
2674 self.tooltip.hide_tooltip()
2675 if props:
2676 row = props[0]
2677 titer = None
2678 try:
2679 titer = model.get_iter(row)
2680 except Exception:
2681 self.tooltip.hide_tooltip()
2682 return
2683 if model[titer][C_TYPE] in ('contact', 'self_contact'):
2684 # we're on a contact entry in the roster
2685 account = model[titer][C_ACCOUNT].decode('utf-8')
2686 jid = model[titer][C_JID].decode('utf-8')
2687 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2688 self.tooltip.id = row
2689 contacts = gajim.contacts.get_contacts(account, jid)
2690 connected_contacts = []
2691 for c in contacts:
2692 if c.show not in ('offline', 'error'):
2693 connected_contacts.append(c)
2694 if not connected_contacts:
2695 # no connected contacts, show the ofline one
2696 connected_contacts = contacts
2697 self.tooltip.account = account
2698 self.tooltip.timeout = gobject.timeout_add(500,
2699 self.show_tooltip, connected_contacts)
2700 elif model[titer][C_TYPE] == 'groupchat':
2701 account = model[titer][C_ACCOUNT].decode('utf-8')
2702 jid = model[titer][C_JID].decode('utf-8')
2703 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2704 self.tooltip.id = row
2705 contact = gajim.contacts.get_contacts(account, jid)
2706 self.tooltip.account = account
2707 self.tooltip.timeout = gobject.timeout_add(500,
2708 self.show_tooltip, contact)
2709 elif model[titer][C_TYPE] == 'account':
2710 # we're on an account entry in the roster
2711 account = model[titer][C_ACCOUNT].decode('utf-8')
2712 if account == 'all':
2713 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2714 self.tooltip.id = row
2715 self.tooltip.account = None
2716 self.tooltip.timeout = gobject.timeout_add(500,
2717 self.show_tooltip, [])
2718 return
2719 jid = gajim.get_jid_from_account(account)
2720 contacts = []
2721 connection = gajim.connections[account]
2722 # get our current contact info
2724 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
2725 accounts = [account])
2726 account_name = account
2727 if gajim.account_is_connected(account):
2728 account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
2729 contact = gajim.contacts.create_self_contact(jid=jid,
2730 account=account, name=account_name,
2731 show=connection.get_status(), status=connection.status,
2732 resource=connection.server_resource,
2733 priority=connection.priority)
2734 if gajim.connections[account].gpg:
2735 contact.keyID = gajim.config.get_per('accounts',
2736 connection.name, 'keyid')
2737 contacts.append(contact)
2738 # if we're online ...
2739 if connection.connection:
2740 roster = connection.connection.getRoster()
2741 # in threadless connection when no roster stanza is sent,
2742 # 'roster' is None
2743 if roster and roster.getItem(jid):
2744 resources = roster.getResources(jid)
2745 # ...get the contact info for our other online resources
2746 for resource in resources:
2747 # Check if we already have this resource
2748 found = False
2749 for contact_ in contacts:
2750 if contact_.resource == resource:
2751 found = True
2752 break
2753 if found:
2754 continue
2755 show = roster.getShow(jid+'/'+resource)
2756 if not show:
2757 show = 'online'
2758 contact = gajim.contacts.create_self_contact(
2759 jid=jid, account=account, show=show,
2760 status=roster.getStatus(jid + '/' + resource),
2761 priority=roster.getPriority(
2762 jid + '/' + resource), resource=resource)
2763 contacts.append(contact)
2764 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2765 self.tooltip.id = row
2766 self.tooltip.account = None
2767 self.tooltip.timeout = gobject.timeout_add(500,
2768 self.show_tooltip, contacts)
2770 def on_agent_logging(self, widget, jid, state, account):
2772 When an agent is requested to log in or off
2774 gajim.connections[account].send_agent_status(jid, state)
2776 def on_edit_agent(self, widget, contact, account):
2778 When we want to modify the agent registration
2780 gajim.connections[account].request_register_agent_info(contact.jid)
2782 def on_remove_agent(self, widget, list_):
2784 When an agent is requested to be removed. list_ is a list of (contact,
2785 account) tuple
2787 for (contact, account) in list_:
2788 if gajim.config.get_per('accounts', account, 'hostname') == \
2789 contact.jid:
2790 # We remove the server contact
2791 # remove it from treeview
2792 gajim.connections[account].unsubscribe(contact.jid)
2793 self.remove_contact(contact.jid, account, backend=True)
2794 return
2796 def remove(list_):
2797 for (contact, account) in list_:
2798 full_jid = contact.get_full_jid()
2799 gajim.connections[account].unsubscribe_agent(full_jid)
2800 # remove transport from treeview
2801 self.remove_contact(contact.jid, account, backend=True)
2803 # Check if there are unread events from some contacts
2804 has_unread_events = False
2805 for (contact, account) in list_:
2806 for jid in gajim.events.get_events(account):
2807 if jid.endswith(contact.jid):
2808 has_unread_events = True
2809 break
2810 if has_unread_events:
2811 dialogs.ErrorDialog(_('You have unread messages'),
2812 _('You must read them before removing this transport.'))
2813 return
2814 if len(list_) == 1:
2815 pritext = _('Transport "%s" will be removed') % list_[0][0].jid
2816 sectext = _('You will no longer be able to send and receive '
2817 'messages from contacts using this transport.')
2818 else:
2819 pritext = _('Transports will be removed')
2820 jids = ''
2821 for (contact, account) in list_:
2822 jids += '\n ' + contact.get_shown_name() + ','
2823 jids = jids[:-1] + '.'
2824 sectext = _('You will no longer be able to send and receive '
2825 'messages to contacts from these transports: %s') % jids
2826 dialogs.ConfirmationDialog(pritext, sectext,
2827 on_response_ok = (remove, list_))
2829 def on_block(self, widget, list_, group=None):
2831 When clicked on the 'block' button in context menu. list_ is a list of
2832 (contact, account)
2834 def on_continue(msg, pep_dict):
2835 if msg is None:
2836 # user pressed Cancel to change status message dialog
2837 return
2838 accounts = []
2839 if group is None:
2840 for (contact, account) in list_:
2841 if account not in accounts:
2842 if not gajim.connections[account].\
2843 privacy_rules_supported:
2844 continue
2845 accounts.append(account)
2846 self.send_status(account, 'offline', msg, to=contact.jid)
2847 new_rule = {'order': u'1', 'type': u'jid',
2848 'action': u'deny', 'value' : contact.jid,
2849 'child': [u'message', u'iq', u'presence-out']}
2850 gajim.connections[account].blocked_list.append(new_rule)
2851 # needed for draw_contact:
2852 gajim.connections[account].blocked_contacts.append(
2853 contact.jid)
2854 self.draw_contact(contact.jid, account)
2855 else:
2856 for (contact, account) in list_:
2857 if account not in accounts:
2858 if not gajim.connections[account].\
2859 privacy_rules_supported:
2860 continue
2861 accounts.append(account)
2862 # needed for draw_group:
2863 gajim.connections[account].blocked_groups.append(group)
2864 self.draw_group(group, account)
2865 self.send_status(account, 'offline', msg, to=contact.jid)
2866 self.draw_contact(contact.jid, account)
2867 new_rule = {'order': u'1', 'type': u'group', 'action': u'deny',
2868 'value' : group, 'child': [u'message', u'iq',
2869 u'presence-out']}
2870 # account is the same for all when we block a group
2871 gajim.connections[list_[0][1]].blocked_list.append(new_rule)
2872 for account in accounts:
2873 connection = gajim.connections[account]
2874 connection.set_privacy_list('block', connection.blocked_list)
2875 if len(connection.blocked_list) == 1:
2876 connection.set_active_list('block')
2877 connection.set_default_list('block')
2878 connection.get_privacy_list('block')
2880 def _block_it(is_checked=None):
2881 if is_checked is not None: # dialog has been shown
2882 if is_checked: # user does not want to be asked again
2883 gajim.config.set('confirm_block', 'no')
2884 else:
2885 gajim.config.set('confirm_block', 'yes')
2886 self.get_status_message('offline', on_continue, show_pep=False)
2888 confirm_block = gajim.config.get('confirm_block')
2889 if confirm_block == 'no':
2890 _block_it()
2891 return
2892 pritext = _('You are about to block a contact. Are you sure you want'
2893 ' to continue?')
2894 sectext = _('This contact will see you offline and you will not '
2895 'receive messages he will send you.')
2896 dialogs.ConfirmationDialogCheck(pritext, sectext,
2897 _('_Do not ask me again'), on_response_ok=_block_it)
2899 def on_unblock(self, widget, list_, group=None):
2901 When clicked on the 'unblock' button in context menu.
2903 accounts = []
2904 if group is None:
2905 for (contact, account) in list_:
2906 if account not in accounts:
2907 if gajim.connections[account].privacy_rules_supported:
2908 accounts.append(account)
2909 gajim.connections[account].new_blocked_list = []
2910 gajim.connections[account].to_unblock = []
2911 gajim.connections[account].to_unblock.append(
2912 contact.jid)
2913 else:
2914 gajim.connections[account].to_unblock.append(contact.jid)
2915 # needed for draw_contact:
2916 if contact.jid in gajim.connections[account].blocked_contacts:
2917 gajim.connections[account].blocked_contacts.remove(
2918 contact.jid)
2919 self.draw_contact(contact.jid, account)
2920 for account in accounts:
2921 for rule in gajim.connections[account].blocked_list:
2922 if rule['action'] != 'deny' or rule['type'] != 'jid' \
2923 or rule['value'] not in \
2924 gajim.connections[account].to_unblock:
2925 gajim.connections[account].new_blocked_list.append(rule)
2926 else:
2927 for (contact, account) in list_:
2928 if account not in accounts:
2929 if gajim.connections[account].privacy_rules_supported:
2930 accounts.append(account)
2931 # needed for draw_group:
2932 if group in gajim.connections[account].blocked_groups:
2933 gajim.connections[account].blocked_groups.remove(
2934 group)
2935 self.draw_group(group, account)
2936 gajim.connections[account].new_blocked_list = []
2937 for rule in gajim.connections[account].blocked_list:
2938 if rule['action'] != 'deny' or \
2939 rule['type'] != 'group' or rule['value'] != group:
2940 gajim.connections[account].new_blocked_list.\
2941 append(rule)
2942 self.draw_contact(contact.jid, account)
2943 for account in accounts:
2944 gajim.connections[account].set_privacy_list('block',
2945 gajim.connections[account].new_blocked_list)
2946 gajim.connections[account].get_privacy_list('block')
2947 if len(gajim.connections[account].new_blocked_list) == 0:
2948 gajim.connections[account].blocked_list = []
2949 gajim.connections[account].blocked_contacts = []
2950 gajim.connections[account].blocked_groups = []
2951 gajim.connections[account].set_default_list('')
2952 gajim.connections[account].set_active_list('')
2953 gajim.connections[account].del_privacy_list('block')
2954 if 'privacy_list_block' in gajim.interface.instances[account]:
2955 del gajim.interface.instances[account]['privacy_list_block']
2956 for (contact, account) in list_:
2957 if not self.regroup:
2958 show = gajim.SHOW_LIST[gajim.connections[account].connected]
2959 else: # accounts merged
2960 show = helpers.get_global_show()
2961 if show == 'invisible':
2962 # Don't send our presence if we're invisible
2963 continue
2964 if account not in accounts:
2965 accounts.append(account)
2966 if gajim.connections[account].privacy_rules_supported:
2967 self.send_status(account, show,
2968 gajim.connections[account].status, to=contact.jid)
2969 else:
2970 self.send_status(account, show,
2971 gajim.connections[account].status, to=contact.jid)
2973 def on_rename(self, widget, row_type, jid, account):
2974 # this function is called either by F2 or by Rename menuitem
2975 if 'rename' in gajim.interface.instances:
2976 gajim.interface.instances['rename'].dialog.present()
2977 return
2979 # account is offline, don't allow to rename
2980 if gajim.connections[account].connected < 2:
2981 return
2982 if row_type in ('contact', 'agent'):
2983 # it's jid
2984 title = _('Rename Contact')
2985 message = _('Enter a new nickname for contact %s') % jid
2986 old_text = gajim.contacts.get_contact_with_highest_priority(account,
2987 jid).name
2988 elif row_type == 'group':
2989 if jid in helpers.special_groups + (_('General'),):
2990 return
2991 old_text = jid
2992 title = _('Rename Group')
2993 message = _('Enter a new name for group %s') % \
2994 gobject.markup_escape_text(jid)
2996 def on_renamed(new_text, account, row_type, jid, old_text):
2997 if 'rename' in gajim.interface.instances:
2998 del gajim.interface.instances['rename']
2999 if row_type in ('contact', 'agent'):
3000 if old_text == new_text:
3001 return
3002 contacts = gajim.contacts.get_contacts(account, jid)
3003 for contact in contacts:
3004 contact.name = new_text
3005 gajim.connections[account].update_contact(jid, new_text, \
3006 contacts[0].groups)
3007 self.draw_contact(jid, account)
3008 # Update opened chats
3009 for ctrl in gajim.interface.msg_win_mgr.get_controls(jid,
3010 account):
3011 ctrl.update_ui()
3012 win = gajim.interface.msg_win_mgr.get_window(jid, account)
3013 win.redraw_tab(ctrl)
3014 win.show_title()
3015 elif row_type == 'group':
3016 # in C_JID column, we hold the group name (which is not escaped)
3017 self.rename_group(old_text, new_text, account)
3019 def on_canceled():
3020 if 'rename' in gajim.interface.instances:
3021 del gajim.interface.instances['rename']
3023 gajim.interface.instances['rename'] = dialogs.InputDialog(title,
3024 message, old_text, False, (on_renamed, account, row_type, jid,
3025 old_text), on_canceled)
3027 def on_remove_group_item_activated(self, widget, group, account):
3028 def on_ok(checked):
3029 for contact in gajim.contacts.get_contacts_from_group(account,
3030 group):
3031 if not checked:
3032 self.remove_contact_from_groups(contact.jid, account,
3033 [group])
3034 else:
3035 gajim.connections[account].unsubscribe(contact.jid)
3036 self.remove_contact(contact.jid, account, backend=True)
3038 dialogs.ConfirmationDialogCheck(_('Remove Group'),
3039 _('Do you want to remove group %s from the roster?') % group,
3040 _('Also remove all contacts in this group from your roster'),
3041 on_response_ok=on_ok)
3043 def on_assign_pgp_key(self, widget, contact, account):
3044 attached_keys = gajim.config.get_per('accounts', account,
3045 'attached_gpg_keys').split()
3046 keys = {}
3047 keyID = _('None')
3048 for i in xrange(len(attached_keys)/2):
3049 keys[attached_keys[2*i]] = attached_keys[2*i+1]
3050 if attached_keys[2*i] == contact.jid:
3051 keyID = attached_keys[2*i+1]
3052 public_keys = gajim.connections[account].ask_gpg_keys()
3053 public_keys[_('None')] = _('None')
3055 def on_key_selected(keyID):
3056 if keyID is None:
3057 return
3058 if keyID[0] == _('None'):
3059 if contact.jid in keys:
3060 del keys[contact.jid]
3061 keyID = ''
3062 else:
3063 keyID = keyID[0]
3064 keys[contact.jid] = keyID
3066 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
3067 if ctrl:
3068 ctrl.update_ui()
3070 keys_str = ''
3071 for jid in keys:
3072 keys_str += jid + ' ' + keys[jid] + ' '
3073 gajim.config.set_per('accounts', account, 'attached_gpg_keys',
3074 keys_str)
3075 for u in gajim.contacts.get_contacts(account, contact.jid):
3076 u.keyID = helpers.prepare_and_validate_gpg_keyID(account,
3077 contact.jid, keyID)
3079 dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
3080 _('Select a key to apply to the contact'), public_keys,
3081 on_key_selected, selected=keyID)
3083 def on_set_custom_avatar_activate(self, widget, contact, account):
3084 def on_ok(widget, path_to_file):
3085 filesize = os.path.getsize(path_to_file) # in bytes
3086 invalid_file = False
3087 msg = ''
3088 if os.path.isfile(path_to_file):
3089 stat = os.stat(path_to_file)
3090 if stat[6] == 0:
3091 invalid_file = True
3092 msg = _('File is empty')
3093 else:
3094 invalid_file = True
3095 msg = _('File does not exist')
3096 if invalid_file:
3097 dialogs.ErrorDialog(_('Could not load image'), msg)
3098 return
3099 try:
3100 pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
3101 if filesize > 16384: # 16 kb
3102 # get the image at 'tooltip size'
3103 # and hope that user did not specify in ACE crazy size
3104 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip')
3105 except gobject.GError, msg: # unknown format
3106 # msg should be string, not object instance
3107 msg = str(msg)
3108 dialogs.ErrorDialog(_('Could not load image'), msg)
3109 return
3110 gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True)
3111 dlg.destroy()
3112 self.update_avatar_in_gui(contact.jid, account)
3114 def on_clear(widget):
3115 dlg.destroy()
3116 # Delete file:
3117 gajim.interface.remove_avatar_files(contact.jid, local=True)
3118 self.update_avatar_in_gui(contact.jid, account)
3120 dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok,
3121 on_response_clear=on_clear)
3123 def on_edit_groups(self, widget, list_):
3124 dialogs.EditGroupsDialog(list_)
3126 def on_history(self, widget, contact, account):
3128 When history menuitem is activated: call log window
3130 if 'logs' in gajim.interface.instances:
3131 gajim.interface.instances['logs'].window.present()
3132 gajim.interface.instances['logs'].open_history(contact.jid, account)
3133 else:
3134 gajim.interface.instances['logs'] = history_window.\
3135 HistoryWindow(contact.jid, account)
3137 def on_disconnect(self, widget, jid, account):
3139 When disconnect menuitem is activated: disconect from room
3141 if jid in gajim.interface.minimized_controls[account]:
3142 ctrl = gajim.interface.minimized_controls[account][jid]
3143 ctrl.shutdown()
3144 ctrl.got_disconnected()
3145 self.remove_groupchat(jid, account)
3147 def on_reconnect(self, widget, jid, account):
3149 When reconnect menuitem is activated: join the room
3151 if jid in gajim.interface.minimized_controls[account]:
3152 ctrl = gajim.interface.minimized_controls[account][jid]
3153 gajim.interface.join_gc_room(account, jid, ctrl.nick,
3154 gajim.gc_passwords.get(jid, ''))
3156 def on_send_single_message_menuitem_activate(self, widget, account,
3157 contact=None):
3158 if contact is None:
3159 dialogs.SingleMessageWindow(account, action='send')
3160 elif isinstance(contact, list):
3161 dialogs.SingleMessageWindow(account, contact, 'send')
3162 else:
3163 jid = contact.jid
3164 if contact.jid == gajim.get_jid_from_account(account):
3165 jid += '/' + contact.resource
3166 dialogs.SingleMessageWindow(account, jid, 'send')
3168 def on_send_file_menuitem_activate(self, widget, contact, account,
3169 resource=None):
3170 gajim.interface.instances['file_transfers'].show_file_send_request(
3171 account, contact)
3173 def on_add_special_notification_menuitem_activate(self, widget, jid):
3174 dialogs.AddSpecialNotificationDialog(jid)
3176 def on_invite_to_new_room(self, widget, list_, resource=None):
3178 Resource parameter MUST NOT be used if more than one contact in list
3180 account_list = []
3181 jid_list = []
3182 for (contact, account) in list_:
3183 if contact.jid not in jid_list:
3184 if resource: # we MUST have one contact only in list_
3185 fjid = contact.jid + '/' + resource
3186 jid_list.append(fjid)
3187 else:
3188 jid_list.append(contact.jid)
3189 if account not in account_list:
3190 account_list.append(account)
3191 # transform None in 'jabber'
3192 type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
3193 for account in account_list:
3194 if gajim.connections[account].muc_jid[type_]:
3195 # create the room on this muc server
3196 if 'join_gc' in gajim.interface.instances[account]:
3197 gajim.interface.instances[account]['join_gc'].window.\
3198 destroy()
3199 try:
3200 gajim.interface.instances[account]['join_gc'] = \
3201 dialogs.JoinGroupchatWindow(account,
3202 gajim.connections[account].muc_jid[type_],
3203 automatic = {'invities': jid_list})
3204 except GajimGeneralException:
3205 continue
3206 break
3208 def on_invite_to_room(self, widget, list_, room_jid, room_account,
3209 resource=None):
3211 Resource parameter MUST NOT be used if more than one contact in list
3213 for e in list_:
3214 contact = e[0]
3215 contact_jid = contact.jid
3216 if resource: # we MUST have one contact only in list_
3217 contact_jid += '/' + resource
3218 gajim.connections[room_account].send_invite(room_jid, contact_jid)
3220 def on_all_groupchat_maximized(self, widget, group_list):
3221 for (contact, account) in group_list:
3222 self.on_groupchat_maximized(widget, contact.jid, account)
3224 def on_groupchat_maximized(self, widget, jid, account):
3226 When a groupchat is maximized
3228 if not jid in gajim.interface.minimized_controls[account]:
3229 # Already opened?
3230 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
3231 account)
3232 if gc_control:
3233 mw = gajim.interface.msg_win_mgr.get_window(jid, account)
3234 mw.set_active_tab(gc_control)
3235 mw.window.window.focus(gtk.get_current_event_time())
3236 return
3237 ctrl = gajim.interface.minimized_controls[account][jid]
3238 mw = gajim.interface.msg_win_mgr.get_window(jid, account)
3239 if not mw:
3240 mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact,
3241 ctrl.account, ctrl.type_id)
3242 ctrl.parent_win = mw
3243 mw.new_tab(ctrl)
3244 mw.set_active_tab(ctrl)
3245 mw.window.window.focus(gtk.get_current_event_time())
3246 self.remove_groupchat(jid, account)
3248 def on_edit_account(self, widget, account):
3249 if 'accounts' in gajim.interface.instances:
3250 gajim.interface.instances['accounts'].window.present()
3251 else:
3252 gajim.interface.instances['accounts'] = config.AccountsWindow()
3253 gajim.interface.instances['accounts'].select_account(account)
3255 def on_zeroconf_properties(self, widget, account):
3256 if 'accounts' in gajim.interface.instances:
3257 gajim.interface.instances['accounts'].window.present()
3258 else:
3259 gajim.interface.instances['accounts'] = config.AccountsWindow()
3260 gajim.interface.instances['accounts'].select_account(account)
3262 def on_open_gmail_inbox(self, widget, account):
3263 url = gajim.connections[account].gmail_url
3264 if url:
3265 helpers.launch_browser_mailer('url', url)
3267 def on_change_status_message_activate(self, widget, account):
3268 show = gajim.SHOW_LIST[gajim.connections[account].connected]
3269 def on_response(message, pep_dict):
3270 if message is None: # None is if user pressed Cancel
3271 return
3272 self.send_status(account, show, message)
3273 self.send_pep(account, pep_dict)
3274 dialogs.ChangeStatusMessageDialog(on_response, show)
3276 def on_add_to_roster(self, widget, contact, account):
3277 dialogs.AddNewContactWindow(account, contact.jid, contact.name)
3280 def on_roster_treeview_scroll_event(self, widget, event):
3281 self.tooltip.hide_tooltip()
3283 def on_roster_treeview_key_press_event(self, widget, event):
3285 When a key is pressed in the treeviews
3287 self.tooltip.hide_tooltip()
3288 if event.keyval == gtk.keysyms.Escape:
3289 self.tree.get_selection().unselect_all()
3290 elif event.keyval == gtk.keysyms.F2:
3291 treeselection = self.tree.get_selection()
3292 model, list_of_paths = treeselection.get_selected_rows()
3293 if len(list_of_paths) != 1:
3294 return
3295 path = list_of_paths[0]
3296 type_ = model[path][C_TYPE]
3297 if type_ in ('contact', 'group', 'agent'):
3298 jid = model[path][C_JID].decode('utf-8')
3299 account = model[path][C_ACCOUNT].decode('utf-8')
3300 self.on_rename(widget, type_, jid, account)
3302 elif event.keyval == gtk.keysyms.Delete:
3303 treeselection = self.tree.get_selection()
3304 model, list_of_paths = treeselection.get_selected_rows()
3305 if not len(list_of_paths):
3306 return
3307 type_ = model[list_of_paths[0]][C_TYPE]
3308 account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8')
3309 if type_ in ('account', 'group', 'self_contact') or \
3310 account == gajim.ZEROCONF_ACC_NAME:
3311 return
3312 list_ = []
3313 for path in list_of_paths:
3314 if model[path][C_TYPE] != type_:
3315 return
3316 jid = model[path][C_JID].decode('utf-8')
3317 account = model[path][C_ACCOUNT].decode('utf-8')
3318 contact = gajim.contacts.get_contact_with_highest_priority(
3319 account, jid)
3320 list_.append((contact, account))
3321 if type_ == 'contact':
3322 self.on_req_usub(widget, list_)
3323 elif type_ == 'agent':
3324 self.on_remove_agent(widget, list_)
3326 def on_roster_treeview_button_release_event(self, widget, event):
3327 try:
3328 path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
3329 except TypeError:
3330 return False
3332 if event.button == 1: # Left click
3333 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3334 not event.state & gtk.gdk.CONTROL_MASK:
3335 # Check if button has been pressed on the same row
3336 if self.clicked_path == path:
3337 self.on_row_activated(widget, path)
3338 self.clicked_path = None
3340 def on_roster_treeview_button_press_event(self, widget, event):
3341 # hide tooltip, no matter the button is pressed
3342 self.tooltip.hide_tooltip()
3343 try:
3344 pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
3345 path, x = pos[0], pos[2]
3346 except TypeError:
3347 self.tree.get_selection().unselect_all()
3348 return False
3350 if event.button == 3: # Right click
3351 try:
3352 model, list_of_paths = self.tree.get_selection().\
3353 get_selected_rows()
3354 except TypeError:
3355 list_of_paths = []
3356 if path not in list_of_paths:
3357 self.tree.get_selection().unselect_all()
3358 self.tree.get_selection().select_path(path)
3359 return self.show_treeview_menu(event)
3361 elif event.button == 2: # Middle click
3362 try:
3363 model, list_of_paths = self.tree.get_selection().\
3364 get_selected_rows()
3365 except TypeError:
3366 list_of_paths = []
3367 if list_of_paths != [path]:
3368 self.tree.get_selection().unselect_all()
3369 self.tree.get_selection().select_path(path)
3370 type_ = model[path][C_TYPE]
3371 if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
3372 self.on_row_activated(widget, path)
3373 elif type_ == 'account':
3374 account = model[path][C_ACCOUNT].decode('utf-8')
3375 if account != 'all':
3376 show = gajim.connections[account].connected
3377 if show > 1: # We are connected
3378 self.on_change_status_message_activate(widget, account)
3379 return True
3380 show = helpers.get_global_show()
3381 if show == 'offline':
3382 return True
3383 def on_response(message, pep_dict):
3384 if message is None:
3385 return True
3386 for acct in gajim.connections:
3387 if not gajim.config.get_per('accounts', acct,
3388 'sync_with_global_status'):
3389 continue
3390 current_show = gajim.SHOW_LIST[gajim.connections[acct].\
3391 connected]
3392 self.send_status(acct, current_show, message)
3393 self.send_pep(acct, pep_dict)
3394 dialogs.ChangeStatusMessageDialog(on_response, show)
3395 return True
3397 elif event.button == 1: # Left click
3398 model = self.modelfilter
3399 type_ = model[path][C_TYPE]
3400 # x_min is the x start position of status icon column
3401 if gajim.config.get('avatar_position_in_roster') == 'left':
3402 x_min = gajim.config.get('roster_avatar_width')
3403 else:
3404 x_min = 0
3405 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3406 not event.state & gtk.gdk.CONTROL_MASK:
3407 # Don't handle double click if we press icon of a metacontact
3408 titer = model.get_iter(path)
3409 if x > x_min and x < x_min + 27 and type_ == 'contact' and \
3410 model.iter_has_child(titer):
3411 if (self.tree.row_expanded(path)):
3412 self.tree.collapse_row(path)
3413 else:
3414 self.tree.expand_row(path, False)
3415 return
3416 # We just save on which row we press button, and open chat
3417 # window on button release to be able to do DND without opening
3418 # chat window
3419 self.clicked_path = path
3420 return
3421 else:
3422 if type_ == 'group' and x < 27:
3423 # first cell in 1st column (the arrow SINGLE clicked)
3424 if (self.tree.row_expanded(path)):
3425 self.tree.collapse_row(path)
3426 else:
3427 self.expand_group_row(path)
3429 elif type_ == 'contact' and x > x_min and x < x_min + 27:
3430 if (self.tree.row_expanded(path)):
3431 self.tree.collapse_row(path)
3432 else:
3433 self.tree.expand_row(path, False)
3435 def expand_group_row(self, path):
3436 self.tree.expand_row(path, False)
3437 iter = self.modelfilter.get_iter(path)
3438 child_iter = self.modelfilter.iter_children(iter)
3439 while child_iter:
3440 type_ = self.modelfilter[child_iter][C_TYPE]
3441 account = self.modelfilter[child_iter][C_ACCOUNT]
3442 group = self.modelfilter[child_iter][C_JID]
3443 if type_ == 'group' and account + group not in self.collapsed_rows:
3444 self.expand_group_row(self.modelfilter.get_path(child_iter))
3445 child_iter = self.modelfilter.iter_next(child_iter)
3447 def on_req_usub(self, widget, list_):
3449 Remove a contact. list_ is a list of (contact, account) tuples
3451 def on_ok(is_checked, list_):
3452 remove_auth = True
3453 if len(list_) == 1:
3454 contact = list_[0][0]
3455 if contact.sub != 'to' and is_checked:
3456 remove_auth = False
3457 for (contact, account) in list_:
3458 if _('Not in Roster') not in contact.get_shown_groups():
3459 gajim.connections[account].unsubscribe(contact.jid,
3460 remove_auth)
3461 self.remove_contact(contact.jid, account, backend=True)
3462 if not remove_auth and contact.sub == 'both':
3463 contact.name = ''
3464 contact.groups = []
3465 contact.sub = 'from'
3466 # we can't see him, but have to set it manually in contact
3467 contact.show = 'offline'
3468 gajim.contacts.add_contact(account, contact)
3469 self.add_contact(contact.jid, account)
3470 def on_ok2(list_):
3471 on_ok(False, list_)
3473 if len(list_) == 1:
3474 contact = list_[0][0]
3475 pritext = _('Contact "%s" will be removed from your roster') % \
3476 contact.get_shown_name()
3477 sectext = _('You are about to remove "%(name)s" (%(jid)s) from '
3478 'your roster.\n') % {'name': contact.get_shown_name(),
3479 'jid': contact.jid}
3480 if contact.sub == 'to':
3481 dialogs.ConfirmationDialog(pritext, sectext + \
3482 _('By removing this contact you also remove authorization '
3483 'resulting in him or her always seeing you as offline.'),
3484 on_response_ok=(on_ok2, list_))
3485 elif _('Not in Roster') in contact.get_shown_groups():
3486 # Contact is not in roster
3487 dialogs.ConfirmationDialog(pritext, sectext + \
3488 _('Do you want to continue?'), on_response_ok=(on_ok2,
3489 list_))
3490 else:
3491 dialogs.ConfirmationDialogCheck(pritext, sectext + \
3492 _('By removing this contact you also by default remove '
3493 'authorization resulting in him or her always seeing you as'
3494 ' offline.'),
3495 _('I want this contact to know my status after removal'),
3496 on_response_ok=(on_ok, list_))
3497 else:
3498 # several contact to remove at the same time
3499 pritext = _('Contacts will be removed from your roster')
3500 jids = ''
3501 for (contact, account) in list_:
3502 jids += '\n ' + contact.get_shown_name() + ' (%s)' % \
3503 contact.jid + ','
3504 sectext = _('By removing these contacts:%s\nyou also remove '
3505 'authorization resulting in them always seeing you as '
3506 'offline.') % jids
3507 dialogs.ConfirmationDialog(pritext, sectext,
3508 on_response_ok=(on_ok2, list_))
3510 def on_send_custom_status(self, widget, contact_list, show, group=None):
3512 Send custom status
3514 # contact_list has only one element except if group != None
3515 def on_response(message, pep_dict):
3516 if message is None: # None if user pressed Cancel
3517 return
3518 account_list = []
3519 for (contact, account) in contact_list:
3520 if account not in account_list:
3521 account_list.append(account)
3522 # 1. update status_sent_to_[groups|users] list
3523 if group:
3524 for account in account_list:
3525 if account not in gajim.interface.status_sent_to_groups:
3526 gajim.interface.status_sent_to_groups[account] = {}
3527 gajim.interface.status_sent_to_groups[account][group] = show
3528 else:
3529 for (contact, account) in contact_list:
3530 if account not in gajim.interface.status_sent_to_users:
3531 gajim.interface.status_sent_to_users[account] = {}
3532 gajim.interface.status_sent_to_users[account][contact.jid] \
3533 = show
3535 # 2. update privacy lists if main status is invisible
3536 for account in account_list:
3537 if gajim.SHOW_LIST[gajim.connections[account].connected] == \
3538 'invisible':
3539 gajim.connections[account].set_invisible_rule()
3541 # 3. send directed presence
3542 for (contact, account) in contact_list:
3543 our_jid = gajim.get_jid_from_account(account)
3544 jid = contact.jid
3545 if jid == our_jid:
3546 jid += '/' + contact.resource
3547 self.send_status(account, show, message, to=jid)
3549 def send_it(is_checked=None):
3550 if is_checked is not None: # dialog has been shown
3551 if is_checked: # user does not want to be asked again
3552 gajim.config.set('confirm_custom_status', 'no')
3553 else:
3554 gajim.config.set('confirm_custom_status', 'yes')
3555 self.get_status_message(show, on_response, show_pep=False,
3556 always_ask=True)
3558 confirm_custom_status = gajim.config.get('confirm_custom_status')
3559 if confirm_custom_status == 'no':
3560 send_it()
3561 return
3562 pritext = _('You are about to send a custom status. Are you sure you '
3563 'want to continue?')
3564 sectext = _('This contact will temporarily see you as %(status)s, '
3565 'but only until you change your status. Then he or she will see '
3566 'your global status.') % {'status': show}
3567 dialogs.ConfirmationDialogCheck(pritext, sectext,
3568 _('_Do not ask me again'), on_response_ok=send_it)
3570 def on_status_combobox_changed(self, widget):
3572 When we change our status via the combobox
3574 model = self.status_combobox.get_model()
3575 active = self.status_combobox.get_active()
3576 if active == -1: # no active item
3577 return
3578 if not self.combobox_callback_active:
3579 self.previous_status_combobox_active = active
3580 return
3581 accounts = gajim.connections.keys()
3582 if len(accounts) == 0:
3583 dialogs.ErrorDialog(_('No account available'),
3584 _('You must create an account before you can chat with other '
3585 'contacts.'))
3586 self.update_status_combobox()
3587 return
3588 status = model[active][2].decode('utf-8')
3589 # status "desync'ed" or not
3590 statuses_unified = helpers.statuses_unified()
3591 if (active == 7 and statuses_unified) or (active == 9 and \
3592 not statuses_unified):
3593 # 'Change status message' selected:
3594 # do not change show, just show change status dialog
3595 status = model[self.previous_status_combobox_active][2].decode(
3596 'utf-8')
3597 def on_response(message, pep_dict):
3598 if message is not None: # None if user pressed Cancel
3599 for account in accounts:
3600 if not gajim.config.get_per('accounts', account,
3601 'sync_with_global_status'):
3602 continue
3603 current_show = gajim.SHOW_LIST[
3604 gajim.connections[account].connected]
3605 self.send_status(account, current_show, message)
3606 self.send_pep(account, pep_dict)
3607 self.combobox_callback_active = False
3608 self.status_combobox.set_active(
3609 self.previous_status_combobox_active)
3610 self.combobox_callback_active = True
3611 dialogs.ChangeStatusMessageDialog(on_response, status)
3612 return
3613 # we are about to change show, so save this new show so in case
3614 # after user chooses "Change status message" menuitem
3615 # we can return to this show
3616 self.previous_status_combobox_active = active
3617 connected_accounts = gajim.get_number_of_connected_accounts()
3619 def on_continue(message, pep_dict):
3620 if message is None:
3621 # user pressed Cancel to change status message dialog
3622 self.update_status_combobox()
3623 return
3624 global_sync_accounts = []
3625 for acct in accounts:
3626 if gajim.config.get_per('accounts', acct,
3627 'sync_with_global_status'):
3628 global_sync_accounts.append(acct)
3629 global_sync_connected_accounts = \
3630 gajim.get_number_of_connected_accounts(global_sync_accounts)
3631 for account in accounts:
3632 if not gajim.config.get_per('accounts', account,
3633 'sync_with_global_status'):
3634 continue
3635 # we are connected (so we wanna change show and status)
3636 # or no account is connected and we want to connect with new
3637 # show and status
3639 if not global_sync_connected_accounts > 0 or \
3640 gajim.connections[account].connected > 0:
3641 self.send_status(account, status, message)
3642 self.send_pep(account, pep_dict)
3643 self.update_status_combobox()
3645 if status == 'invisible':
3646 bug_user = False
3647 for account in accounts:
3648 if connected_accounts < 1 or gajim.account_is_connected(
3649 account):
3650 if not gajim.config.get_per('accounts', account,
3651 'sync_with_global_status'):
3652 continue
3653 # We're going to change our status to invisible
3654 if self.connected_rooms(account):
3655 bug_user = True
3656 break
3657 if bug_user:
3658 def on_ok():
3659 self.get_status_message(status, on_continue, show_pep=False)
3661 def on_cancel():
3662 self.update_status_combobox()
3664 dialogs.ConfirmationDialog(
3665 _('You are participating in one or more group chats'),
3666 _('Changing your status to invisible will result in '
3667 'disconnection from those group chats. Are you sure you '
3668 'want to go invisible?'), on_reponse_ok=on_ok,
3669 on_response_cancel=on_cancel)
3670 return
3672 self.get_status_message(status, on_continue)
3674 def on_preferences_menuitem_activate(self, widget):
3675 if 'preferences' in gajim.interface.instances:
3676 gajim.interface.instances['preferences'].window.present()
3677 else:
3678 gajim.interface.instances['preferences'] = config.PreferencesWindow(
3681 def on_plugins_menuitem_activate(self, widget):
3682 if gajim.interface.instances.has_key('plugins'):
3683 gajim.interface.instances['plugins'].window.present()
3684 else:
3685 gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow()
3687 def on_publish_tune_toggled(self, widget, account):
3688 active = widget.get_active()
3689 gajim.config.set_per('accounts', account, 'publish_tune', active)
3690 if active:
3691 gajim.interface.enable_music_listener()
3692 else:
3693 gajim.connections[account].retract_tune()
3694 # disable music listener only if no other account uses it
3695 for acc in gajim.connections:
3696 if gajim.config.get_per('accounts', acc, 'publish_tune'):
3697 break
3698 else:
3699 gajim.interface.disable_music_listener()
3701 helpers.update_optional_features(account)
3703 def on_publish_location_toggled(self, widget, account):
3704 active = widget.get_active()
3705 gajim.config.set_per('accounts', account, 'publish_location', active)
3706 if active:
3707 location_listener.enable()
3708 else:
3709 gajim.connections[account].retract_location()
3710 # disable music listener only if no other account uses it
3711 for acc in gajim.connections:
3712 if gajim.config.get_per('accounts', acc, 'publish_location'):
3713 break
3714 else:
3715 location_listener.disable()
3717 helpers.update_optional_features(account)
3719 def on_pep_services_menuitem_activate(self, widget, account):
3720 if 'pep_services' in gajim.interface.instances[account]:
3721 gajim.interface.instances[account]['pep_services'].window.present()
3722 else:
3723 gajim.interface.instances[account]['pep_services'] = \
3724 config.ManagePEPServicesWindow(account)
3726 def on_add_new_contact(self, widget, account):
3727 dialogs.AddNewContactWindow(account)
3729 def on_join_gc_activate(self, widget, account):
3731 When the join gc menuitem is clicked, show the join gc window
3733 invisible_show = gajim.SHOW_LIST.index('invisible')
3734 if gajim.connections[account].connected == invisible_show:
3735 dialogs.ErrorDialog(_('You cannot join a group chat while you are '
3736 'invisible'))
3737 return
3738 if 'join_gc' in gajim.interface.instances[account]:
3739 gajim.interface.instances[account]['join_gc'].window.present()
3740 else:
3741 try:
3742 gajim.interface.instances[account]['join_gc'] = \
3743 dialogs.JoinGroupchatWindow(account)
3744 except GajimGeneralException:
3745 pass
3747 def on_new_chat_menuitem_activate(self, widget, account):
3748 dialogs.NewChatDialog(account)
3750 def on_contents_menuitem_activate(self, widget):
3751 helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
3753 def on_faq_menuitem_activate(self, widget):
3754 helpers.launch_browser_mailer('url',
3755 'http://trac.gajim.org/wiki/GajimFaq')
3757 def on_features_menuitem_activate(self, widget):
3758 features_window.FeaturesWindow()
3760 def on_about_menuitem_activate(self, widget):
3761 dialogs.AboutDialog()
3763 def on_accounts_menuitem_activate(self, widget):
3764 if 'accounts' in gajim.interface.instances:
3765 gajim.interface.instances['accounts'].window.present()
3766 else:
3767 gajim.interface.instances['accounts'] = config.AccountsWindow()
3769 def on_file_transfers_menuitem_activate(self, widget):
3770 if gajim.interface.instances['file_transfers'].window.get_property(
3771 'visible'):
3772 gajim.interface.instances['file_transfers'].window.present()
3773 else:
3774 gajim.interface.instances['file_transfers'].window.show_all()
3776 def on_history_menuitem_activate(self, widget):
3777 if 'logs' in gajim.interface.instances:
3778 gajim.interface.instances['logs'].window.present()
3779 else:
3780 gajim.interface.instances['logs'] = history_window.\
3781 HistoryWindow()
3783 def on_show_transports_menuitem_activate(self, widget):
3784 gajim.config.set('show_transports_group', widget.get_active())
3785 self.refilter_shown_roster_items()
3787 def on_manage_bookmarks_menuitem_activate(self, widget):
3788 config.ManageBookmarksWindow()
3790 def on_profile_avatar_menuitem_activate(self, widget, account):
3791 gajim.interface.edit_own_details(account)
3793 def on_execute_command(self, widget, contact, account, resource=None):
3795 Execute command. Full JID needed; if it is other contact, resource is
3796 necessary. Widget is unnecessary, only to be able to make this a
3797 callback
3799 jid = contact.jid
3800 if resource is not None:
3801 jid = jid + u'/' + resource
3802 adhoc_commands.CommandWindow(account, jid)
3804 def on_roster_window_focus_in_event(self, widget, event):
3805 # roster received focus, so if we had urgency REMOVE IT
3806 # NOTE: we do not have to read the message to remove urgency
3807 # so this functions does that
3808 gtkgui_helpers.set_unset_urgency_hint(widget, False)
3810 # if a contact row is selected, update colors (eg. for status msg)
3811 # because gtk engines may differ in bg when window is selected
3812 # or not
3813 if len(self._last_selected_contact):
3814 for (jid, account) in self._last_selected_contact:
3815 self.draw_contact(jid, account, selected=True, focus=True)
3817 def on_roster_window_focus_out_event(self, widget, event):
3818 # if a contact row is selected, update colors (eg. for status msg)
3819 # because gtk engines may differ in bg when window is selected
3820 # or not
3821 if len(self._last_selected_contact):
3822 for (jid, account) in self._last_selected_contact:
3823 self.draw_contact(jid, account, selected=True, focus=False)
3825 def on_roster_window_key_press_event(self, widget, event):
3826 if event.keyval == gtk.keysyms.Escape:
3827 if gajim.interface.msg_win_mgr.mode == \
3828 MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
3829 gajim.interface.msg_win_mgr.one_window_opened():
3830 # let message window close the tab
3831 return
3832 list_of_paths = self.tree.get_selection().get_selected_rows()[1]
3833 if not len(list_of_paths) and gajim.interface.systray_enabled and \
3834 not gajim.config.get('quit_on_roster_x_button'):
3835 self.tooltip.hide_tooltip()
3836 self.window.hide()
3837 elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \
3838 gtk.keysyms.i:
3839 treeselection = self.tree.get_selection()
3840 model, list_of_paths = treeselection.get_selected_rows()
3841 for path in list_of_paths:
3842 type_ = model[path][C_TYPE]
3843 if type_ in ('contact', 'agent'):
3844 jid = model[path][C_JID].decode('utf-8')
3845 account = model[path][C_ACCOUNT].decode('utf-8')
3846 contact = gajim.contacts.get_first_contact_from_jid(account,
3847 jid)
3848 self.on_info(widget, contact, account)
3849 elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \
3850 gtk.keysyms.h:
3851 treeselection = self.tree.get_selection()
3852 model, list_of_paths = treeselection.get_selected_rows()
3853 if len(list_of_paths) != 1:
3854 return
3855 path = list_of_paths[0]
3856 type_ = model[path][C_TYPE]
3857 if type_ in ('contact', 'agent'):
3858 jid = model[path][C_JID].decode('utf-8')
3859 account = model[path][C_ACCOUNT].decode('utf-8')
3860 contact = gajim.contacts.get_first_contact_from_jid(account,
3861 jid)
3862 self.on_history(widget, contact, account)
3864 def on_roster_window_popup_menu(self, widget):
3865 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
3866 self.show_treeview_menu(event)
3868 def on_row_activated(self, widget, path):
3870 When an iter is activated (double-click or single click if gnome is set
3871 this way)
3873 model = self.modelfilter
3874 account = model[path][C_ACCOUNT].decode('utf-8')
3875 type_ = model[path][C_TYPE]
3876 if type_ in ('group', 'account'):
3877 if self.tree.row_expanded(path):
3878 self.tree.collapse_row(path)
3879 else:
3880 self.tree.expand_row(path, False)
3881 return
3882 jid = model[path][C_JID].decode('utf-8')
3883 resource = None
3884 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
3885 titer = model.get_iter(path)
3886 if contact.is_groupchat():
3887 first_ev = gajim.events.get_first_event(account, jid)
3888 if first_ev and self.open_event(account, jid, first_ev):
3889 # We are invited to a GC
3890 # open event cares about connecting to it
3891 self.remove_groupchat(jid, account)
3892 else:
3893 self.on_groupchat_maximized(None, jid, account)
3894 return
3896 # else
3897 first_ev = gajim.events.get_first_event(account, jid)
3898 if not first_ev:
3899 # look in other resources
3900 for c in gajim.contacts.get_contacts(account, jid):
3901 fjid = c.get_full_jid()
3902 first_ev = gajim.events.get_first_event(account, fjid)
3903 if first_ev:
3904 resource = c.resource
3905 break
3906 if not first_ev and model.iter_has_child(titer):
3907 child_iter = model.iter_children(titer)
3908 while not first_ev and child_iter:
3909 child_jid = model[child_iter][C_JID].decode('utf-8')
3910 first_ev = gajim.events.get_first_event(account, child_jid)
3911 if first_ev:
3912 jid = child_jid
3913 else:
3914 child_iter = model.iter_next(child_iter)
3915 session = None
3916 if first_ev:
3917 if first_ev.type_ in ('chat', 'normal'):
3918 session = first_ev.parameters[8]
3919 fjid = jid
3920 if resource:
3921 fjid += '/' + resource
3922 if self.open_event(account, fjid, first_ev):
3923 return
3924 # else
3925 contact = gajim.contacts.get_contact(account, jid, resource)
3926 if not contact or isinstance(contact, list):
3927 contact = gajim.contacts.get_contact_with_highest_priority(account,
3928 jid)
3929 if jid == gajim.get_jid_from_account(account):
3930 resource = contact.resource
3932 gajim.interface.on_open_chat_window(None, contact, account, \
3933 resource=resource, session=session)
3935 def on_roster_treeview_row_activated(self, widget, path, col=0):
3937 When an iter is double clicked: open the first event window
3939 if not gajim.single_click:
3940 self.on_row_activated(widget, path)
3942 def on_roster_treeview_row_expanded(self, widget, titer, path):
3944 When a row is expanded change the icon of the arrow
3946 self._toggeling_row = True
3947 model = widget.get_model()
3948 child_model = model.get_model()
3949 child_iter = model.convert_iter_to_child_iter(titer)
3951 if self.regroup: # merged accounts
3952 accounts = gajim.connections.keys()
3953 else:
3954 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
3956 type_ = model[titer][C_TYPE]
3957 if type_ == 'group':
3958 group = model[titer][C_JID].decode('utf-8')
3959 child_model[child_iter][C_IMG] = \
3960 gajim.interface.jabber_state_images['16']['opened']
3961 for account in accounts:
3962 if group in gajim.groups[account]: # This account has this group
3963 gajim.groups[account][group]['expand'] = True
3964 if account + group in self.collapsed_rows:
3965 self.collapsed_rows.remove(account + group)
3966 for contact in gajim.contacts.iter_contacts(account):
3967 jid = contact.jid
3968 if group in contact.groups and \
3969 gajim.contacts.is_big_brother(account, jid, accounts) and \
3970 account + group + jid not in self.collapsed_rows:
3971 titers = self._get_contact_iter(jid, account)
3972 for titer in titers:
3973 path = model.get_path(titer)
3974 self.tree.expand_row(path, False)
3975 elif type_ == 'account':
3976 account = accounts[0] # There is only one cause we don't use merge
3977 if account in self.collapsed_rows:
3978 self.collapsed_rows.remove(account)
3979 self.draw_account(account)
3980 # When we expand, groups are collapsed. Restore expand state
3981 for group in gajim.groups[account]:
3982 if gajim.groups[account][group]['expand']:
3983 titer = self._get_group_iter(group, account)
3984 if titer:
3985 path = model.get_path(titer)
3986 self.tree.expand_row(path, False)
3987 elif type_ == 'contact':
3988 # Metacontact got toggled, update icon
3989 jid = model[titer][C_JID].decode('utf-8')
3990 account = model[titer][C_ACCOUNT].decode('utf-8')
3991 contact = gajim.contacts.get_contact(account, jid)
3992 for group in contact.groups:
3993 if account + group + jid in self.collapsed_rows:
3994 self.collapsed_rows.remove(account + group + jid)
3995 family = gajim.contacts.get_metacontacts_family(account, jid)
3996 nearby_family = \
3997 self._get_nearby_family_and_big_brother(family, account)[0]
3998 # Redraw all brothers to show pending events
3999 for data in nearby_family:
4000 self.draw_contact(data['jid'], data['account'])
4002 self._toggeling_row = False
4004 def on_roster_treeview_row_collapsed(self, widget, titer, path):
4006 When a row is collapsed change the icon of the arrow
4008 self._toggeling_row = True
4009 model = widget.get_model()
4010 child_model = model.get_model()
4011 child_iter = model.convert_iter_to_child_iter(titer)
4013 if self.regroup: # merged accounts
4014 accounts = gajim.connections.keys()
4015 else:
4016 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
4018 type_ = model[titer][C_TYPE]
4019 if type_ == 'group':
4020 child_model[child_iter][C_IMG] = gajim.interface.\
4021 jabber_state_images['16']['closed']
4022 group = model[titer][C_JID].decode('utf-8')
4023 for account in accounts:
4024 if group in gajim.groups[account]: # This account has this group
4025 gajim.groups[account][group]['expand'] = False
4026 if account + group not in self.collapsed_rows:
4027 self.collapsed_rows.append(account + group)
4028 elif type_ == 'account':
4029 account = accounts[0] # There is only one cause we don't use merge
4030 if account not in self.collapsed_rows:
4031 self.collapsed_rows.append(account)
4032 self.draw_account(account)
4033 elif type_ == 'contact':
4034 # Metacontact got toggled, update icon
4035 jid = model[titer][C_JID].decode('utf-8')
4036 account = model[titer][C_ACCOUNT].decode('utf-8')
4037 contact = gajim.contacts.get_contact(account, jid)
4038 for group in contact.groups:
4039 if account + group + jid not in self.collapsed_rows:
4040 self.collapsed_rows.append(account + group + jid)
4041 family = gajim.contacts.get_metacontacts_family(account, jid)
4042 nearby_family = \
4043 self._get_nearby_family_and_big_brother(family, account)[0]
4044 # Redraw all brothers to show pending events
4045 for data in nearby_family:
4046 self.draw_contact(data['jid'], data['account'])
4048 self._toggeling_row = False
4050 def on_modelfilter_row_has_child_toggled(self, model, path, titer):
4052 Called when a row has gotten the first or lost its last child row
4054 Expand Parent if necessary.
4056 if self._toggeling_row:
4057 # Signal is emitted when we write to our model
4058 return
4060 type_ = model[titer][C_TYPE]
4061 account = model[titer][C_ACCOUNT]
4062 if not account:
4063 return
4065 account = account.decode('utf-8')
4067 if type_ == 'contact':
4068 child_iter = model.convert_iter_to_child_iter(titer)
4069 if self.model.iter_has_child(child_iter):
4070 # we are a bigbrother metacontact
4071 # redraw us to show/hide expand icon
4072 if self.filtering:
4073 # Prevent endless loops
4074 jid = model[titer][C_JID].decode('utf-8')
4075 gobject.idle_add(self.draw_contact, jid, account)
4076 elif type_ == 'group':
4077 group = model[titer][C_JID].decode('utf-8')
4078 self._adjust_group_expand_collapse_state(group, account)
4079 elif type_ == 'account':
4080 self._adjust_account_expand_collapse_state(account)
4082 # Selection can change when the model is filtered
4083 # Only write to the model when filtering is finished!
4085 # FIXME: When we are filtering our custom colors are somehow lost
4087 # def on_treeview_selection_changed(self, selection):
4088 # '''Called when selection in TreeView has changed.
4090 # Redraw unselected rows to make status message readable
4091 # on all possible backgrounds.
4092 # '''
4093 # model, list_of_paths = selection.get_selected_rows()
4094 # if len(self._last_selected_contact):
4095 # # update unselected rows
4096 # for (jid, account) in self._last_selected_contact:
4097 # gobject.idle_add(self.draw_contact, jid,
4098 # account)
4099 # self._last_selected_contact = []
4100 # if len(list_of_paths) == 0:
4101 # return
4102 # for path in list_of_paths:
4103 # row = model[path]
4104 # if row[C_TYPE] != 'contact':
4105 # self._last_selected_contact = []
4106 # return
4107 # jid = row[C_JID].decode('utf-8')
4108 # account = row[C_ACCOUNT].decode('utf-8')
4109 # self._last_selected_contact.append((jid, account))
4110 # gobject.idle_add(self.draw_contact, jid, account, True)
4112 def on_service_disco_menuitem_activate(self, widget, account):
4113 server_jid = gajim.config.get_per('accounts', account, 'hostname')
4114 if server_jid in gajim.interface.instances[account]['disco']:
4115 gajim.interface.instances[account]['disco'][server_jid].\
4116 window.present()
4117 else:
4118 try:
4119 # Object will add itself to the window dict
4120 disco.ServiceDiscoveryWindow(account, address_entry=True)
4121 except GajimGeneralException:
4122 pass
4124 def on_show_offline_contacts_menuitem_activate(self, widget):
4126 When show offline option is changed: redraw the treeview
4128 gajim.config.set('showoffline', not gajim.config.get('showoffline'))
4129 self.refilter_shown_roster_items()
4130 w = self.xml.get_object('show_only_active_contacts_menuitem')
4131 if gajim.config.get('showoffline'):
4132 # We need to filter twice to show groups with no contacts inside
4133 # in the correct expand state
4134 self.refilter_shown_roster_items()
4135 w.set_sensitive(False)
4136 else:
4137 w.set_sensitive(True)
4139 def on_show_only_active_contacts_menuitem_activate(self, widget):
4141 When show only active contact option is changed: redraw the treeview
4143 gajim.config.set('show_only_chat_and_online', not gajim.config.get(
4144 'show_only_chat_and_online'))
4145 self.refilter_shown_roster_items()
4146 w = self.xml.get_object('show_offline_contacts_menuitem')
4147 if gajim.config.get('show_only_chat_and_online'):
4148 # We need to filter twice to show groups with no contacts inside
4149 # in the correct expand state
4150 self.refilter_shown_roster_items()
4151 w.set_sensitive(False)
4152 else:
4153 w.set_sensitive(True)
4155 def on_view_menu_activate(self, widget):
4156 # Hide the show roster menu if we are not in the right windowing mode.
4157 if self.hpaned.get_child2() is not None:
4158 self.xml.get_object('show_roster_menuitem').show()
4159 else:
4160 self.xml.get_object('show_roster_menuitem').hide()
4162 def on_show_roster_menuitem_toggled(self, widget):
4163 # when num controls is 0 this menuitem is hidden, but still need to
4164 # disable keybinding
4165 if self.hpaned.get_child2() is not None:
4166 self.show_roster_vbox(widget.get_active())
4168 def on_roster_hpaned_notify(self, pane, gparamspec):
4170 Keep changing the width of the roster
4171 (when a gtk.Paned widget handle is dragged)
4173 if gparamspec.name == 'position':
4174 roster_width = pane.get_child1().allocation.width
4175 gajim.config.set('roster_width', roster_width)
4177 ################################################################################
4178 ### Drag and Drop handling
4179 ################################################################################
4181 def drag_data_get_data(self, treeview, context, selection, target_id,
4182 etime):
4183 model, list_of_paths = self.tree.get_selection().get_selected_rows()
4184 if len(list_of_paths) != 1:
4185 return
4186 path = list_of_paths[0]
4187 data = ''
4188 if len(path) >= 2:
4189 data = model[path][C_JID]
4190 selection.set(selection.target, 8, data)
4192 def drag_begin(self, treeview, context):
4193 self.dragging = True
4195 def drag_end(self, treeview, context):
4196 self.dragging = False
4198 def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
4199 c_dest, was_big_brother, context, etime):
4200 gajim.connections[account_dest].send_contacts([c_source], c_dest.jid)
4202 def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
4203 c_dest, was_big_brother, context, etime):
4205 if not gajim.connections[account_source].private_storage_supported or \
4206 not gajim.connections[account_dest].private_storage_supported:
4207 dialogs.WarningDialog(_('Metacontacts storage not supported by '
4208 'your server'),
4209 _('Your server does not support storing metacontacts '
4210 'information. So those information will not be saved on next '
4211 'reconnection.'))
4213 def merge_contacts(is_checked=None):
4214 contacts = 0
4215 if is_checked is not None: # dialog has been shown
4216 if is_checked: # user does not want to be asked again
4217 gajim.config.set('confirm_metacontacts', 'no')
4218 else:
4219 gajim.config.set('confirm_metacontacts', 'yes')
4221 # We might have dropped on a metacontact.
4222 # Remove it and readd later with updated family info
4223 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
4224 c_dest.jid)
4225 if dest_family:
4226 self._remove_metacontact_family(dest_family, account_dest)
4227 source_family = gajim.contacts.get_metacontacts_family(
4228 account_source, c_source.jid)
4229 if dest_family == source_family:
4230 n = contacts = len(dest_family)
4231 for tag in source_family:
4232 if tag['jid'] == c_source.jid:
4233 tag['order'] = contacts
4234 continue
4235 if 'order' in tag:
4236 n -= 1
4237 tag['order'] = n
4238 else:
4239 self._remove_entity(c_dest, account_dest)
4241 old_family = gajim.contacts.get_metacontacts_family(account_source,
4242 c_source.jid)
4243 old_groups = c_source.groups
4245 # Remove old source contact(s)
4246 if was_big_brother:
4247 # We have got little brothers. Readd them all
4248 self._remove_metacontact_family(old_family, account_source)
4249 else:
4250 # We are only a litle brother. Simply remove us from our big
4251 # brother
4252 if self._get_contact_iter(c_source.jid, account_source):
4253 # When we have been in the group before.
4254 # Do not try to remove us again
4255 self._remove_entity(c_source, account_source)
4257 own_data = {}
4258 own_data['jid'] = c_source.jid
4259 own_data['account'] = account_source
4260 # Don't touch the rest of the family
4261 old_family = [own_data]
4263 # Apply new tag and update contact
4264 for data in old_family:
4265 if account_source != data['account'] and not self.regroup:
4266 continue
4268 _account = data['account']
4269 _jid = data['jid']
4270 _contact = gajim.contacts.get_first_contact_from_jid(_account,
4271 _jid)
4272 if not _contact:
4273 # One of the metacontacts may be not connected.
4274 continue
4276 _contact.groups = c_dest.groups[:]
4277 gajim.contacts.add_metacontact(account_dest, c_dest.jid,
4278 _account, _contact.jid, contacts)
4279 gajim.connections[account_source].update_contact(_contact.jid,
4280 _contact.name, _contact.groups)
4282 # Re-add all and update GUI
4283 new_family = gajim.contacts.get_metacontacts_family(account_source,
4284 c_source.jid)
4285 brothers = self._add_metacontact_family(new_family, account_source)
4287 for c, acc in brothers:
4288 self.draw_completely(c.jid, acc)
4290 old_groups.extend(c_dest.groups)
4291 for g in old_groups:
4292 self.draw_group(g, account_source)
4294 self.draw_account(account_source)
4295 context.finish(True, True, etime)
4297 confirm_metacontacts = gajim.config.get('confirm_metacontacts')
4298 if confirm_metacontacts == 'no':
4299 merge_contacts()
4300 return
4301 pritext = _('You are about to create a metacontact. Are you sure you '
4302 'want to continue?')
4303 sectext = _('Metacontacts are a way to regroup several contacts in one '
4304 'line. Generally it is used when the same person has several '
4305 'Jabber accounts or transport accounts.')
4306 dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
4307 _('_Do not ask me again'), on_response_ok=merge_contacts)
4308 if not confirm_metacontacts: # First time we see this window
4309 dlg.checkbutton.set_active(True)
4311 def on_drop_in_group(self, widget, account, c_source, grp_dest,
4312 is_big_brother, context, etime, grp_source = None):
4313 if is_big_brother:
4314 # add whole metacontact to new group
4315 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4316 # remove afterwards so the contact is not moved to General in the
4317 # meantime
4318 if grp_dest != grp_source:
4319 self.remove_contact_from_groups(c_source.jid, account,
4320 [grp_source])
4321 else:
4322 # Normal contact or little brother
4323 family = gajim.contacts.get_metacontacts_family(account,
4324 c_source.jid)
4325 if family:
4326 # Little brother
4327 # Remove whole family. Remove us from the family.
4328 # Then re-add other family members.
4329 self._remove_metacontact_family(family, account)
4330 gajim.contacts.remove_metacontact(account, c_source.jid)
4331 for data in family:
4332 if account != data['account'] and not self.regroup:
4333 continue
4334 if data['jid'] == c_source.jid and\
4335 data['account'] == account:
4336 continue
4337 self.add_contact(data['jid'], data['account'])
4338 break
4340 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4342 else:
4343 # Normal contact
4344 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4345 # remove afterwards so the contact is not moved to General in
4346 # the meantime
4347 if grp_dest != grp_source:
4348 self.remove_contact_from_groups(c_source.jid, account,
4349 [grp_source])
4351 if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY):
4352 context.finish(True, True, etime)
4354 def drag_drop(self, treeview, context, x, y, timestamp):
4355 target_list = treeview.drag_dest_get_target_list()
4356 target = treeview.drag_dest_find_target(context, target_list)
4357 treeview.drag_get_data(context, target)
4358 context.finish(False, True)
4359 return True
4361 def move_group(self, old_name, new_name, account):
4362 for group in gajim.groups[account].keys():
4363 if group.startswith(old_name):
4364 self.rename_group(group, group.replace(old_name, new_name),
4365 account)
4367 def drag_data_received_data(self, treeview, context, x, y, selection, info,
4368 etime):
4369 treeview.stop_emission('drag_data_received')
4370 drop_info = treeview.get_dest_row_at_pos(x, y)
4371 if not drop_info:
4372 return
4373 if not selection.data:
4374 return # prevents tb when several entrys are dragged
4375 model = treeview.get_model()
4376 data = selection.data
4377 path_dest, position = drop_info
4379 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \
4380 and path_dest[1] == 0: # dropped before the first group
4381 return
4382 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
4383 # dropped before a group: we drop it in the previous group every
4384 # time
4385 path_dest = (path_dest[0], path_dest[1]-1)
4386 # destination: the row something got dropped on
4387 iter_dest = model.get_iter(path_dest)
4388 type_dest = model[iter_dest][C_TYPE].decode('utf-8')
4389 jid_dest = model[iter_dest][C_JID].decode('utf-8')
4390 account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
4392 # drop on account row in merged mode, we cannot know the desired account
4393 if account_dest == 'all':
4394 return
4395 # nothing can be done, if destination account is offline
4396 if gajim.connections[account_dest].connected < 2:
4397 return
4399 # A file got dropped on the roster
4400 if info == self.TARGET_TYPE_URI_LIST:
4401 if len(path_dest) < 3:
4402 return
4403 if type_dest != 'contact':
4404 return
4405 c_dest = gajim.contacts.get_contact_with_highest_priority(
4406 account_dest, jid_dest)
4407 if not c_dest.supports(NS_FILE):
4408 return
4409 uri = data.strip()
4410 uri_splitted = uri.split() # we may have more than one file dropped
4411 try:
4412 # This is always the last element in windows
4413 uri_splitted.remove('\0')
4414 except ValueError:
4415 pass
4416 nb_uri = len(uri_splitted)
4417 # Check the URIs
4418 bad_uris = []
4419 for a_uri in uri_splitted:
4420 path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
4421 if not os.path.isfile(path):
4422 bad_uris.append(a_uri)
4423 if len(bad_uris):
4424 dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
4425 return
4426 def _on_send_files(account, jid, uris):
4427 c = gajim.contacts.get_contact_with_highest_priority(account,
4428 jid)
4429 for uri in uris:
4430 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
4431 if os.path.isfile(path): # is it file?
4432 gajim.interface.instances['file_transfers'].send_file(
4433 account, c, path)
4434 # Popup dialog to confirm sending
4435 prim_text = 'Send file?'
4436 sec_text = i18n.ngettext('Do you want to send this file to %s:',
4437 'Do you want to send these files to %s:', nb_uri) %\
4438 c_dest.get_shown_name()
4439 for uri in uri_splitted:
4440 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
4441 sec_text += '\n' + os.path.basename(path)
4442 dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
4443 on_response_ok=(_on_send_files, account_dest, jid_dest,
4444 uri_splitted))
4445 dialog.popup()
4446 return
4448 # a roster entry was dragged and dropped somewhere in the roster
4450 # source: the row that was dragged
4451 path_source = treeview.get_selection().get_selected_rows()[1][0]
4452 iter_source = model.get_iter(path_source)
4453 type_source = model[iter_source][C_TYPE]
4454 account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
4456 if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
4457 return
4459 if type_dest == 'self_contact':
4460 # drop on self contact row
4461 return
4463 if type_dest == 'groupchat':
4464 # drop on a minimized groupchat
4465 # TODO: Invite to groupchat if type_dest = contact
4466 return
4468 if type_source == 'group':
4469 if account_source != account_dest:
4470 # drop on another account
4471 return
4472 grp_source = model[iter_source][C_JID].decode('utf-8')
4473 delimiter = gajim.connections[account_source].nested_group_delimiter
4474 grp_source_list = grp_source.split(delimiter)
4475 new_grp = None
4476 if type_dest == 'account':
4477 new_grp = grp_source_list[-1]
4478 elif type_dest == 'group':
4479 new_grp = model[iter_dest][C_JID].decode('utf-8') + delimiter +\
4480 grp_source_list[-1]
4481 if new_grp:
4482 self.move_group(grp_source, new_grp, account_source)
4484 # Only normal contacts and group can be dragged
4485 if type_source != 'contact':
4486 return
4488 # A contact was dropped
4489 if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
4490 # drop on zeroconf account, adding not possible
4491 return
4493 if type_dest == 'account' and account_source == account_dest:
4494 # drop on the account it was dragged from
4495 return
4497 # Get valid source group, jid and contact
4498 it = iter_source
4499 while model[it][C_TYPE] == 'contact':
4500 it = model.iter_parent(it)
4501 grp_source = model[it][C_JID].decode('utf-8')
4502 if grp_source in helpers.special_groups and \
4503 grp_source not in ('Not in Roster', 'Observers'):
4504 # a transport or a minimized groupchat was dragged
4505 # we can add it to other accounts but not move it to another group,
4506 # see below
4507 return
4508 jid_source = data.decode('utf-8')
4509 c_source = gajim.contacts.get_contact_with_highest_priority(
4510 account_source, jid_source)
4512 # Get destination group
4513 grp_dest = None
4514 if type_dest == 'group':
4515 grp_dest = model[iter_dest][C_JID].decode('utf-8')
4516 elif type_dest in ('contact', 'agent'):
4517 it = iter_dest
4518 while model[it][C_TYPE] != 'group':
4519 it = model.iter_parent(it)
4520 grp_dest = model[it][C_JID].decode('utf-8')
4521 if grp_dest in helpers.special_groups:
4522 return
4524 if jid_source == jid_dest:
4525 if grp_source == grp_dest and account_source == account_dest:
4526 # Drop on self
4527 return
4529 # contact drop somewhere in or on a foreign account
4530 if (type_dest == 'account' or not self.regroup) and \
4531 account_source != account_dest:
4532 # add to account in specified group
4533 dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
4534 user_nick=c_source.name, group=grp_dest)
4535 return
4537 # we may not add contacts from special_groups
4538 if grp_source in helpers.special_groups :
4539 return
4541 # Is the contact we drag a meta contact?
4542 accounts = (self.regroup and gajim.contacts.get_accounts()) or \
4543 account_source
4544 is_big_brother = gajim.contacts.is_big_brother(account_source,
4545 jid_source, accounts)
4547 drop_in_middle_of_meta = False
4548 if type_dest == 'contact':
4549 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4:
4550 drop_in_middle_of_meta = True
4551 if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or\
4552 self.modelfilter.iter_has_child(iter_dest)):
4553 drop_in_middle_of_meta = True
4554 # Contact drop on group row or between two contacts that are
4555 # not metacontacts
4556 if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE,
4557 gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta:
4558 self.on_drop_in_group(None, account_source, c_source, grp_dest,
4559 is_big_brother, context, etime, grp_source)
4560 return
4562 # Contact drop on another contact, make meta contacts
4563 if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \
4564 position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta:
4565 c_dest = gajim.contacts.get_contact_with_highest_priority(
4566 account_dest, jid_dest)
4567 if not c_dest:
4568 # c_dest is None if jid_dest doesn't belong to account
4569 return
4570 menu = gtk.Menu()
4571 item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(),
4572 c_dest.get_shown_name()))
4573 item.connect('activate', self.on_drop_rosterx, account_source,
4574 c_source, account_dest, c_dest, is_big_brother, context, etime)
4575 menu.append(item)
4577 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
4578 c_dest.jid)
4579 source_family = gajim.contacts.get_metacontacts_family(
4580 account_source, c_source.jid)
4581 if dest_family == source_family:
4582 item = gtk.MenuItem(_('Make %s first contact') % (
4583 c_source.get_shown_name()))
4584 else:
4585 item = gtk.MenuItem(_('Make %s and %s metacontacts') % (
4586 c_source.get_shown_name(), c_dest.get_shown_name()))
4588 item.connect('activate', self.on_drop_in_contact, account_source,
4589 c_source, account_dest, c_dest, is_big_brother, context, etime)
4591 menu.append(item)
4593 menu.attach_to_widget(self.tree, None)
4594 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
4595 menu.show_all()
4596 menu.popup(None, None, None, 1, etime)
4598 ################################################################################
4599 ### Everything about images and icons....
4600 ### Cleanup assigned to Jim++ :-)
4601 ################################################################################
4603 def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
4605 Check jid and return the appropriate state images dict for the demanded
4606 size. icon_name is taken into account when jid is from transport:
4607 transport iconset doesn't contain all icons, so we fall back to jabber
4610 transport = gajim.get_transport_name_from_jid(jid)
4611 if transport and size in self.transports_state_images:
4612 if transport not in self.transports_state_images[size]:
4613 # we don't have iconset for this transport loaded yet. Let's do
4614 # it
4615 self.make_transport_state_images(transport)
4616 if transport in self.transports_state_images[size] and \
4617 icon_name in self.transports_state_images[size][transport]:
4618 return self.transports_state_images[size][transport]
4619 return gajim.interface.jabber_state_images[size]
4621 def make_transport_state_images(self, transport):
4623 Initialize opened and closed 'transport' iconset dict
4625 if gajim.config.get('use_transports_iconsets'):
4626 folder = os.path.join(helpers.get_transport_path(transport),
4627 '16x16')
4628 pixo, pixc = gtkgui_helpers.load_icons_meta()
4629 self.transports_state_images['opened'][transport] = \
4630 gtkgui_helpers.load_iconset(folder, pixo, transport=True)
4631 self.transports_state_images['closed'][transport] = \
4632 gtkgui_helpers.load_iconset(folder, pixc, transport=True)
4633 folder = os.path.join(helpers.get_transport_path(transport),
4634 '32x32')
4635 self.transports_state_images['32'][transport] = \
4636 gtkgui_helpers.load_iconset(folder, transport=True)
4637 folder = os.path.join(helpers.get_transport_path(transport),
4638 '16x16')
4639 self.transports_state_images['16'][transport] = \
4640 gtkgui_helpers.load_iconset(folder, transport=True)
4642 def update_jabber_state_images(self):
4643 # Update the roster
4644 self.setup_and_draw_roster()
4645 # Update the status combobox
4646 model = self.status_combobox.get_model()
4647 titer = model.get_iter_root()
4648 while titer:
4649 if model[titer][2] != '':
4650 # If it's not change status message iter
4651 # eg. if it has show parameter not ''
4652 model[titer][1] = gajim.interface.jabber_state_images['16'][
4653 model[titer][2]]
4654 titer = model.iter_next(titer)
4655 # Update the systray
4656 if gajim.interface.systray_enabled:
4657 gajim.interface.systray.set_img()
4659 for win in gajim.interface.msg_win_mgr.windows():
4660 for ctrl in win.controls():
4661 ctrl.update_ui()
4662 win.redraw_tab(ctrl)
4664 self.update_status_combobox()
4666 def set_account_status_icon(self, account):
4667 status = gajim.connections[account].connected
4668 child_iterA = self._get_account_iter(account, self.model)
4669 if not child_iterA:
4670 return
4671 if not self.regroup:
4672 show = gajim.SHOW_LIST[status]
4673 else: # accounts merged
4674 show = helpers.get_global_show()
4675 self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[
4676 '16'][show]
4678 ################################################################################
4679 ### Style and theme related methods
4680 ################################################################################
4682 def show_title(self):
4683 change_title_allowed = gajim.config.get('change_roster_title')
4684 if not change_title_allowed:
4685 return
4687 if gajim.config.get('one_message_window') == 'always_with_roster':
4688 # always_with_roster mode defers to the MessageWindow
4689 if not gajim.interface.msg_win_mgr.one_window_opened():
4690 # No MessageWindow to defer to
4691 self.window.set_title('Gajim')
4692 return
4694 nb_unread = 0
4695 start = ''
4696 for account in gajim.connections:
4697 # Count events in roster title only if we don't auto open them
4698 if not helpers.allow_popup_window(account):
4699 nb_unread += gajim.events.get_nb_events(['chat', 'normal',
4700 'file-request', 'file-error', 'file-completed',
4701 'file-request-error', 'file-send-error', 'file-stopped',
4702 'printed_chat'], account)
4703 if nb_unread > 1:
4704 start = '[' + str(nb_unread) + '] '
4705 elif nb_unread == 1:
4706 start = '* '
4708 self.window.set_title(start + 'Gajim')
4710 gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
4712 def _change_style(self, model, path, titer, option):
4713 if option is None or model[titer][C_TYPE] == option:
4714 # We changed style for this type of row
4715 model[titer][C_NAME] = model[titer][C_NAME]
4717 def change_roster_style(self, option):
4718 self.model.foreach(self._change_style, option)
4719 for win in gajim.interface.msg_win_mgr.windows():
4720 win.repaint_themed_widgets()
4722 def repaint_themed_widgets(self):
4724 Notify windows that contain themed widgets to repaint them
4726 for win in gajim.interface.msg_win_mgr.windows():
4727 win.repaint_themed_widgets()
4728 for account in gajim.connections:
4729 for addr in gajim.interface.instances[account]['disco']:
4730 gajim.interface.instances[account]['disco'][addr].paint_banner()
4731 for ctrl in gajim.interface.minimized_controls[account].values():
4732 ctrl.repaint_themed_widgets()
4734 def update_avatar_in_gui(self, jid, account):
4735 # Update roster
4736 self.draw_avatar(jid, account)
4737 # Update chat window
4739 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
4740 if ctrl:
4741 ctrl.show_avatar()
4743 def set_renderer_color(self, renderer, style, set_background=True):
4745 Set style for treeview cell, using PRELIGHT system color
4747 if set_background:
4748 bgcolor = self.tree.style.bg[style]
4749 renderer.set_property('cell-background-gdk', bgcolor)
4750 else:
4751 fgcolor = self.tree.style.fg[style]
4752 renderer.set_property('foreground-gdk', fgcolor)
4754 def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
4756 When a row is added, set properties for icon renderer
4758 type_ = model[titer][C_TYPE]
4759 if type_ == 'account':
4760 self._set_account_row_background_color(renderer)
4761 renderer.set_property('xalign', 0)
4762 elif type_ == 'group':
4763 self._set_group_row_background_color(renderer)
4764 parent_iter = model.iter_parent(titer)
4765 if model[parent_iter][C_TYPE] == 'group':
4766 renderer.set_property('xalign', 0.4)
4767 else:
4768 renderer.set_property('xalign', 0.2)
4769 elif type_:
4770 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4771 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4772 # This can append when at the moment we add the row
4773 return
4774 jid = model[titer][C_JID].decode('utf-8')
4775 account = model[titer][C_ACCOUNT].decode('utf-8')
4776 self._set_contact_row_background_color(renderer, jid, account)
4777 parent_iter = model.iter_parent(titer)
4778 if model[parent_iter][C_TYPE] == 'contact':
4779 renderer.set_property('xalign', 1)
4780 else:
4781 renderer.set_property('xalign', 0.6)
4782 renderer.set_property('width', 26)
4784 def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
4786 When a row is added, set properties for name renderer
4788 theme = gajim.config.get('roster_theme')
4789 type_ = model[titer][C_TYPE]
4790 if type_ == 'account':
4791 color = gajim.config.get_per('themes', theme, 'accounttextcolor')
4792 if color:
4793 renderer.set_property('foreground', color)
4794 else:
4795 self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False)
4796 renderer.set_property('font',
4797 gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
4798 renderer.set_property('xpad', 0)
4799 renderer.set_property('width', 3)
4800 self._set_account_row_background_color(renderer)
4801 elif type_ == 'group':
4802 color = gajim.config.get_per('themes', theme, 'grouptextcolor')
4803 if color:
4804 renderer.set_property('foreground', color)
4805 else:
4806 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
4807 renderer.set_property('font',
4808 gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
4809 parent_iter = model.iter_parent(titer)
4810 if model[parent_iter][C_TYPE] == 'group':
4811 renderer.set_property('xpad', 8)
4812 else:
4813 renderer.set_property('xpad', 4)
4814 self._set_group_row_background_color(renderer)
4815 elif type_:
4816 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4817 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4818 # This can append when at the moment we add the row
4819 return
4820 jid = model[titer][C_JID].decode('utf-8')
4821 account = model[titer][C_ACCOUNT].decode('utf-8')
4822 color = None
4823 if type_ == 'groupchat':
4824 ctrl = gajim.interface.minimized_controls[account].get(jid,
4825 None)
4826 if ctrl and ctrl.attention_flag:
4827 color = gajim.config.get_per('themes', theme,
4828 'state_muc_directed_msg_color')
4829 renderer.set_property('foreground', 'red')
4830 if not color:
4831 color = gajim.config.get_per('themes', theme,
4832 'contacttextcolor')
4833 if color:
4834 renderer.set_property('foreground', color)
4835 else:
4836 renderer.set_property('foreground', None)
4837 self._set_contact_row_background_color(renderer, jid, account)
4838 renderer.set_property('font',
4839 gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
4840 parent_iter = model.iter_parent(titer)
4841 if model[parent_iter][C_TYPE] == 'contact':
4842 renderer.set_property('xpad', 16)
4843 else:
4844 renderer.set_property('xpad', 12)
4846 def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
4847 data=None):
4849 When a row is added, draw the respective pep icon
4851 type_ = model[titer][C_TYPE]
4853 # allocate space for the icon only if needed
4854 if not model[titer][data]:
4855 renderer.set_property('visible', False)
4856 else:
4857 renderer.set_property('visible', True)
4859 if type_ == 'account':
4860 self._set_account_row_background_color(renderer)
4861 renderer.set_property('xalign', 1)
4862 elif type_:
4863 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4864 # This can append at the moment we add the row
4865 return
4866 jid = model[titer][C_JID].decode('utf-8')
4867 account = model[titer][C_ACCOUNT].decode('utf-8')
4868 self._set_contact_row_background_color(renderer, jid, account)
4870 def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer,
4871 data=None):
4873 When a row is added, set properties for avatar renderer
4875 type_ = model[titer][C_TYPE]
4876 if type_ in ('group', 'account'):
4877 renderer.set_property('visible', False)
4878 return
4880 # allocate space for the icon only if needed
4881 if model[titer][C_AVATAR_PIXBUF] or \
4882 gajim.config.get('avatar_position_in_roster') == 'left':
4883 renderer.set_property('visible', True)
4884 if type_:
4885 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4886 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4887 # This can append at the moment we add the row
4888 return
4889 jid = model[titer][C_JID].decode('utf-8')
4890 account = model[titer][C_ACCOUNT].decode('utf-8')
4891 self._set_contact_row_background_color(renderer, jid, account)
4892 else:
4893 renderer.set_property('visible', False)
4895 if gajim.config.get('avatar_position_in_roster') == 'left':
4896 renderer.set_property('width', gajim.config.get(
4897 'roster_avatar_width'))
4898 renderer.set_property('xalign', 0.5)
4899 else:
4900 renderer.set_property('xalign', 1) # align pixbuf to the right
4902 def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer,
4903 data=None):
4905 When a row is added, set properties for padlock renderer
4907 type_ = model[titer][C_TYPE]
4908 # allocate space for the icon only if needed
4909 if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]:
4910 renderer.set_property('visible', True)
4911 self._set_account_row_background_color(renderer)
4912 renderer.set_property('xalign', 1) # align pixbuf to the right
4913 else:
4914 renderer.set_property('visible', False)
4916 def _set_account_row_background_color(self, renderer):
4917 theme = gajim.config.get('roster_theme')
4918 color = gajim.config.get_per('themes', theme, 'accountbgcolor')
4919 if color:
4920 renderer.set_property('cell-background', color)
4921 else:
4922 self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
4924 def _set_contact_row_background_color(self, renderer, jid, account):
4925 theme = gajim.config.get('roster_theme')
4926 if jid in gajim.newly_added[account]:
4927 renderer.set_property('cell-background', gajim.config.get(
4928 'just_connected_bg_color'))
4929 elif jid in gajim.to_be_removed[account]:
4930 renderer.set_property('cell-background', gajim.config.get(
4931 'just_disconnected_bg_color'))
4932 else:
4933 color = gajim.config.get_per('themes', theme, 'contactbgcolor')
4934 renderer.set_property('cell-background', color if color else None)
4936 def _set_group_row_background_color(self, renderer):
4937 theme = gajim.config.get('roster_theme')
4938 color = gajim.config.get_per('themes', theme, 'groupbgcolor')
4939 if color:
4940 renderer.set_property('cell-background', color)
4941 else:
4942 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
4944 ################################################################################
4945 ### Everything about building menus
4946 ### FIXME: We really need to make it simpler! 1465 lines are a few to much....
4947 ################################################################################
4949 def make_menu(self, force=False):
4951 Create the main window's menus
4953 if not force and not self.actions_menu_needs_rebuild:
4954 return
4955 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
4956 single_message_menuitem = self.xml.get_object(
4957 'send_single_message_menuitem')
4958 join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
4959 muc_icon = gtkgui_helpers.load_icon('muc_active')
4960 if muc_icon:
4961 join_gc_menuitem.set_image(muc_icon)
4962 add_new_contact_menuitem = self.xml.get_object(
4963 'add_new_contact_menuitem')
4964 service_disco_menuitem = self.xml.get_object('service_disco_menuitem')
4965 advanced_menuitem = self.xml.get_object('advanced_menuitem')
4966 profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem')
4968 # destroy old advanced menus
4969 for m in self.advanced_menus:
4970 m.destroy()
4972 # make it sensitive. it is insensitive only if no accounts are
4973 # *available*
4974 advanced_menuitem.set_sensitive(True)
4976 if self.add_new_contact_handler_id:
4977 add_new_contact_menuitem.handler_disconnect(
4978 self.add_new_contact_handler_id)
4979 self.add_new_contact_handler_id = None
4981 if self.service_disco_handler_id:
4982 service_disco_menuitem.handler_disconnect(
4983 self.service_disco_handler_id)
4984 self.service_disco_handler_id = None
4986 if self.single_message_menuitem_handler_id:
4987 single_message_menuitem.handler_disconnect(
4988 self.single_message_menuitem_handler_id)
4989 self.single_message_menuitem_handler_id = None
4991 if self.profile_avatar_menuitem_handler_id:
4992 profile_avatar_menuitem.handler_disconnect(
4993 self.profile_avatar_menuitem_handler_id)
4994 self.profile_avatar_menuitem_handler_id = None
4996 # remove the existing submenus
4997 add_new_contact_menuitem.remove_submenu()
4998 service_disco_menuitem.remove_submenu()
4999 join_gc_menuitem.remove_submenu()
5000 single_message_menuitem.remove_submenu()
5001 advanced_menuitem.remove_submenu()
5002 profile_avatar_menuitem.remove_submenu()
5004 gc_sub_menu = gtk.Menu() # gc is always a submenu
5005 join_gc_menuitem.set_submenu(gc_sub_menu)
5007 connected_accounts = gajim.get_number_of_connected_accounts()
5009 connected_accounts_with_private_storage = 0
5011 # items that get shown whether an account is zeroconf or not
5012 accounts_list = sorted(gajim.contacts.get_accounts())
5013 if connected_accounts > 2 or \
5014 (connected_accounts > 1 and not gajim.zeroconf_is_connected()):
5015 # 2 or more "real" (no zeroconf) accounts? make submenus
5016 new_chat_sub_menu = gtk.Menu()
5018 for account in accounts_list:
5019 if gajim.connections[account].connected <= 1 or \
5020 gajim.config.get_per('accounts', account, 'is_zeroconf'):
5021 # if offline or connecting or zeroconf
5022 continue
5024 # new chat
5025 new_chat_item = gtk.MenuItem(_('using account %s') % account,
5026 False)
5027 new_chat_sub_menu.append(new_chat_item)
5028 new_chat_item.connect('activate',
5029 self.on_new_chat_menuitem_activate, account)
5031 new_chat_menuitem.set_submenu(new_chat_sub_menu)
5032 new_chat_sub_menu.show_all()
5034 # menu items that don't apply to zeroconf connections
5035 if connected_accounts == 1 or (connected_accounts == 2 and \
5036 gajim.zeroconf_is_connected()):
5037 # only one 'real' (non-zeroconf) account is connected, don't need
5038 # submenus
5040 for account in accounts_list:
5041 if gajim.account_is_connected(account) and \
5042 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
5043 # gc
5044 if gajim.connections[account].private_storage_supported:
5045 connected_accounts_with_private_storage += 1
5046 self.add_bookmarks_list(gc_sub_menu, account)
5047 gc_sub_menu.show_all()
5048 # add
5049 if not self.add_new_contact_handler_id:
5050 self.add_new_contact_handler_id = \
5051 add_new_contact_menuitem.connect(
5052 'activate', self.on_add_new_contact, account)
5053 # disco
5054 if not self.service_disco_handler_id:
5055 self.service_disco_handler_id = service_disco_menuitem.\
5056 connect('activate',
5057 self.on_service_disco_menuitem_activate, account)
5059 # single message
5060 if not self.single_message_menuitem_handler_id:
5061 self.single_message_menuitem_handler_id = \
5062 single_message_menuitem.connect('activate', \
5063 self.on_send_single_message_menuitem_activate, account)
5065 break # No other account connected
5066 else:
5067 # 2 or more 'real' accounts are connected, make submenus
5068 single_message_sub_menu = gtk.Menu()
5069 add_sub_menu = gtk.Menu()
5070 disco_sub_menu = gtk.Menu()
5072 for account in accounts_list:
5073 if gajim.connections[account].connected <= 1 or \
5074 gajim.config.get_per('accounts', account, 'is_zeroconf'):
5075 # skip account if it's offline or connecting or is zeroconf
5076 continue
5078 # single message
5079 single_message_item = gtk.MenuItem(_('using account %s') % \
5080 account, False)
5081 single_message_sub_menu.append(single_message_item)
5082 single_message_item.connect('activate',
5083 self.on_send_single_message_menuitem_activate, account)
5085 # join gc
5086 if gajim.connections[account].private_storage_supported:
5087 connected_accounts_with_private_storage += 1
5088 gc_item = gtk.MenuItem(_('using account %s') % account, False)
5089 gc_sub_menu.append(gc_item)
5090 gc_menuitem_menu = gtk.Menu()
5091 self.add_bookmarks_list(gc_menuitem_menu, account)
5092 gc_item.set_submenu(gc_menuitem_menu)
5094 # add
5095 add_item = gtk.MenuItem(_('to %s account') % account, False)
5096 add_sub_menu.append(add_item)
5097 add_item.connect('activate', self.on_add_new_contact, account)
5099 # disco
5100 disco_item = gtk.MenuItem(_('using %s account') % account,
5101 False)
5102 disco_sub_menu.append(disco_item)
5103 disco_item.connect('activate',
5104 self.on_service_disco_menuitem_activate, account)
5106 single_message_menuitem.set_submenu(single_message_sub_menu)
5107 single_message_sub_menu.show_all()
5108 gc_sub_menu.show_all()
5109 add_new_contact_menuitem.set_submenu(add_sub_menu)
5110 add_sub_menu.show_all()
5111 service_disco_menuitem.set_submenu(disco_sub_menu)
5112 disco_sub_menu.show_all()
5114 if connected_accounts == 0:
5115 # no connected accounts, make the menuitems insensitive
5116 for item in (new_chat_menuitem, join_gc_menuitem,
5117 add_new_contact_menuitem, service_disco_menuitem,
5118 single_message_menuitem):
5119 item.set_sensitive(False)
5120 else: # we have one or more connected accounts
5121 for item in (new_chat_menuitem, join_gc_menuitem,
5122 add_new_contact_menuitem, service_disco_menuitem,
5123 single_message_menuitem):
5124 item.set_sensitive(True)
5125 # disable some fields if only local account is there
5126 if connected_accounts == 1:
5127 for account in gajim.connections:
5128 if gajim.account_is_connected(account) and \
5129 gajim.connections[account].is_zeroconf:
5130 for item in (new_chat_menuitem, join_gc_menuitem,
5131 add_new_contact_menuitem, service_disco_menuitem,
5132 single_message_menuitem):
5133 item.set_sensitive(False)
5135 # Manage GC bookmarks
5136 newitem = gtk.SeparatorMenuItem() # separator
5137 gc_sub_menu.append(newitem)
5139 newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
5140 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5141 gtk.ICON_SIZE_MENU)
5142 newitem.set_image(img)
5143 newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
5144 gc_sub_menu.append(newitem)
5145 gc_sub_menu.show_all()
5146 if connected_accounts_with_private_storage == 0:
5147 newitem.set_sensitive(False)
5149 connected_accounts_with_vcard = []
5150 for account in gajim.connections:
5151 if gajim.account_is_connected(account) and \
5152 gajim.connections[account].vcard_supported:
5153 connected_accounts_with_vcard.append(account)
5154 if len(connected_accounts_with_vcard) > 1:
5155 # 2 or more accounts? make submenus
5156 profile_avatar_sub_menu = gtk.Menu()
5157 for account in connected_accounts_with_vcard:
5158 # profile, avatar
5159 profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
5160 False)
5161 profile_avatar_sub_menu.append(profile_avatar_item)
5162 profile_avatar_item.connect('activate',
5163 self.on_profile_avatar_menuitem_activate, account)
5164 profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
5165 profile_avatar_sub_menu.show_all()
5166 elif len(connected_accounts_with_vcard) == 1:
5167 # user has only one account
5168 account = connected_accounts_with_vcard[0]
5169 # profile, avatar
5170 if not self.profile_avatar_menuitem_handler_id:
5171 self.profile_avatar_menuitem_handler_id = \
5172 profile_avatar_menuitem.connect('activate',
5173 self.on_profile_avatar_menuitem_activate, account)
5175 if len(connected_accounts_with_vcard) == 0:
5176 profile_avatar_menuitem.set_sensitive(False)
5177 else:
5178 profile_avatar_menuitem.set_sensitive(True)
5180 # Advanced Actions
5181 if len(gajim.connections) == 0: # user has no accounts
5182 advanced_menuitem.set_sensitive(False)
5183 elif len(gajim.connections) == 1: # we have one acccount
5184 account = gajim.connections.keys()[0]
5185 advanced_menuitem_menu = \
5186 self.get_and_connect_advanced_menuitem_menu(account)
5187 self.advanced_menus.append(advanced_menuitem_menu)
5189 self.add_history_manager_menuitem(advanced_menuitem_menu)
5191 advanced_menuitem.set_submenu(advanced_menuitem_menu)
5192 advanced_menuitem_menu.show_all()
5193 else: # user has *more* than one account : build advanced submenus
5194 advanced_sub_menu = gtk.Menu()
5195 accounts = [] # Put accounts in a list to sort them
5196 for account in gajim.connections:
5197 accounts.append(account)
5198 accounts.sort()
5199 for account in accounts:
5200 advanced_item = gtk.MenuItem(_('for account %s') % account,
5201 False)
5202 advanced_sub_menu.append(advanced_item)
5203 advanced_menuitem_menu = \
5204 self.get_and_connect_advanced_menuitem_menu(account)
5205 self.advanced_menus.append(advanced_menuitem_menu)
5206 advanced_item.set_submenu(advanced_menuitem_menu)
5208 self.add_history_manager_menuitem(advanced_sub_menu)
5210 advanced_menuitem.set_submenu(advanced_sub_menu)
5211 advanced_sub_menu.show_all()
5213 self.actions_menu_needs_rebuild = False
5215 def build_account_menu(self, account):
5216 # we have to create our own set of icons for the menu
5217 # using self.jabber_status_images is poopoo
5218 iconset = gajim.config.get('iconset')
5219 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5220 state_images = gtkgui_helpers.load_iconset(path)
5222 if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
5223 xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui')
5224 account_context_menu = xml.get_object('account_context_menu')
5226 status_menuitem = xml.get_object('status_menuitem')
5227 start_chat_menuitem = xml.get_object('start_chat_menuitem')
5228 join_group_chat_menuitem = xml.get_object(
5229 'join_group_chat_menuitem')
5230 muc_icon = gtkgui_helpers.load_icon('muc_active')
5231 if muc_icon:
5232 join_group_chat_menuitem.set_image(muc_icon)
5233 open_gmail_inbox_menuitem = xml.get_object(
5234 'open_gmail_inbox_menuitem')
5235 add_contact_menuitem = xml.get_object('add_contact_menuitem')
5236 service_discovery_menuitem = xml.get_object(
5237 'service_discovery_menuitem')
5238 execute_command_menuitem = xml.get_object(
5239 'execute_command_menuitem')
5240 edit_account_menuitem = xml.get_object('edit_account_menuitem')
5241 sub_menu = gtk.Menu()
5242 status_menuitem.set_submenu(sub_menu)
5244 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
5245 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5246 item = gtk.ImageMenuItem(uf_show)
5247 icon = state_images[show]
5248 item.set_image(icon)
5249 sub_menu.append(item)
5250 con = gajim.connections[account]
5251 if show == 'invisible' and con.connected > 1 and \
5252 not con.privacy_rules_supported:
5253 item.set_sensitive(False)
5254 else:
5255 item.connect('activate', self.change_status, account, show)
5257 item = gtk.SeparatorMenuItem()
5258 sub_menu.append(item)
5260 item = gtk.ImageMenuItem(_('_Change Status Message'))
5261 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5262 sub_menu.append(item)
5263 item.connect('activate', self.on_change_status_message_activate,
5264 account)
5265 if gajim.connections[account].connected < 2:
5266 item.set_sensitive(False)
5268 item = gtk.SeparatorMenuItem()
5269 sub_menu.append(item)
5271 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5272 item = gtk.ImageMenuItem(uf_show)
5273 icon = state_images['offline']
5274 item.set_image(icon)
5275 sub_menu.append(item)
5276 item.connect('activate', self.change_status, account, 'offline')
5278 pep_menuitem = xml.get_object('pep_menuitem')
5279 if gajim.connections[account].pep_supported:
5280 pep_submenu = gtk.Menu()
5281 pep_menuitem.set_submenu(pep_submenu)
5282 def add_item(label, opt_name, func):
5283 item = gtk.CheckMenuItem(label)
5284 pep_submenu.append(item)
5285 if not dbus_support.supported:
5286 item.set_sensitive(False)
5287 else:
5288 activ = gajim.config.get_per('accounts', account,
5289 opt_name)
5290 item.set_active(activ)
5291 item.connect('toggled', func, account)
5293 add_item(_('Publish Tune'), 'publish_tune',
5294 self.on_publish_tune_toggled)
5295 add_item(_('Publish Location'), 'publish_location',
5296 self.on_publish_location_toggled)
5298 pep_config = gtk.ImageMenuItem(_('Configure Services...'))
5299 item = gtk.SeparatorMenuItem()
5300 pep_submenu.append(item)
5301 pep_config.set_sensitive(True)
5302 pep_submenu.append(pep_config)
5303 pep_config.connect('activate',
5304 self.on_pep_services_menuitem_activate, account)
5305 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5306 gtk.ICON_SIZE_MENU)
5307 pep_config.set_image(img)
5309 else:
5310 pep_menuitem.set_sensitive(False)
5312 if not gajim.connections[account].gmail_url:
5313 open_gmail_inbox_menuitem.set_no_show_all(True)
5314 open_gmail_inbox_menuitem.hide()
5315 else:
5316 open_gmail_inbox_menuitem.connect('activate',
5317 self.on_open_gmail_inbox, account)
5319 edit_account_menuitem.connect('activate', self.on_edit_account,
5320 account)
5321 add_contact_menuitem.connect('activate', self.on_add_new_contact,
5322 account)
5323 service_discovery_menuitem.connect('activate',
5324 self.on_service_disco_menuitem_activate, account)
5325 hostname = gajim.config.get_per('accounts', account, 'hostname')
5326 contact = gajim.contacts.create_contact(jid=hostname,
5327 account=account) # Fake contact
5328 execute_command_menuitem.connect('activate',
5329 self.on_execute_command, contact, account)
5331 start_chat_menuitem.connect('activate',
5332 self.on_new_chat_menuitem_activate, account)
5334 gc_sub_menu = gtk.Menu() # gc is always a submenu
5335 join_group_chat_menuitem.set_submenu(gc_sub_menu)
5336 self.add_bookmarks_list(gc_sub_menu, account)
5338 # make some items insensitive if account is offline
5339 if gajim.connections[account].connected < 2:
5340 for widget in (add_contact_menuitem, service_discovery_menuitem,
5341 join_group_chat_menuitem, execute_command_menuitem,
5342 pep_menuitem, start_chat_menuitem):
5343 widget.set_sensitive(False)
5344 else:
5345 xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui')
5346 account_context_menu = xml.get_object('zeroconf_context_menu')
5348 status_menuitem = xml.get_object('status_menuitem')
5349 zeroconf_properties_menuitem = xml.get_object(
5350 'zeroconf_properties_menuitem')
5351 sub_menu = gtk.Menu()
5352 status_menuitem.set_submenu(sub_menu)
5354 for show in ('online', 'away', 'dnd', 'invisible'):
5355 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5356 item = gtk.ImageMenuItem(uf_show)
5357 icon = state_images[show]
5358 item.set_image(icon)
5359 sub_menu.append(item)
5360 item.connect('activate', self.change_status, account, show)
5362 item = gtk.SeparatorMenuItem()
5363 sub_menu.append(item)
5365 item = gtk.ImageMenuItem(_('_Change Status Message'))
5366 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5367 sub_menu.append(item)
5368 item.connect('activate', self.on_change_status_message_activate,
5369 account)
5370 if gajim.connections[account].connected < 2:
5371 item.set_sensitive(False)
5373 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5374 item = gtk.ImageMenuItem(uf_show)
5375 icon = state_images['offline']
5376 item.set_image(icon)
5377 sub_menu.append(item)
5378 item.connect('activate', self.change_status, account, 'offline')
5380 zeroconf_properties_menuitem.connect('activate',
5381 self.on_zeroconf_properties, account)
5383 return account_context_menu
5385 def make_account_menu(self, event, titer):
5387 Make account's popup menu
5389 model = self.modelfilter
5390 account = model[titer][C_ACCOUNT].decode('utf-8')
5392 if account != 'all': # not in merged mode
5393 menu = self.build_account_menu(account)
5394 else:
5395 menu = gtk.Menu()
5396 iconset = gajim.config.get('iconset')
5397 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5398 accounts = [] # Put accounts in a list to sort them
5399 for account in gajim.connections:
5400 accounts.append(account)
5401 accounts.sort()
5402 for account in accounts:
5403 state_images = gtkgui_helpers.load_iconset(path)
5404 item = gtk.ImageMenuItem(account)
5405 show = gajim.SHOW_LIST[gajim.connections[account].connected]
5406 icon = state_images[show]
5407 item.set_image(icon)
5408 account_menu = self.build_account_menu(account)
5409 item.set_submenu(account_menu)
5410 menu.append(item)
5412 event_button = gtkgui_helpers.get_possible_button_event(event)
5414 menu.attach_to_widget(self.tree, None)
5415 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5416 menu.show_all()
5417 menu.popup(None, None, None, event_button, event.time)
5419 def make_group_menu(self, event, titer):
5421 Make group's popup menu
5423 model = self.modelfilter
5424 path = model.get_path(titer)
5425 group = model[titer][C_JID].decode('utf-8')
5426 account = model[titer][C_ACCOUNT].decode('utf-8')
5428 list_ = [] # list of (jid, account) tuples
5429 list_online = [] # list of (jid, account) tuples
5431 group = model[titer][C_JID]
5432 for jid in gajim.contacts.get_jid_list(account):
5433 contact = gajim.contacts.get_contact_with_highest_priority(account,
5434 jid)
5435 if group in contact.get_shown_groups():
5436 if contact.show not in ('offline', 'error'):
5437 list_online.append((contact, account))
5438 list_.append((contact, account))
5439 menu = gtk.Menu()
5441 # Make special context menu if group is Groupchats
5442 if group == _('Groupchats'):
5443 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All'))
5444 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
5445 gtk.ICON_SIZE_MENU)
5446 maximize_menuitem.set_image(icon)
5447 maximize_menuitem.connect('activate',
5448 self.on_all_groupchat_maximized, list_)
5449 menu.append(maximize_menuitem)
5450 else:
5451 # Send Group Message
5452 send_group_message_item = gtk.ImageMenuItem(
5453 _('Send Group M_essage'))
5454 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5455 send_group_message_item.set_image(icon)
5457 send_group_message_submenu = gtk.Menu()
5458 send_group_message_item.set_submenu(send_group_message_submenu)
5459 menu.append(send_group_message_item)
5461 group_message_to_all_item = gtk.MenuItem(_('To all users'))
5462 send_group_message_submenu.append(group_message_to_all_item)
5464 group_message_to_all_online_item = gtk.MenuItem(
5465 _('To all online users'))
5466 send_group_message_submenu.append(group_message_to_all_online_item)
5468 group_message_to_all_online_item.connect('activate',
5469 self.on_send_single_message_menuitem_activate, account,
5470 list_online)
5471 group_message_to_all_item.connect('activate',
5472 self.on_send_single_message_menuitem_activate, account, list_)
5474 # Invite to
5475 invite_menuitem = gtk.ImageMenuItem(_('In_vite to'))
5476 muc_icon = gtkgui_helpers.load_icon('muc_active')
5477 if muc_icon:
5478 invite_menuitem.set_image(muc_icon)
5480 gui_menu_builder.build_invite_submenu(invite_menuitem, list_online)
5481 menu.append(invite_menuitem)
5483 # Send Custom Status
5484 send_custom_status_menuitem = gtk.ImageMenuItem(
5485 _('Send Cus_tom Status'))
5486 # add a special img for this menuitem
5487 if helpers.group_is_blocked(account, group):
5488 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5489 'offline'))
5490 send_custom_status_menuitem.set_sensitive(False)
5491 else:
5492 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5493 gtk.ICON_SIZE_MENU)
5494 send_custom_status_menuitem.set_image(icon)
5495 status_menuitems = gtk.Menu()
5496 send_custom_status_menuitem.set_submenu(status_menuitems)
5497 iconset = gajim.config.get('iconset')
5498 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5499 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5500 # icon MUST be different instance for every item
5501 state_images = gtkgui_helpers.load_iconset(path)
5502 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5503 status_menuitem.connect('activate', self.on_send_custom_status,
5504 list_, s, group)
5505 icon = state_images[s]
5506 status_menuitem.set_image(icon)
5507 status_menuitems.append(status_menuitem)
5508 menu.append(send_custom_status_menuitem)
5510 # there is no singlemessage and custom status for zeroconf
5511 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
5512 send_custom_status_menuitem.set_sensitive(False)
5513 send_group_message_item.set_sensitive(False)
5515 if not group in helpers.special_groups:
5516 item = gtk.SeparatorMenuItem() # separator
5517 menu.append(item)
5519 # Rename
5520 rename_item = gtk.ImageMenuItem(_('Re_name'))
5521 # add a special img for rename menuitem
5522 gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input')
5523 menu.append(rename_item)
5524 rename_item.connect('activate', self.on_rename, 'group', group,
5525 account)
5527 # Block group
5528 is_blocked = False
5529 if self.regroup:
5530 for g_account in gajim.connections:
5531 if helpers.group_is_blocked(g_account, group):
5532 is_blocked = True
5533 else:
5534 if helpers.group_is_blocked(account, group):
5535 is_blocked = True
5537 if is_blocked and gajim.connections[account].\
5538 privacy_rules_supported:
5539 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5540 icon = gtk.image_new_from_stock(gtk.STOCK_STOP,
5541 gtk.ICON_SIZE_MENU)
5542 unblock_menuitem.set_image(icon)
5543 unblock_menuitem.connect('activate', self.on_unblock, list_,
5544 group)
5545 menu.append(unblock_menuitem)
5546 else:
5547 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5548 icon = gtk.image_new_from_stock(gtk.STOCK_STOP,
5549 gtk.ICON_SIZE_MENU)
5550 block_menuitem.set_image(icon)
5551 block_menuitem.connect('activate', self.on_block, list_, group)
5552 menu.append(block_menuitem)
5553 if not gajim.connections[account].privacy_rules_supported:
5554 block_menuitem.set_sensitive(False)
5556 # Remove group
5557 remove_item = gtk.ImageMenuItem(_('_Remove'))
5558 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE,
5559 gtk.ICON_SIZE_MENU)
5560 remove_item.set_image(icon)
5561 menu.append(remove_item)
5562 remove_item.connect('activate', self.on_remove_group_item_activated,
5563 group, account)
5565 # unsensitive if account is not connected
5566 if gajim.connections[account].connected < 2:
5567 rename_item.set_sensitive(False)
5569 # General group cannot be changed
5570 if group == _('General'):
5571 rename_item.set_sensitive(False)
5572 remove_item.set_sensitive(False)
5574 event_button = gtkgui_helpers.get_possible_button_event(event)
5576 menu.attach_to_widget(self.tree, None)
5577 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5578 menu.show_all()
5579 menu.popup(None, None, None, event_button, event.time)
5581 def make_contact_menu(self, event, titer):
5583 Make contact's popup menu
5585 model = self.modelfilter
5586 jid = model[titer][C_JID].decode('utf-8')
5587 account = model[titer][C_ACCOUNT].decode('utf-8')
5588 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5589 menu = gui_menu_builder.get_contact_menu(contact, account)
5590 event_button = gtkgui_helpers.get_possible_button_event(event)
5591 menu.attach_to_widget(self.tree, None)
5592 menu.popup(None, None, None, event_button, event.time)
5594 def make_multiple_contact_menu(self, event, iters):
5596 Make group's popup menu
5598 model = self.modelfilter
5599 list_ = [] # list of (jid, account) tuples
5600 one_account_offline = False
5601 is_blocked = True
5602 privacy_rules_supported = True
5603 for titer in iters:
5604 jid = model[titer][C_JID].decode('utf-8')
5605 account = model[titer][C_ACCOUNT].decode('utf-8')
5606 if gajim.connections[account].connected < 2:
5607 one_account_offline = True
5608 if not gajim.connections[account].privacy_rules_supported:
5609 privacy_rules_supported = False
5610 contact = gajim.contacts.get_contact_with_highest_priority(account,
5611 jid)
5612 if helpers.jid_is_blocked(account, jid):
5613 is_blocked = False
5614 list_.append((contact, account))
5616 menu = gtk.Menu()
5617 account = None
5618 for (contact, current_account) in list_:
5619 # check that we use the same account for every sender
5620 if account is not None and account != current_account:
5621 account = None
5622 break
5623 account = current_account
5624 if account is not None:
5625 send_group_message_item = gtk.ImageMenuItem(
5626 _('Send Group M_essage'))
5627 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5628 send_group_message_item.set_image(icon)
5629 menu.append(send_group_message_item)
5630 send_group_message_item.connect('activate',
5631 self.on_send_single_message_menuitem_activate, account, list_)
5633 # Invite to Groupchat
5634 invite_item = gtk.ImageMenuItem(_('In_vite to'))
5635 muc_icon = gtkgui_helpers.load_icon('muc_active')
5636 if muc_icon:
5637 invite_item.set_image(muc_icon)
5639 gui_menu_builder.build_invite_submenu(invite_item, list_)
5640 menu.append(invite_item)
5642 item = gtk.SeparatorMenuItem() # separator
5643 menu.append(item)
5645 # Manage Transport submenu
5646 item = gtk.ImageMenuItem(_('_Manage Contacts'))
5647 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES,
5648 gtk.ICON_SIZE_MENU)
5649 item.set_image(icon)
5650 manage_contacts_submenu = gtk.Menu()
5651 item.set_submenu(manage_contacts_submenu)
5652 menu.append(item)
5654 # Edit Groups
5655 edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups'))
5656 icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
5657 edit_groups_item.set_image(icon)
5658 manage_contacts_submenu.append(edit_groups_item)
5659 edit_groups_item.connect('activate', self.on_edit_groups, list_)
5661 item = gtk.SeparatorMenuItem() # separator
5662 manage_contacts_submenu.append(item)
5664 # Block
5665 if is_blocked and privacy_rules_supported:
5666 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5667 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5668 unblock_menuitem.set_image(icon)
5669 unblock_menuitem.connect('activate', self.on_unblock, list_)
5670 manage_contacts_submenu.append(unblock_menuitem)
5671 else:
5672 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5673 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5674 block_menuitem.set_image(icon)
5675 block_menuitem.connect('activate', self.on_block, list_)
5676 manage_contacts_submenu.append(block_menuitem)
5678 if not privacy_rules_supported:
5679 block_menuitem.set_sensitive(False)
5681 # Remove
5682 remove_item = gtk.ImageMenuItem(_('_Remove'))
5683 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5684 remove_item.set_image(icon)
5685 manage_contacts_submenu.append(remove_item)
5686 remove_item.connect('activate', self.on_req_usub, list_)
5687 # unsensitive remove if one account is not connected
5688 if one_account_offline:
5689 remove_item.set_sensitive(False)
5691 event_button = gtkgui_helpers.get_possible_button_event(event)
5693 menu.attach_to_widget(self.tree, None)
5694 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5695 menu.show_all()
5696 menu.popup(None, None, None, event_button, event.time)
5698 def make_transport_menu(self, event, titer):
5700 Make transport's popup menu
5702 model = self.modelfilter
5703 jid = model[titer][C_JID].decode('utf-8')
5704 path = model.get_path(titer)
5705 account = model[titer][C_ACCOUNT].decode('utf-8')
5706 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5707 menu = gtk.Menu()
5709 # Send single message
5710 item = gtk.ImageMenuItem(_('Send Single Message'))
5711 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5712 item.set_image(icon)
5713 item.connect('activate',
5714 self.on_send_single_message_menuitem_activate, account, contact)
5715 menu.append(item)
5717 blocked = False
5718 if helpers.jid_is_blocked(account, jid):
5719 blocked = True
5721 # Send Custom Status
5722 send_custom_status_menuitem = gtk.ImageMenuItem(
5723 _('Send Cus_tom Status'))
5724 # add a special img for this menuitem
5725 if blocked:
5726 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5727 'offline'))
5728 send_custom_status_menuitem.set_sensitive(False)
5729 else:
5730 if account in gajim.interface.status_sent_to_users and \
5731 jid in gajim.interface.status_sent_to_users[account]:
5732 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5733 gajim.interface.status_sent_to_users[account][jid]))
5734 else:
5735 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5736 gtk.ICON_SIZE_MENU)
5737 send_custom_status_menuitem.set_image(icon)
5738 status_menuitems = gtk.Menu()
5739 send_custom_status_menuitem.set_submenu(status_menuitems)
5740 iconset = gajim.config.get('iconset')
5741 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5742 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5743 # icon MUST be different instance for every item
5744 state_images = gtkgui_helpers.load_iconset(path)
5745 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5746 status_menuitem.connect('activate', self.on_send_custom_status,
5747 [(contact, account)], s)
5748 icon = state_images[s]
5749 status_menuitem.set_image(icon)
5750 status_menuitems.append(status_menuitem)
5751 menu.append(send_custom_status_menuitem)
5753 item = gtk.SeparatorMenuItem() # separator
5754 menu.append(item)
5756 # Execute Command
5757 item = gtk.ImageMenuItem(_('Execute Command...'))
5758 icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
5759 item.set_image(icon)
5760 menu.append(item)
5761 item.connect('activate', self.on_execute_command, contact, account,
5762 contact.resource)
5763 if gajim.account_is_disconnected(account):
5764 item.set_sensitive(False)
5766 # Manage Transport submenu
5767 item = gtk.ImageMenuItem(_('_Manage Transport'))
5768 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES,
5769 gtk.ICON_SIZE_MENU)
5770 item.set_image(icon)
5771 manage_transport_submenu = gtk.Menu()
5772 item.set_submenu(manage_transport_submenu)
5773 menu.append(item)
5775 # Modify Transport
5776 item = gtk.ImageMenuItem(_('_Modify Transport'))
5777 icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5778 gtk.ICON_SIZE_MENU)
5779 item.set_image(icon)
5780 manage_transport_submenu.append(item)
5781 item.connect('activate', self.on_edit_agent, contact, account)
5782 if gajim.account_is_disconnected(account):
5783 item.set_sensitive(False)
5785 # Rename
5786 item = gtk.ImageMenuItem(_('_Rename'))
5787 # add a special img for rename menuitem
5788 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5789 manage_transport_submenu.append(item)
5790 item.connect('activate', self.on_rename, 'agent', jid, account)
5791 if gajim.account_is_disconnected(account):
5792 item.set_sensitive(False)
5794 item = gtk.SeparatorMenuItem() # separator
5795 manage_transport_submenu.append(item)
5797 # Block
5798 if blocked:
5799 item = gtk.ImageMenuItem(_('_Unblock'))
5800 item.connect('activate', self.on_unblock, [(contact, account)])
5801 else:
5802 item = gtk.ImageMenuItem(_('_Block'))
5803 item.connect('activate', self.on_block, [(contact, account)])
5805 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5806 item.set_image(icon)
5807 manage_transport_submenu.append(item)
5808 if gajim.account_is_disconnected(account):
5809 item.set_sensitive(False)
5811 # Remove
5812 item = gtk.ImageMenuItem(_('_Remove'))
5813 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5814 item.set_image(icon)
5815 manage_transport_submenu.append(item)
5816 item.connect('activate', self.on_remove_agent, [(contact, account)])
5817 if gajim.account_is_disconnected(account):
5818 item.set_sensitive(False)
5820 item = gtk.SeparatorMenuItem() # separator
5821 menu.append(item)
5823 # Information
5824 information_menuitem = gtk.ImageMenuItem(_('_Information'))
5825 icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
5826 information_menuitem.set_image(icon)
5827 menu.append(information_menuitem)
5828 information_menuitem.connect('activate', self.on_info, contact, account)
5830 event_button = gtkgui_helpers.get_possible_button_event(event)
5832 menu.attach_to_widget(self.tree, None)
5833 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5834 menu.show_all()
5835 menu.popup(None, None, None, event_button, event.time)
5837 def make_groupchat_menu(self, event, titer):
5838 model = self.modelfilter
5840 jid = model[titer][C_JID].decode('utf-8')
5841 account = model[titer][C_ACCOUNT].decode('utf-8')
5842 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5843 menu = gtk.Menu()
5845 if jid in gajim.interface.minimized_controls[account]:
5846 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize'))
5847 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
5848 gtk.ICON_SIZE_MENU)
5849 maximize_menuitem.set_image(icon)
5850 maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
5851 jid, account)
5852 menu.append(maximize_menuitem)
5854 if not gajim.gc_connected[account].get(jid, False):
5855 connect_menuitem = gtk.ImageMenuItem(_('_Reconnect'))
5856 connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \
5857 gtk.ICON_SIZE_MENU)
5858 connect_menuitem.set_image(connect_icon)
5859 connect_menuitem.connect('activate', self.on_reconnect, jid,
5860 account)
5861 menu.append(connect_menuitem)
5862 disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect'))
5863 disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \
5864 gtk.ICON_SIZE_MENU)
5865 disconnect_menuitem.set_image(disconnect_icon)
5866 disconnect_menuitem.connect('activate', self.on_disconnect, jid,
5867 account)
5868 menu.append(disconnect_menuitem)
5870 item = gtk.SeparatorMenuItem() # separator
5871 menu.append(item)
5873 history_menuitem = gtk.ImageMenuItem(_('_History'))
5874 history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \
5875 gtk.ICON_SIZE_MENU)
5876 history_menuitem.set_image(history_icon)
5877 history_menuitem .connect('activate', self.on_history, contact, account)
5878 menu.append(history_menuitem)
5880 event_button = gtkgui_helpers.get_possible_button_event(event)
5882 menu.attach_to_widget(self.tree, None)
5883 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5884 menu.show_all()
5885 menu.popup(None, None, None, event_button, event.time)
5887 def get_and_connect_advanced_menuitem_menu(self, account):
5889 Add FOR ACCOUNT options
5891 xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui')
5892 advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
5894 xml_console_menuitem = xml.get_object('xml_console_menuitem')
5895 archiving_preferences_menuitem = xml.get_object(
5896 'archiving_preferences_menuitem')
5897 privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
5898 administrator_menuitem = xml.get_object('administrator_menuitem')
5899 send_server_message_menuitem = xml.get_object(
5900 'send_server_message_menuitem')
5901 set_motd_menuitem = xml.get_object('set_motd_menuitem')
5902 update_motd_menuitem = xml.get_object('update_motd_menuitem')
5903 delete_motd_menuitem = xml.get_object('delete_motd_menuitem')
5905 xml_console_menuitem.connect('activate',
5906 self.on_xml_console_menuitem_activate, account)
5908 if gajim.connections[account]:
5909 if gajim.connections[account].privacy_rules_supported:
5910 privacy_lists_menuitem.connect('activate',
5911 self.on_privacy_lists_menuitem_activate, account)
5912 else:
5913 privacy_lists_menuitem.set_sensitive(False)
5914 if gajim.connections[account].archive_pref_supported:
5915 archiving_preferences_menuitem.connect('activate',
5916 self.on_archiving_preferences_menuitem_activate, account)
5917 else:
5918 archiving_preferences_menuitem.set_sensitive(False)
5920 if gajim.connections[account].is_zeroconf:
5921 administrator_menuitem.set_sensitive(False)
5922 send_server_message_menuitem.set_sensitive(False)
5923 set_motd_menuitem.set_sensitive(False)
5924 update_motd_menuitem.set_sensitive(False)
5925 delete_motd_menuitem.set_sensitive(False)
5926 else:
5927 send_server_message_menuitem.connect('activate',
5928 self.on_send_server_message_menuitem_activate, account)
5930 set_motd_menuitem.connect('activate',
5931 self.on_set_motd_menuitem_activate, account)
5933 update_motd_menuitem.connect('activate',
5934 self.on_update_motd_menuitem_activate, account)
5936 delete_motd_menuitem.connect('activate',
5937 self.on_delete_motd_menuitem_activate, account)
5939 advanced_menuitem_menu.show_all()
5941 return advanced_menuitem_menu
5943 def add_history_manager_menuitem(self, menu):
5945 Add a seperator and History Manager menuitem BELOW for account menuitems
5947 item = gtk.SeparatorMenuItem() # separator
5948 menu.append(item)
5950 # History manager
5951 item = gtk.ImageMenuItem(_('History Manager'))
5952 icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
5953 gtk.ICON_SIZE_MENU)
5954 item.set_image(icon)
5955 menu.append(item)
5956 item.connect('activate', self.on_history_manager_menuitem_activate)
5958 def add_bookmarks_list(self, gc_sub_menu, account):
5960 Show join new group chat item and bookmarks list for an account
5962 item = gtk.ImageMenuItem(_('_Join New Group Chat'))
5963 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5964 item.set_image(icon)
5965 item.connect('activate', self.on_join_gc_activate, account)
5967 gc_sub_menu.append(item)
5969 # User has at least one bookmark.
5970 if gajim.connections[account].bookmarks:
5971 item = gtk.SeparatorMenuItem()
5972 gc_sub_menu.append(item)
5974 for bookmark in gajim.connections[account].bookmarks:
5975 # Do not use underline.
5976 item = gtk.MenuItem(bookmark['name'], False)
5977 item.connect('activate', self.on_bookmark_menuitem_activate,
5978 account, bookmark)
5979 gc_sub_menu.append(item)
5981 def set_actions_menu_needs_rebuild(self):
5982 self.actions_menu_needs_rebuild = True
5983 # Just handle new_chat_menuitem to have ctrl+N working even if we don't
5984 # open the menu
5985 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
5986 ag = gtk.accel_groups_from_object(self.window)[0]
5988 if self.new_chat_menuitem_handler_id:
5989 new_chat_menuitem.handler_disconnect(
5990 self.new_chat_menuitem_handler_id)
5991 self.new_chat_menuitem_handler_id = None
5993 new_chat_menuitem.remove_submenu()
5995 connected_accounts = gajim.get_number_of_connected_accounts()
5996 if connected_accounts == 1 or (connected_accounts == 2 and \
5997 gajim.zeroconf_is_connected()):
5998 # only one 'real' (non-zeroconf) account is connected, don't need
5999 # submenus
6000 accounts_list = sorted(gajim.contacts.get_accounts())
6001 for account in accounts_list:
6002 if gajim.account_is_connected(account) and \
6003 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
6004 if not self.new_chat_menuitem_handler_id:
6005 self.new_chat_menuitem_handler_id = new_chat_menuitem.\
6006 connect('activate',
6007 self.on_new_chat_menuitem_activate, account)
6009 def show_appropriate_context_menu(self, event, iters):
6010 # iters must be all of the same type
6011 model = self.modelfilter
6012 type_ = model[iters[0]][C_TYPE]
6013 for titer in iters[1:]:
6014 if model[titer][C_TYPE] != type_:
6015 return
6016 if type_ == 'group' and len(iters) == 1:
6017 self.make_group_menu(event, iters[0])
6018 if type_ == 'groupchat' and len(iters) == 1:
6019 self.make_groupchat_menu(event, iters[0])
6020 elif type_ == 'agent' and len(iters) == 1:
6021 self.make_transport_menu(event, iters[0])
6022 elif type_ in ('contact', 'self_contact') and len(iters) == 1:
6023 self.make_contact_menu(event, iters[0])
6024 elif type_ == 'contact':
6025 self.make_multiple_contact_menu(event, iters)
6026 elif type_ == 'account' and len(iters) == 1:
6027 self.make_account_menu(event, iters[0])
6029 def show_treeview_menu(self, event):
6030 try:
6031 model, list_of_paths = self.tree.get_selection().get_selected_rows()
6032 except TypeError:
6033 self.tree.get_selection().unselect_all()
6034 return
6035 if not len(list_of_paths):
6036 # no row is selected
6037 return
6038 if len(list_of_paths) > 1:
6039 iters = []
6040 for path in list_of_paths:
6041 iters.append(model.get_iter(path))
6042 else:
6043 path = list_of_paths[0]
6044 iters = [model.get_iter(path)]
6045 self.show_appropriate_context_menu(event, iters)
6047 return True
6049 def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier):
6051 Bring up the conference join dialog, when CTRL+J accelerator is being
6052 activated
6054 # find a connected account:
6055 for account in gajim.connections:
6056 if gajim.account_is_connected(account):
6057 break
6058 self.on_join_gc_activate(None, account)
6059 return True
6061 def fill_column(self, col):
6062 for rend in self.renderers_list:
6063 col.pack_start(rend[1], expand=rend[2])
6064 col.add_attribute(rend[1], rend[3], rend[4])
6065 col.set_cell_data_func(rend[1], rend[5], rend[6])
6066 # set renderers propertys
6067 for renderer in self.renderers_propertys.keys():
6068 renderer.set_property(self.renderers_propertys[renderer][0],
6069 self.renderers_propertys[renderer][1])
6071 ################################################################################
6073 ################################################################################
6075 def __init__(self):
6076 self.filtering = False
6077 # Number of renderers plugins added
6078 self.nb_ext_renderers = 0
6079 # [icon, name, type, jid, account, editable, mood_pixbuf,
6080 # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
6081 # padlock_pixbuf]
6082 self.columns = [gtk.Image, str, str, str, str,
6083 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
6084 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf]
6085 self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
6086 self.window = self.xml.get_object('roster_window')
6087 self.hpaned = self.xml.get_object('roster_hpaned')
6088 gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
6089 gajim.interface.msg_win_mgr.connect('window-delete',
6090 self.on_message_window_delete)
6091 self.advanced_menus = [] # We keep them to destroy them
6092 if gajim.config.get('roster_window_skip_taskbar'):
6093 self.window.set_property('skip-taskbar-hint', True)
6094 self.tree = self.xml.get_object('roster_treeview')
6095 sel = self.tree.get_selection()
6096 sel.set_mode(gtk.SELECTION_MULTIPLE)
6097 # sel.connect('changed',
6098 # self.on_treeview_selection_changed)
6100 self._iters = {}
6101 # for merged mode
6102 self._iters['MERGED'] = {'account': None, 'groups': {}}
6103 # holds a list of (jid, account) tupples
6104 self._last_selected_contact = []
6105 self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
6106 'closed': {}}
6108 self.last_save_dir = None
6109 self.editing_path = None # path of row with cell in edit mode
6110 self.add_new_contact_handler_id = False
6111 self.service_disco_handler_id = False
6112 self.new_chat_menuitem_handler_id = False
6113 self.single_message_menuitem_handler_id = False
6114 self.profile_avatar_menuitem_handler_id = False
6115 #FIXME: When list_accel_closures will be wrapped in pygtk
6116 # no need of this variable
6117 self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
6118 self.set_actions_menu_needs_rebuild()
6119 self.regroup = gajim.config.get('mergeaccounts')
6120 self.clicked_path = None # Used remember on wich row we clicked
6121 if len(gajim.connections) < 2:
6122 # Do not merge accounts if only one exists
6123 self.regroup = False
6124 gtkgui_helpers.resize_window(self.window,
6125 gajim.config.get('roster_width'),
6126 gajim.config.get('roster_height'))
6127 gtkgui_helpers.move_window(self.window,
6128 gajim.config.get('roster_x-position'),
6129 gajim.config.get('roster_y-position'))
6131 self.popups_notification_height = 0
6132 self.popup_notification_windows = []
6134 # Remove contact from roster when last event opened
6135 # { (contact, account): { backend: boolean }
6136 self.contacts_to_be_removed = {}
6137 gajim.events.event_removed_subscribe(self.on_event_removed)
6139 # when this value become 0 we quit main application. If it's more than 0
6140 # it means we are waiting for this number of accounts to disconnect
6141 # before quitting
6142 self.quit_on_next_offline = -1
6144 # uf_show, img, show, sensitive
6145 liststore = gtk.ListStore(str, gtk.Image, str, bool)
6146 self.status_combobox = self.xml.get_object('status_combobox')
6148 cell = cell_renderer_image.CellRendererImage(0, 1)
6149 self.status_combobox.pack_start(cell, False)
6151 # img to show is in in 2nd column of liststore
6152 self.status_combobox.add_attribute(cell, 'image', 1)
6153 # if it will be sensitive or not it is in the fourth column
6154 # all items in the 'row' must have sensitive to False
6155 # if we want False (so we add it for img_cell too)
6156 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6158 cell = gtk.CellRendererText()
6159 cell.set_property('xpad', 5) # padding for status text
6160 self.status_combobox.pack_start(cell, True)
6161 # text to show is in in first column of liststore
6162 self.status_combobox.add_attribute(cell, 'text', 0)
6163 # if it will be sensitive or not it is in the fourth column
6164 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6166 self.status_combobox.set_row_separator_func(self._iter_is_separator)
6168 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
6169 uf_show = helpers.get_uf_show(show)
6170 liststore.append([uf_show,
6171 gajim.interface.jabber_state_images['16'][show], show, True])
6172 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6173 liststore.append(['SEPARATOR', None, '', True])
6175 path = gtkgui_helpers.get_icon_path('gajim-kbd_input')
6176 img = gtk.Image()
6177 img.set_from_file(path)
6178 # sensitivity to False because by default we're offline
6179 self.status_message_menuitem_iter = liststore.append(
6180 [_('Change Status Message...'), img, '', False])
6181 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6182 liststore.append(['SEPARATOR', None, '', True])
6184 uf_show = helpers.get_uf_show('offline')
6185 liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
6186 'offline'], 'offline', True])
6188 status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
6189 'invisible', 'separator1', 'change_status_msg', 'separator2',
6190 'offline']
6191 self.status_combobox.set_model(liststore)
6193 # default to offline
6194 number_of_menuitem = status_combobox_items.index('offline')
6195 self.status_combobox.set_active(number_of_menuitem)
6197 # holds index to previously selected item so if
6198 # "change status message..." is selected we can fallback to previously
6199 # selected item and not stay with that item selected
6200 self.previous_status_combobox_active = number_of_menuitem
6202 showOffline = gajim.config.get('showoffline')
6203 showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online')
6205 w = self.xml.get_object('show_offline_contacts_menuitem')
6206 w.set_active(showOffline)
6207 if showOnlyChatAndOnline:
6208 w.set_sensitive(False)
6210 w = self.xml.get_object('show_only_active_contacts_menuitem')
6211 w.set_active(showOnlyChatAndOnline)
6212 if showOffline:
6213 w.set_sensitive(False)
6215 show_transports_group = gajim.config.get('show_transports_group')
6216 self.xml.get_object('show_transports_menuitem').set_active(
6217 show_transports_group)
6219 self.xml.get_object('show_roster_menuitem').set_active(True)
6221 # columns
6222 col = gtk.TreeViewColumn()
6223 # list of renderers with attributes / properties in the form:
6224 # (name, renderer_object, expand?, attribute_name, attribute_value,
6225 # cell_data_func, func_arg)
6226 self.renderers_list = []
6227 self.renderers_propertys ={}
6228 self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF,
6229 'activity': C_ACTIVITY_PIXBUF, 'tune': C_TUNE_PIXBUF,
6230 'location': C_LOCATION_PIXBUF}
6232 renderer_text = gtk.CellRendererText()
6233 self.renderers_propertys[renderer_text] = ('ellipsize',
6234 pango.ELLIPSIZE_END)
6236 def add_avatar_renderer():
6237 self.renderers_list.append(('avatar', gtk.CellRendererPixbuf(),
6238 False, 'pixbuf', C_AVATAR_PIXBUF,
6239 self._fill_avatar_pixbuf_renderer, None))
6241 if gajim.config.get('avatar_position_in_roster') == 'left':
6242 add_avatar_renderer()
6244 self.renderers_list += (
6245 ('icon', cell_renderer_image.CellRendererImage(0, 0), False,
6246 'image', C_IMG, self._iconCellDataFunc, None),
6248 ('name', renderer_text, True,
6249 'markup', C_NAME, self._nameCellDataFunc, None),
6251 ('mood', gtk.CellRendererPixbuf(), False,
6252 'pixbuf', C_MOOD_PIXBUF,
6253 self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF),
6255 ('activity', gtk.CellRendererPixbuf(), False,
6256 'pixbuf', C_ACTIVITY_PIXBUF,
6257 self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF),
6259 ('tune', gtk.CellRendererPixbuf(), False,
6260 'pixbuf', C_TUNE_PIXBUF,
6261 self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF),
6263 ('location', gtk.CellRendererPixbuf(), False,
6264 'pixbuf', C_LOCATION_PIXBUF,
6265 self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF))
6267 if gajim.config.get('avatar_position_in_roster') == 'right':
6268 add_avatar_renderer()
6270 self.renderers_list.append(('padlock', gtk.CellRendererPixbuf(), False,
6271 'pixbuf', C_PADLOCK_PIXBUF,
6272 self._fill_padlock_pixbuf_renderer, None))
6274 # fill and append column
6275 self.fill_column(col)
6276 self.tree.append_column(col)
6278 # do not show gtk arrows workaround
6279 col = gtk.TreeViewColumn()
6280 render_pixbuf = gtk.CellRendererPixbuf()
6281 col.pack_start(render_pixbuf, expand=False)
6282 self.tree.append_column(col)
6283 col.set_visible(False)
6284 self.tree.set_expander_column(col)
6286 # set search function
6287 self.tree.set_search_equal_func(self._search_roster_func)
6289 # signals
6290 self.TARGET_TYPE_URI_LIST = 80
6291 TARGETS = [('MY_TREE_MODEL_ROW',
6292 gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)]
6293 TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
6294 ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)]
6295 self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS,
6296 gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY)
6297 self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT)
6298 self.tree.connect('drag_begin', self.drag_begin)
6299 self.tree.connect('drag_end', self.drag_end)
6300 self.tree.connect('drag_drop', self.drag_drop)
6301 self.tree.connect('drag_data_get', self.drag_data_get_data)
6302 self.tree.connect('drag_data_received', self.drag_data_received_data)
6303 self.dragging = False
6304 self.xml.connect_signals(self)
6305 self.combobox_callback_active = True
6307 self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
6308 self.tooltip = tooltips.RosterTooltip()
6309 # Workaroung: For strange reasons signal is behaving like row-changed
6310 self._toggeling_row = False
6311 self.setup_and_draw_roster()
6313 if gajim.config.get('show_roster_on_startup') == 'always':
6314 self.window.show_all()
6315 elif gajim.config.get('show_roster_on_startup') == 'never':
6316 if gajim.config.get('trayicon') != 'always':
6317 # Without trayicon, user should see the roster!
6318 self.window.show_all()
6319 gajim.config.set('last_roster_visible', True)
6320 else:
6321 if gajim.config.get('last_roster_visible') or \
6322 gajim.config.get('trayicon') != 'always':
6323 self.window.show_all()
6325 if len(gajim.connections) == 0: # if we have no account
6326 def _open_wizard():
6327 gajim.interface.instances['account_creation_wizard'] = \
6328 config.AccountCreationWizardWindow()
6329 # Open wizard only after roster is created, so we can make it
6330 # transient for the roster window
6331 gobject.idle_add(_open_wizard)
6332 if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
6333 # Create zeroconf in config file
6334 from common.zeroconf import connection_zeroconf
6335 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
6337 # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
6338 # conference.
6339 accel_group = gtk.accel_groups_from_object(self.window)[0]
6340 accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK,
6341 gtk.ACCEL_MASK, self.on_ctrl_j)
6343 # Setting CTRL+N to be the shortcut for show Start chat dialog
6344 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
6345 new_chat_menuitem.add_accelerator('activate', accel_group,
6346 gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
6348 gajim.ged.register_event_handler('presence-received', ged.GUI1,
6349 self._nec_presence_received)
6350 # presence has to be fully handled so that contact is added to occupant
6351 # list before roster can be correctly updated
6352 gajim.ged.register_event_handler('gc-presence-received', ged.GUI2,
6353 self._nec_gc_presence_received)
6354 gajim.ged.register_event_handler('roster-received', ged.GUI1,
6355 self._nec_roster_received)
6356 gajim.ged.register_event_handler('anonymous-auth', ged.GUI1,
6357 self._nec_anonymous_auth)
6358 gajim.ged.register_event_handler('our-show', ged.GUI1,
6359 self._nec_our_show)
6360 gajim.ged.register_event_handler('connection-type', ged.GUI1,
6361 self._nec_connection_type)
6362 gajim.ged.register_event_handler('agent-removed', ged.GUI1,
6363 self._nec_agent_removed)
6364 gajim.ged.register_event_handler('pep-received', ged.GUI1,
6365 self._nec_pep_received)
6366 gajim.ged.register_event_handler('vcard-received', ged.GUI1,
6367 self._nec_vcard_received)
6368 gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
6369 self._nec_gc_subject_received)
6370 gajim.ged.register_event_handler('metacontacts-received', ged.GUI2,
6371 self._nec_metacontacts_received)
6372 gajim.ged.register_event_handler('signed-in', ged.GUI1,
6373 self._nec_signed_in)