add an option to allow hidding roster even if systray is not shown. Fixes #6930
[gajim.git] / src / roster_window.py
blob77d41987bd84541c858627c01dc8ca1face9251c
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': {}}
1421 for acct in gajim.contacts.get_accounts():
1422 self._iters[acct] = {'account': None, 'groups': {}, 'contacts': {}}
1424 for acct in gajim.contacts.get_accounts():
1425 self.add_account(acct)
1426 self.add_account_contacts(acct)
1428 # Recalculate column width for ellipsizing
1429 self.tree.columns_autosize()
1432 def select_contact(self, jid, account):
1434 Select contact in roster. If contact is hidden but has events, show him
1436 # Refiltering SHOULD NOT be needed:
1437 # When a contact gets a new event he will be redrawn and his
1438 # icon changes, so _visible_func WILL be called on him anyway
1439 iters = self._get_contact_iter(jid, account)
1440 if not iters:
1441 # Not visible in roster
1442 return
1443 path = self.modelfilter.get_path(iters[0])
1444 if self.dragging or not gajim.config.get(
1445 'scroll_roster_to_last_message'):
1446 # do not change selection while DND'ing
1447 return
1448 # Expand his parent, so this path is visible, don't expand it.
1449 self.tree.expand_to_path(path[:-1])
1450 self.tree.scroll_to_cell(path)
1451 self.tree.set_cursor(path)
1454 def _adjust_account_expand_collapse_state(self, account):
1456 Expand/collapse account row based on self.collapsed_rows
1458 if not self.tree.get_model():
1459 return
1460 iterA = self._get_account_iter(account)
1461 if not iterA:
1462 # thank you modelfilter
1463 return
1464 path = self.modelfilter.get_path(iterA)
1465 if account in self.collapsed_rows:
1466 self.tree.collapse_row(path)
1467 else:
1468 self.tree.expand_row(path, False)
1469 return False
1472 def _adjust_group_expand_collapse_state(self, group, account):
1474 Expand/collapse group row based on self.collapsed_rows
1476 if not self.tree.get_model():
1477 return
1478 delimiter = gajim.connections[account].nested_group_delimiter
1479 group_splited = group.split(delimiter)
1480 i = 1
1481 while i < len(group_splited) + 1:
1482 g = delimiter.join(group_splited[:i])
1483 iterG = self._get_group_iter(g, account)
1484 if not iterG:
1485 # Group not visible
1486 return
1487 path = self.modelfilter.get_path(iterG)
1488 if account + g in self.collapsed_rows:
1489 self.tree.collapse_row(path)
1490 else:
1491 self.tree.expand_row(path, False)
1492 i += 1
1494 ##############################################################################
1495 ### Roster and Modelfilter handling
1496 ##############################################################################
1498 def refilter_shown_roster_items(self):
1499 self.filtering = True
1500 self.modelfilter.refilter()
1501 self.filtering = False
1503 def contact_has_pending_roster_events(self, contact, account):
1505 Return True if the contact or one if it resources has pending events
1507 # jid has pending events
1508 if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
1509 return True
1510 # check events of all resources
1511 for contact_ in gajim.contacts.get_contacts(account, contact.jid):
1512 if contact_.resource and gajim.events.get_nb_roster_events(account,
1513 contact_.get_full_jid()) > 0:
1514 return True
1515 return False
1517 def contact_is_visible(self, contact, account):
1518 if self.contact_has_pending_roster_events(contact, account):
1519 return True
1521 if contact.show in ('offline', 'error'):
1522 if contact.jid in gajim.to_be_removed[account]:
1523 return True
1524 return False
1525 if gajim.config.get('show_only_chat_and_online') and contact.show in (
1526 'away', 'xa', 'busy'):
1527 return False
1528 return True
1530 def _visible_func(self, model, titer):
1532 Determine whether iter should be visible in the treeview
1534 type_ = model[titer][C_TYPE]
1535 if not type_:
1536 return False
1537 if type_ == 'account':
1538 # Always show account
1539 return True
1541 account = model[titer][C_ACCOUNT]
1542 if not account:
1543 return False
1545 account = account.decode('utf-8')
1546 jid = model[titer][C_JID]
1547 if not jid:
1548 return False
1549 jid = jid.decode('utf-8')
1550 if type_ == 'group':
1551 group = jid
1552 if group == _('Transports'):
1553 if self.regroup:
1554 accounts = gajim.contacts.get_accounts()
1555 else:
1556 accounts = [account]
1557 for _acc in accounts:
1558 for contact in gajim.contacts.iter_contacts(_acc):
1559 if group in contact.get_shown_groups() and \
1560 self.contact_has_pending_roster_events(contact, _acc):
1561 return True
1562 return gajim.config.get('show_transports_group') and \
1563 (gajim.account_is_connected(account) or \
1564 gajim.config.get('showoffline'))
1565 if gajim.config.get('showoffline'):
1566 return True
1568 if self.regroup:
1569 # C_ACCOUNT for groups depends on the order
1570 # accounts were connected
1571 # Check all accounts for online group contacts
1572 accounts = gajim.contacts.get_accounts()
1573 else:
1574 accounts = [account]
1575 for _acc in accounts:
1576 delimiter = gajim.connections[_acc].nested_group_delimiter
1577 for contact in gajim.contacts.iter_contacts(_acc):
1578 if not self.contact_is_visible(contact, _acc):
1579 continue
1580 # Is this contact in this group?
1581 for grp in contact.get_shown_groups():
1582 while grp:
1583 if group == grp:
1584 return True
1585 grp = delimiter.join(grp.split(delimiter)[:-1])
1586 return False
1587 if type_ == 'contact':
1588 if self.rfilter_enabled:
1589 return self.rfilter_string in model[titer][C_NAME].lower()
1590 if gajim.config.get('showoffline'):
1591 return True
1592 bb_jid = None
1593 bb_account = None
1594 family = gajim.contacts.get_metacontacts_family(account, jid)
1595 if family:
1596 nearby_family, bb_jid, bb_account = \
1597 self._get_nearby_family_and_big_brother(family, account)
1598 if (bb_jid, bb_account) == (jid, account):
1599 # Show the big brother if a child has pending events
1600 for data in nearby_family:
1601 jid = data['jid']
1602 account = data['account']
1603 contact = gajim.contacts.get_contact_with_highest_priority(
1604 account, jid)
1605 if contact and self.contact_is_visible(contact, account):
1606 return True
1607 return False
1608 else:
1609 contact = gajim.contacts.get_contact_with_highest_priority(
1610 account, jid)
1611 return self.contact_is_visible(contact, account)
1612 if type_ == 'agent':
1613 contact = gajim.contacts.get_contact_with_highest_priority(account,
1614 jid)
1615 return self.contact_has_pending_roster_events(contact, account) or \
1616 (gajim.config.get('show_transports_group') and \
1617 (gajim.account_is_connected(account) or \
1618 gajim.config.get('showoffline')))
1619 return True
1621 def _compareIters(self, model, iter1, iter2, data=None):
1623 Compare two iters to sort them
1625 name1 = model[iter1][C_NAME]
1626 name2 = model[iter2][C_NAME]
1627 if not name1 or not name2:
1628 return 0
1629 name1 = name1.decode('utf-8')
1630 name2 = name2.decode('utf-8')
1631 type1 = model[iter1][C_TYPE]
1632 type2 = model[iter2][C_TYPE]
1633 if type1 == 'self_contact':
1634 return -1
1635 if type2 == 'self_contact':
1636 return 1
1637 if type1 == 'group':
1638 name1 = model[iter1][C_JID]
1639 name2 = model[iter2][C_JID]
1640 if name1 == _('Transports'):
1641 return 1
1642 if name2 == _('Transports'):
1643 return -1
1644 if name1 == _('Not in Roster'):
1645 return 1
1646 if name2 == _('Not in Roster'):
1647 return -1
1648 if name1 == _('Groupchats'):
1649 return 1
1650 if name2 == _('Groupchats'):
1651 return -1
1652 account1 = model[iter1][C_ACCOUNT]
1653 account2 = model[iter2][C_ACCOUNT]
1654 if not account1 or not account2:
1655 return 0
1656 account1 = account1.decode('utf-8')
1657 account2 = account2.decode('utf-8')
1658 if type1 == 'account':
1659 return locale.strcoll(account1, account2)
1660 jid1 = model[iter1][C_JID].decode('utf-8')
1661 jid2 = model[iter2][C_JID].decode('utf-8')
1662 if type1 == 'contact':
1663 lcontact1 = gajim.contacts.get_contacts(account1, jid1)
1664 contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
1665 if not contact1:
1666 return 0
1667 name1 = contact1.get_shown_name()
1668 if type2 == 'contact':
1669 lcontact2 = gajim.contacts.get_contacts(account2, jid2)
1670 contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
1671 if not contact2:
1672 return 0
1673 name2 = contact2.get_shown_name()
1674 # We first compare by show if sort_by_show_in_roster is True or if it's
1675 # a child contact
1676 if type1 == 'contact' and type2 == 'contact' and \
1677 gajim.config.get('sort_by_show_in_roster'):
1678 cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
1679 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
1680 s = self.get_show(lcontact1)
1681 show1 = cshow.get(s, 9)
1682 s = self.get_show(lcontact2)
1683 show2 = cshow.get(s, 9)
1684 removing1 = False
1685 removing2 = False
1686 if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
1687 removing1 = True
1688 if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
1689 removing2 = True
1690 if removing1 and not removing2:
1691 return 1
1692 if removing2 and not removing1:
1693 return -1
1694 sub1 = contact1.sub
1695 sub2 = contact2.sub
1696 # none and from goes after
1697 if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
1698 return -1
1699 if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
1700 return 1
1701 if show1 < show2:
1702 return -1
1703 elif show1 > show2:
1704 return 1
1705 # We compare names
1706 cmp_result = locale.strcoll(name1.lower(), name2.lower())
1707 if cmp_result < 0:
1708 return -1
1709 if cmp_result > 0:
1710 return 1
1711 if type1 == 'contact' and type2 == 'contact':
1712 # We compare account names
1713 cmp_result = locale.strcoll(account1.lower(), account2.lower())
1714 if cmp_result < 0:
1715 return -1
1716 if cmp_result > 0:
1717 return 1
1718 # We compare jids
1719 cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
1720 if cmp_result < 0:
1721 return -1
1722 if cmp_result > 0:
1723 return 1
1724 return 0
1726 ################################################################################
1727 ### FIXME: Methods that don't belong to roster window...
1728 ### ... atleast not in there current form
1729 ################################################################################
1731 def fire_up_unread_messages_events(self, account):
1733 Read from db the unread messages, and fire them up, and if we find very
1734 old unread messages, delete them from unread table
1736 results = gajim.logger.get_unread_msgs()
1737 for result in results:
1738 jid = result[4]
1739 shown = result[5]
1740 if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
1741 shown:
1742 # We have this jid in our contacts list
1743 # XXX unread messages should probably have their session saved
1744 # with them
1745 session = gajim.connections[account].make_new_session(jid)
1747 tim = time.localtime(float(result[2]))
1748 session.roster_message(jid, result[1], tim, msg_type='chat',
1749 msg_id=result[0])
1750 gajim.logger.set_shown_unread_msgs(result[0])
1752 elif (time.time() - result[2]) > 2592000:
1753 # ok, here we see that we have a message in unread messages
1754 # table that is older than a month. It is probably from someone
1755 # not in our roster for accounts we usually launch, so we will
1756 # delete this id from unread message tables.
1757 gajim.logger.set_read_messages([result[0]])
1759 def fill_contacts_and_groups_dicts(self, array, account):
1761 Fill gajim.contacts and gajim.groups
1763 # FIXME: This function needs to be splitted
1764 # Most of the logic SHOULD NOT be done at GUI level
1765 if account not in gajim.contacts.get_accounts():
1766 gajim.contacts.add_account(account)
1767 if not account in self._iters:
1768 self._iters[account] = {'account': None, 'groups': {},
1769 'contacts': {}}
1770 if account not in gajim.groups:
1771 gajim.groups[account] = {}
1772 if gajim.config.get('show_self_contact') == 'always':
1773 self_jid = gajim.get_jid_from_account(account)
1774 if gajim.connections[account].server_resource:
1775 self_jid += '/' + gajim.connections[account].server_resource
1776 array[self_jid] = {'name': gajim.nicks[account],
1777 'groups': ['self_contact'], 'subscription': 'both',
1778 'ask': 'none'}
1779 # .keys() is needed
1780 for jid in array.keys():
1781 # Remove the contact in roster. It might has changed
1782 self.remove_contact(jid, account, force=True)
1783 # Remove old Contact instances
1784 gajim.contacts.remove_jid(account, jid, remove_meta=False)
1785 jids = jid.split('/')
1786 # get jid
1787 ji = jids[0]
1788 # get resource
1789 resource = ''
1790 if len(jids) > 1:
1791 resource = '/'.join(jids[1:])
1792 # get name
1793 name = array[jid]['name'] or ''
1794 show = 'offline' # show is offline by default
1795 status = '' # no status message by default
1797 keyID = ''
1798 attached_keys = gajim.config.get_per('accounts', account,
1799 'attached_gpg_keys').split()
1800 if jid in attached_keys:
1801 keyID = attached_keys[attached_keys.index(jid) + 1]
1803 if gajim.jid_is_transport(jid):
1804 array[jid]['groups'] = [_('Transports')]
1805 #TRANSP - potential
1806 contact1 = gajim.contacts.create_contact(jid=ji, account=account,
1807 name=name, groups=array[jid]['groups'], show=show,
1808 status=status, sub=array[jid]['subscription'],
1809 ask=array[jid]['ask'], resource=resource, keyID=keyID)
1810 gajim.contacts.add_contact(account, contact1)
1812 if gajim.config.get('ask_avatars_on_startup'):
1813 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
1814 if pixbuf == 'ask':
1815 transport = gajim.get_transport_name_from_jid(contact1.jid)
1816 if not transport or gajim.jid_is_transport(contact1.jid):
1817 jid_with_resource = contact1.jid
1818 if contact1.resource:
1819 jid_with_resource += '/' + contact1.resource
1820 gajim.connections[account].request_vcard(
1821 jid_with_resource)
1822 else:
1823 host = gajim.get_server_from_jid(contact1.jid)
1824 if host not in gajim.transport_avatar[account]:
1825 gajim.transport_avatar[account][host] = \
1826 [contact1.jid]
1827 else:
1828 gajim.transport_avatar[account][host].append(
1829 contact1.jid)
1831 # If we already have chat windows opened, update them with new
1832 # contact instance
1833 chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
1834 if chat_control:
1835 chat_control.contact = contact1
1837 def connected_rooms(self, account):
1838 if account in gajim.gc_connected[account].values():
1839 return True
1840 return False
1842 def on_event_removed(self, event_list):
1844 Remove contacts on last events removed
1846 Only performed if removal was requested before but the contact still had
1847 pending events
1849 contact_list = ((event.jid.split('/')[0], event.account) for event in \
1850 event_list)
1852 for jid, account in contact_list:
1853 self.draw_contact(jid, account)
1854 # Remove contacts in roster if removal was requested
1855 key = (jid, account)
1856 if key in self.contacts_to_be_removed.keys():
1857 backend = self.contacts_to_be_removed[key]['backend']
1858 del self.contacts_to_be_removed[key]
1859 # Remove contact will delay removal if there are more events
1860 # pending
1861 self.remove_contact(jid, account, backend=backend)
1862 self.show_title()
1864 def open_event(self, account, jid, event):
1866 If an event was handled, return True, else return False
1868 data = event.parameters
1869 ft = gajim.interface.instances['file_transfers']
1870 event = gajim.events.get_first_event(account, jid, event.type_)
1871 if event.type_ == 'normal':
1872 dialogs.SingleMessageWindow(account, jid,
1873 action='receive', from_whom=jid, subject=data[1],
1874 message=data[0], resource=data[5], session=data[8],
1875 form_node=data[9])
1876 gajim.events.remove_events(account, jid, event)
1877 return True
1878 elif event.type_ == 'file-request':
1879 contact = gajim.contacts.get_contact_with_highest_priority(account,
1880 jid)
1881 ft.show_file_request(account, contact, data)
1882 gajim.events.remove_events(account, jid, event)
1883 return True
1884 elif event.type_ in ('file-request-error', 'file-send-error'):
1885 ft.show_send_error(data)
1886 gajim.events.remove_events(account, jid, event)
1887 return True
1888 elif event.type_ in ('file-error', 'file-stopped'):
1889 msg_err = ''
1890 if data['error'] == -1:
1891 msg_err = _('Remote contact stopped transfer')
1892 elif data['error'] == -6:
1893 msg_err = _('Error opening file')
1894 ft.show_stopped(jid, data, error_msg=msg_err)
1895 gajim.events.remove_events(account, jid, event)
1896 return True
1897 elif event.type_ == 'file-completed':
1898 ft.show_completed(jid, data)
1899 gajim.events.remove_events(account, jid, event)
1900 return True
1901 elif event.type_ == 'gc-invitation':
1902 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1903 data[1])
1904 gajim.events.remove_events(account, jid, event)
1905 return True
1906 elif event.type_ == 'subscription_request':
1907 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1908 gajim.events.remove_events(account, jid, event)
1909 return True
1910 elif event.type_ == 'unsubscribed':
1911 gajim.interface.show_unsubscribed_dialog(account, data)
1912 gajim.events.remove_events(account, jid, event)
1913 return True
1914 elif event.type_ == 'jingle-incoming':
1915 peerjid, sid, content_types = data
1916 dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
1917 gajim.events.remove_events(account, jid, event)
1918 return True
1919 return False
1921 ################################################################################
1922 ### This and that... random.
1923 ################################################################################
1925 def show_roster_vbox(self, active):
1926 vb = self.xml.get_object('roster_vbox2')
1927 if active:
1928 vb.set_no_show_all(False)
1929 vb.show()
1930 else:
1931 vb.hide()
1932 vb.set_no_show_all(True)
1934 def show_tooltip(self, contact):
1935 pointer = self.tree.get_pointer()
1936 props = self.tree.get_path_at_pos(pointer[0], pointer[1])
1937 # check if the current pointer is at the same path
1938 # as it was before setting the timeout
1939 if props and self.tooltip.id == props[0]:
1940 # bounding rectangle of coordinates for the cell within the treeview
1941 rect = self.tree.get_cell_area(props[0], props[1])
1943 # position of the treeview on the screen
1944 position = self.tree.window.get_origin()
1945 self.tooltip.show_tooltip(contact, rect.height, position[1] + \
1946 rect.y)
1947 else:
1948 self.tooltip.hide_tooltip()
1951 def authorize(self, widget, jid, account):
1953 Authorize a contact (by re-sending auth menuitem)
1955 gajim.connections[account].send_authorization(jid)
1956 dialogs.InformationDialog(_('Authorization has been sent'),
1957 _('Now "%s" will know your status.') %jid)
1959 def req_sub(self, widget, jid, txt, account, groups=None, nickname=None,
1960 auto_auth=False):
1962 Request subscription to a contact
1964 groups_list = groups or []
1965 gajim.connections[account].request_subscription(jid, txt, nickname,
1966 groups_list, auto_auth, gajim.nicks[account])
1967 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
1968 if not contact:
1969 keyID = ''
1970 attached_keys = gajim.config.get_per('accounts', account,
1971 'attached_gpg_keys').split()
1972 if jid in attached_keys:
1973 keyID = attached_keys[attached_keys.index(jid) + 1]
1974 contact = gajim.contacts.create_contact(jid=jid, account=account,
1975 name=nickname, groups=groups_list, show='requested', status='',
1976 ask='none', sub='subscribe', keyID=keyID)
1977 gajim.contacts.add_contact(account, contact)
1978 else:
1979 if not _('Not in Roster') in contact.get_shown_groups():
1980 dialogs.InformationDialog(_('Subscription request has been '
1981 'sent'), _('If "%s" accepts this request you will know his '
1982 'or her status.') % jid)
1983 return
1984 self.remove_contact(contact.jid, account, force=True)
1985 contact.groups = groups_list
1986 if nickname:
1987 contact.name = nickname
1988 self.add_contact(jid, account)
1990 def revoke_auth(self, widget, jid, account):
1992 Revoke a contact's authorization
1994 gajim.connections[account].refuse_authorization(jid)
1995 dialogs.InformationDialog(_('Authorization has been removed'),
1996 _('Now "%s" will always see you as offline.') %jid)
1998 def set_state(self, account, state):
1999 child_iterA = self._get_account_iter(account, self.model)
2000 if child_iterA:
2001 self.model[child_iterA][0] = \
2002 gajim.interface.jabber_state_images['16'][state]
2003 if gajim.interface.systray_enabled:
2004 gajim.interface.systray.change_status(state)
2006 def set_connecting_state(self, account):
2007 self.set_state(account, 'connecting')
2009 def send_status(self, account, status, txt, auto=False, to=None):
2010 if status != 'offline':
2011 if to is None:
2012 if status == gajim.connections[account].get_status() and \
2013 txt == gajim.connections[account].status:
2014 return
2015 gajim.config.set_per('accounts', account, 'last_status', status)
2016 gajim.config.set_per('accounts', account, 'last_status_msg',
2017 helpers.to_one_line(txt))
2018 if gajim.connections[account].connected < 2:
2019 self.set_connecting_state(account)
2021 keyid = gajim.config.get_per('accounts', account, 'keyid')
2022 if keyid and not gajim.connections[account].gpg:
2023 dialogs.WarningDialog(_('GPG is not usable'),
2024 _('You will be connected to %s without OpenPGP.') % \
2025 account)
2027 self.send_status_continue(account, status, txt, auto, to)
2029 def send_pep(self, account, pep_dict):
2030 connection = gajim.connections[account]
2032 if 'activity' in pep_dict:
2033 activity = pep_dict['activity']
2034 subactivity = pep_dict.get('subactivity', None)
2035 activity_text = pep_dict.get('activity_text', None)
2036 connection.send_activity(activity, subactivity, activity_text)
2037 else:
2038 connection.retract_activity()
2040 if 'mood' in pep_dict:
2041 mood = pep_dict['mood']
2042 mood_text = pep_dict.get('mood_text', None)
2043 connection.send_mood(mood, mood_text)
2044 else:
2045 connection.retract_mood()
2047 def delete_pep(self, jid, account):
2048 if jid == gajim.get_jid_from_account(account):
2049 gajim.connections[account].pep = {}
2050 self.draw_account(account)
2052 for contact in gajim.contacts.get_contacts(account, jid):
2053 contact.pep = {}
2055 self.draw_all_pep_types(jid, account)
2056 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
2057 if ctrl:
2058 ctrl.update_all_pep_types()
2060 def send_status_continue(self, account, status, txt, auto, to):
2061 if gajim.account_is_connected(account) and not to:
2062 if status == 'online' and gajim.interface.sleeper.getState() != \
2063 common.sleepy.STATE_UNKNOWN:
2064 gajim.sleeper_state[account] = 'online'
2065 elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \
2066 status == 'offline':
2067 gajim.sleeper_state[account] = 'off'
2069 if to:
2070 gajim.connections[account].send_custom_status(status, txt, to)
2071 else:
2072 if status in ('invisible', 'offline'):
2073 self.delete_pep(gajim.get_jid_from_account(account), account)
2074 was_invisible = gajim.connections[account].connected == \
2075 gajim.SHOW_LIST.index('invisible')
2076 gajim.connections[account].change_status(status, txt, auto)
2078 if account in gajim.interface.status_sent_to_users:
2079 gajim.interface.status_sent_to_users[account] = {}
2080 if account in gajim.interface.status_sent_to_groups:
2081 gajim.interface.status_sent_to_groups[account] = {}
2082 for gc_control in gajim.interface.msg_win_mgr.get_controls(
2083 message_control.TYPE_GC) + \
2084 gajim.interface.minimized_controls[account].values():
2085 if gc_control.account == account:
2086 if gajim.gc_connected[account][gc_control.room_jid]:
2087 gajim.connections[account].send_gc_status(
2088 gc_control.nick, gc_control.room_jid, status, txt)
2089 else:
2090 # for some reason, we are not connected to the room even
2091 # if tab is opened, send initial join_gc()
2092 gajim.connections[account].join_gc(gc_control.nick,
2093 gc_control.room_jid, None)
2094 if was_invisible and status != 'offline':
2095 # We come back from invisible, join bookmarks
2096 gajim.interface.auto_join_bookmarks(account)
2099 def chg_contact_status(self, contact, show, status, account):
2101 When a contact changes his or her status
2103 contact_instances = gajim.contacts.get_contacts(account, contact.jid)
2104 contact.show = show
2105 contact.status = status
2106 # name is to show in conversation window
2107 name = contact.get_shown_name()
2108 fjid = contact.get_full_jid()
2110 # The contact has several resources
2111 if len(contact_instances) > 1:
2112 if contact.resource != '':
2113 name += '/' + contact.resource
2115 # Remove resource when going offline
2116 if show in ('offline', 'error') and \
2117 not self.contact_has_pending_roster_events(contact, account):
2118 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2119 if ctrl:
2120 ctrl.update_ui()
2121 ctrl.parent_win.redraw_tab(ctrl)
2122 # keep the contact around, since it's
2123 # already attached to the control
2124 else:
2125 gajim.contacts.remove_contact(account, contact)
2127 elif contact.jid == gajim.get_jid_from_account(account) and \
2128 show in ('offline', 'error'):
2129 if gajim.config.get('show_self_contact') != 'never':
2130 # SelfContact went offline. Remove him when last pending
2131 # message was read
2132 self.remove_contact(contact.jid, account, backend=True)
2134 uf_show = helpers.get_uf_show(show)
2136 # print status in chat window and update status/GPG image
2137 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
2138 if ctrl and ctrl.type_id != message_control.TYPE_GC:
2139 ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
2140 account, contact.jid)
2141 ctrl.update_status_display(name, uf_show, status)
2143 if contact.resource:
2144 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2145 if ctrl:
2146 ctrl.update_status_display(name, uf_show, status)
2148 # Delete pep if needed
2149 keep_pep = any(c.show not in ('error', 'offline') for c in
2150 contact_instances)
2151 if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
2152 and not contact.is_groupchat():
2153 self.delete_pep(contact.jid, account)
2155 # Redraw everything and select the sender
2156 self.adjust_and_draw_contact_context(contact.jid, account)
2159 def on_status_changed(self, account, show):
2161 The core tells us that our status has changed
2163 if account not in gajim.contacts.get_accounts():
2164 return
2165 child_iterA = self._get_account_iter(account, self.model)
2166 if gajim.config.get('show_self_contact') == 'always':
2167 self_resource = gajim.connections[account].server_resource
2168 self_contact = gajim.contacts.get_contact(account,
2169 gajim.get_jid_from_account(account), resource=self_resource)
2170 if self_contact:
2171 status = gajim.connections[account].status
2172 self.chg_contact_status(self_contact, show, status, account)
2173 self.set_account_status_icon(account)
2174 if show == 'offline':
2175 if self.quit_on_next_offline > -1:
2176 # we want to quit, we are waiting for all accounts to be offline
2177 self.quit_on_next_offline -= 1
2178 if self.quit_on_next_offline < 1:
2179 # all accounts offline, quit
2180 self.quit_gtkgui_interface()
2181 else:
2182 # No need to redraw contacts if we're quitting
2183 if child_iterA:
2184 self.model[child_iterA][C_AVATAR_PIXBUF] = None
2185 if account in gajim.con_types:
2186 gajim.con_types[account] = None
2187 for jid in gajim.contacts.get_jid_list(account):
2188 lcontact = gajim.contacts.get_contacts(account, jid)
2189 ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid,
2190 account)
2191 for contact in [c for c in lcontact if (
2192 (c.show != 'offline' or c.is_transport()) and not ctrl)]:
2193 self.chg_contact_status(contact, 'offline', '', account)
2194 self.set_actions_menu_needs_rebuild()
2195 self.update_status_combobox()
2197 def get_status_message(self, show, on_response, show_pep=True,
2198 always_ask=False):
2200 Get the status message by:
2202 1/ looking in default status message
2203 2/ asking to user if needed depending on ask_on(ff)line_status and
2204 always_ask
2205 show_pep can be False to hide pep things from status message or True
2207 empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
2208 'mood': '', 'mood_text': ''}
2209 if show in gajim.config.get_per('defaultstatusmsg'):
2210 if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
2211 msg = gajim.config.get_per('defaultstatusmsg', show, 'message')
2212 msg = helpers.from_one_line(msg)
2213 on_response(msg, empty_pep)
2214 return
2215 if not always_ask and ((show == 'online' and not gajim.config.get(
2216 'ask_online_status')) or (show in ('offline', 'invisible') and not \
2217 gajim.config.get('ask_offline_status'))):
2218 on_response('', empty_pep)
2219 return
2221 dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
2222 dlg.dialog.present() # show it on current workspace
2224 def change_status(self, widget, account, status):
2225 def change(account, status):
2226 def on_response(message, pep_dict):
2227 if message is None:
2228 # user pressed Cancel to change status message dialog
2229 return
2230 self.send_status(account, status, message)
2231 self.send_pep(account, pep_dict)
2232 self.get_status_message(status, on_response)
2234 if status == 'invisible' and self.connected_rooms(account):
2235 dialogs.ConfirmationDialog(
2236 _('You are participating in one or more group chats'),
2237 _('Changing your status to invisible will result in '
2238 'disconnection from those group chats. Are you sure you want '
2239 'to go invisible?'), on_response_ok = (change, account, status))
2240 else:
2241 change(account, status)
2243 def update_status_combobox(self):
2244 # table to change index in connection.connected to index in combobox
2245 table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
2246 'xa':3, 'dnd':4, 'invisible':5}
2248 # we check if there are more options in the combobox that it should
2249 # if yes, we remove the first ones
2250 while len(self.status_combobox.get_model()) > len(table)+2:
2251 self.status_combobox.remove_text(0)
2253 show = helpers.get_global_show()
2254 # temporarily block signal in order not to send status that we show
2255 # in the combobox
2256 self.combobox_callback_active = False
2257 if helpers.statuses_unified():
2258 self.status_combobox.set_active(table[show])
2259 else:
2260 uf_show = helpers.get_uf_show(show)
2261 liststore = self.status_combobox.get_model()
2262 liststore.prepend(['SEPARATOR', None, '', True])
2263 status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
2264 liststore.prepend([status_combobox_text,
2265 gajim.interface.jabber_state_images['16'][show], show, False])
2266 self.status_combobox.set_active(0)
2267 gajim.interface.change_awn_icon_status(show)
2268 self.combobox_callback_active = True
2269 if gajim.interface.systray_enabled:
2270 gajim.interface.systray.change_status(show)
2272 def get_show(self, lcontact):
2273 prio = lcontact[0].priority
2274 show = lcontact[0].show
2275 for u in lcontact:
2276 if u.priority > prio:
2277 prio = u.priority
2278 show = u.show
2279 return show
2281 def on_message_window_delete(self, win_mgr, msg_win):
2282 if gajim.config.get('one_message_window') == 'always_with_roster':
2283 self.show_roster_vbox(True)
2284 gtkgui_helpers.resize_window(self.window,
2285 gajim.config.get('roster_width'),
2286 gajim.config.get('roster_height'))
2288 def close_all_from_dict(self, dic):
2290 Close all the windows in the given dictionary
2292 for w in dic.values():
2293 if isinstance(w, dict):
2294 self.close_all_from_dict(w)
2295 else:
2296 w.window.destroy()
2298 def close_all(self, account, force=False):
2300 Close all the windows from an account. If force is True, do not ask
2301 confirmation before closing chat/gc windows
2303 if account in gajim.interface.instances:
2304 self.close_all_from_dict(gajim.interface.instances[account])
2305 for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
2306 ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
2307 force = force)
2309 def on_roster_window_delete_event(self, widget, event):
2311 Main window X button was clicked
2313 if not gajim.config.get('quit_on_roster_x_button') and (
2314 (gajim.interface.systray_enabled and gajim.config.get('trayicon') != \
2315 'on_event') or gajim.config.get('allow_hide_roster')):
2316 self.tooltip.hide_tooltip()
2317 if gajim.config.get('save-roster-position'):
2318 x, y = self.window.get_position()
2319 gajim.config.set('roster_x-position', x)
2320 gajim.config.set('roster_y-position', y)
2321 self.window.hide()
2322 elif gajim.config.get('quit_on_roster_x_button'):
2323 self.on_quit_request()
2324 else:
2325 def on_ok(checked):
2326 if checked:
2327 gajim.config.set('quit_on_roster_x_button', True)
2328 self.on_quit_request()
2329 dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'),
2330 _('Are you sure you want to quit Gajim?'),
2331 _('Always close Gajim'), on_response_ok=on_ok)
2332 return True # do NOT destroy the window
2334 def prepare_quit(self):
2335 msgwin_width_adjust = 0
2337 # in case show_roster_on_start is False and roster is never shown
2338 # window.window is None
2339 if self.window.window is not None:
2340 if gajim.config.get('save-roster-position'):
2341 x, y = self.window.window.get_root_origin()
2342 gajim.config.set('roster_x-position', x)
2343 gajim.config.set('roster_y-position', y)
2344 width, height = self.window.get_size()
2345 # For the width use the size of the vbox containing the tree and
2346 # status combo, this will cancel out any hpaned width
2347 width = self.xml.get_object('roster_vbox2').allocation.width
2348 gajim.config.set('roster_width', width)
2349 gajim.config.set('roster_height', height)
2350 if not self.xml.get_object('roster_vbox2').get_property('visible'):
2351 # The roster vbox is hidden, so the message window is larger
2352 # then we want to save (i.e. the window will grow every startup)
2353 # so adjust.
2354 msgwin_width_adjust = -1 * width
2355 gajim.config.set('last_roster_visible',
2356 self.window.get_property('visible'))
2357 gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust)
2359 gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows))
2360 gajim.interface.save_config()
2361 for account in gajim.connections:
2362 gajim.connections[account].quit(True)
2363 self.close_all(account)
2364 if gajim.interface.systray_enabled:
2365 gajim.interface.hide_systray()
2367 def quit_gtkgui_interface(self):
2369 When we quit the gtk interface - exit gtk
2371 self.prepare_quit()
2372 gtk.main_quit()
2374 def on_quit_request(self, widget=None):
2376 User wants to quit. Check if he should be warned about messages pending.
2377 Terminate all sessions and send offline to all connected account. We do
2378 NOT really quit gajim here
2380 accounts = gajim.connections.keys()
2381 get_msg = False
2382 for acct in accounts:
2383 if gajim.connections[acct].connected:
2384 get_msg = True
2385 break
2387 def on_continue3(message, pep_dict):
2388 self.quit_on_next_offline = 0
2389 accounts_to_disconnect = []
2390 for acct in accounts:
2391 if gajim.connections[acct].connected > 1:
2392 self.quit_on_next_offline += 1
2393 accounts_to_disconnect.append(acct)
2395 for acct in accounts_to_disconnect:
2396 self.send_status(acct, 'offline', message)
2397 self.send_pep(acct, pep_dict)
2399 if not self.quit_on_next_offline:
2400 self.quit_gtkgui_interface()
2402 def on_continue2(message, pep_dict):
2403 # check if there is an active file transfer
2404 from common.protocol.bytestream import (is_transfer_active)
2405 files_props = gajim.interface.instances['file_transfers'].\
2406 files_props
2407 transfer_active = False
2408 for x in files_props:
2409 for y in files_props[x]:
2410 if is_transfer_active(files_props[x][y]):
2411 transfer_active = True
2412 break
2414 if transfer_active:
2415 dialogs.ConfirmationDialog(_('You have running file transfers'),
2416 _('If you quit now, the file(s) being transferred will '
2417 'be stopped. Do you still want to quit?'),
2418 on_response_ok=(on_continue3, message, pep_dict))
2419 return
2420 on_continue3(message, pep_dict)
2422 def on_continue(message, pep_dict):
2423 if message is None:
2424 # user pressed Cancel to change status message dialog
2425 return
2426 # check if we have unread messages
2427 unread = gajim.events.get_nb_events()
2428 if not gajim.config.get('notify_on_all_muc_messages'):
2429 unread_not_to_notify = gajim.events.get_nb_events(
2430 ['printed_gc_msg'])
2431 unread -= unread_not_to_notify
2433 # check if we have recent messages
2434 recent = False
2435 for win in gajim.interface.msg_win_mgr.windows():
2436 for ctrl in win.controls():
2437 fjid = ctrl.get_full_jid()
2438 if fjid in gajim.last_message_time[ctrl.account]:
2439 if time.time() - gajim.last_message_time[ctrl.account][
2440 fjid] < 2:
2441 recent = True
2442 break
2443 if recent:
2444 break
2446 if unread or recent:
2447 dialogs.ConfirmationDialog(_('You have unread messages'),
2448 _('Messages will only be available for reading them later '
2449 'if you have history enabled and contact is in your '
2450 'roster.'), on_response_ok=(on_continue2,
2451 message, pep_dict))
2452 return
2453 on_continue2(message, pep_dict)
2455 if get_msg:
2456 self.get_status_message('offline', on_continue, show_pep=False)
2457 else:
2458 on_continue('', None)
2460 def _nec_presence_received(self, obj):
2461 account = obj.conn.name
2462 jid = obj.jid
2464 if obj.need_add_in_roster:
2465 self.add_contact(jid, account)
2467 jid_list = gajim.contacts.get_jid_list(account)
2468 if jid in jid_list or jid == gajim.get_jid_from_account(account):
2469 if not gajim.jid_is_transport(jid) and len(obj.contact_list) == 1:
2470 if obj.old_show == 0 and obj.new_show > 1:
2471 gobject.timeout_add_seconds(5, self.remove_newly_added, jid,
2472 account)
2473 elif obj.old_show > 1 and obj.new_show == 0 and \
2474 obj.conn.connected > 1:
2475 gobject.timeout_add_seconds(5, self.remove_to_be_removed,
2476 jid, account)
2478 if obj.need_redraw:
2479 self.draw_contact(jid, account)
2481 if gajim.jid_is_transport(jid) and jid in jid_list:
2482 # It must be an agent
2483 # Update existing iter and group counting
2484 self.draw_contact(jid, account)
2485 self.draw_group(_('Transports'), account)
2486 if obj.new_show > 1 and jid in gajim.transport_avatar[account]:
2487 # transport just signed in.
2488 # request avatars
2489 for jid_ in gajim.transport_avatar[account][jid]:
2490 obj.conn.request_vcard(jid_)
2492 if obj.contact:
2493 self.chg_contact_status(obj.contact, obj.show, obj.status, account)
2495 def _nec_gc_presence_received(self, obj):
2496 account = obj.conn.name
2497 if obj.room_jid in gajim.interface.minimized_controls[account]:
2498 gc_ctrl = gajim.interface.minimized_controls[account][obj.room_jid]
2499 else:
2500 return
2502 if obj.nick == gc_ctrl.nick:
2503 contact = gajim.contacts.get_contact_with_highest_priority(account,
2504 obj.room_jid)
2505 if contact:
2506 contact.show = obj.show
2507 self.draw_contact(obj.room_jid, account)
2508 self.draw_group(_('Groupchats'), account)
2510 def _nec_roster_received(self, obj):
2511 if obj.received_from_server:
2512 self.fill_contacts_and_groups_dicts(obj.roster, obj.conn.name)
2513 self.add_account_contacts(obj.conn.name)
2514 self.fire_up_unread_messages_events(obj.conn.name)
2515 else:
2516 gobject.idle_add(self.refilter_shown_roster_items)
2518 def _nec_anonymous_auth(self, obj):
2520 This event is raised when our JID changed (most probably because we use
2521 anonymous account. We update contact and roster entry in this case
2523 self.rename_self_contact(obj.old_jid, obj.new_jid, obj.conn.name)
2525 def _nec_our_show(self, obj):
2526 model = self.status_combobox.get_model()
2527 if obj.show == 'offline':
2528 # sensitivity for this menuitem
2529 if gajim.get_number_of_connected_accounts() == 0:
2530 model[self.status_message_menuitem_iter][3] = False
2531 else:
2532 # sensitivity for this menuitem
2533 model[self.status_message_menuitem_iter][3] = True
2534 self.on_status_changed(obj.conn.name, obj.show)
2536 def _nec_connection_type(self, obj):
2537 self.draw_account(obj.conn.name)
2539 def _nec_agent_removed(self, obj):
2540 for jid in obj.jid_list:
2541 self.remove_contact(jid, obj.conn.name, backend=True)
2543 def _nec_pep_received(self, obj):
2544 if obj.jid == common.gajim.get_jid_from_account(obj.conn.name):
2545 self.draw_account(obj.conn.name)
2547 if obj.pep_type == 'nickname':
2548 self.draw_contact(obj.jid, obj.conn.name)
2549 else:
2550 self.draw_pep(obj.jid, obj.conn.name, obj.pep_type)
2552 def _nec_vcard_received(self, obj):
2553 if obj.resource:
2554 # it's a muc occupant vcard
2555 return
2556 self.draw_avatar(obj.jid, obj.conn.name)
2558 def _nec_gc_subject_received(self, obj):
2559 contact = gajim.contacts.get_contact_with_highest_priority(
2560 obj.conn.name, obj.room_jid)
2561 if contact:
2562 contact.status = obj.subject
2563 self.draw_contact(obj.room_jid, obj.conn.name)
2565 def _nec_metacontacts_received(self, obj):
2566 self.redraw_metacontacts(obj.conn.name)
2568 def _nec_signed_in(self, obj):
2569 self.set_actions_menu_needs_rebuild()
2570 self.draw_account(obj.conn.name)
2572 def _nec_decrypted_message_received(self, obj):
2573 if not obj.msgtxt: # empty message text
2574 return True
2575 if obj.mtype not in ('norml', 'chat'):
2576 return
2577 if obj.session.control:
2578 typ = ''
2579 if obj.mtype == 'error':
2580 typ = 'error'
2582 obj.session.control.print_conversation(obj.msgtxt, typ,
2583 tim=obj.timestamp, encrypted=obj.encrypted, subject=obj.subject,
2584 xhtml=obj.xhtml, displaymarking=obj.displaymarking)
2585 if obj.msg_id:
2586 gajim.logger.set_read_messages([obj.msg_id])
2587 elif obj.popup:
2588 if not obj.session.control:
2589 contact = gajim.contacts.get_contact(obj.conn.name, obj.jid,
2590 obj.resource_for_chat)
2591 obj.session.control = gajim.interface.new_chat(contact,
2592 obj.conn.name, resource=obj.resource_for_chat,
2593 session=obj.session)
2594 if len(gajim.events.get_events(obj.conn.name, obj.fjid)):
2595 obj.session.control.read_queue()
2597 if obj.show_in_roster:
2598 self.draw_contact(obj.jid, obj.conn.name)
2599 self.show_title() # we show the * or [n]
2600 # Select the big brother contact in roster, it's visible because it
2601 # has events.
2602 family = gajim.contacts.get_metacontacts_family(obj.conn.name,
2603 obj.jid)
2604 if family:
2605 nearby_family, bb_jid, bb_account = \
2606 gajim.contacts.get_nearby_family_and_big_brother(family,
2607 obj.conn.name)
2608 else:
2609 bb_jid, bb_account = obj.jid, obj.conn.name
2610 self.select_contact(bb_jid, bb_account)
2612 ################################################################################
2613 ### Menu and GUI callbacks
2614 ### FIXME: order callbacks in itself...
2615 ################################################################################
2617 def on_actions_menuitem_activate(self, widget):
2618 self.make_menu()
2620 def on_edit_menuitem_activate(self, widget):
2622 Need to call make_menu to build profile, avatar item
2624 self.make_menu()
2626 def on_bookmark_menuitem_activate(self, widget, account, bookmark):
2627 gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'],
2628 bookmark['password'])
2630 def on_send_server_message_menuitem_activate(self, widget, account):
2631 server = gajim.config.get_per('accounts', account, 'hostname')
2632 server += '/announce/online'
2633 dialogs.SingleMessageWindow(account, server, 'send')
2635 def on_xml_console_menuitem_activate(self, widget, account):
2636 if 'xml_console' in gajim.interface.instances[account]:
2637 gajim.interface.instances[account]['xml_console'].window.present()
2638 else:
2639 gajim.interface.instances[account]['xml_console'] = \
2640 dialogs.XMLConsoleWindow(account)
2642 def on_archiving_preferences_menuitem_activate(self, widget, account):
2643 if 'archiving_preferences' in gajim.interface.instances[account]:
2644 gajim.interface.instances[account]['archiving_preferences'].window.\
2645 present()
2646 else:
2647 gajim.interface.instances[account]['archiving_preferences'] = \
2648 dialogs.ArchivingPreferencesWindow(account)
2650 def on_privacy_lists_menuitem_activate(self, widget, account):
2651 if 'privacy_lists' in gajim.interface.instances[account]:
2652 gajim.interface.instances[account]['privacy_lists'].window.present()
2653 else:
2654 gajim.interface.instances[account]['privacy_lists'] = \
2655 dialogs.PrivacyListsWindow(account)
2657 def on_set_motd_menuitem_activate(self, widget, account):
2658 server = gajim.config.get_per('accounts', account, 'hostname')
2659 server += '/announce/motd'
2660 dialogs.SingleMessageWindow(account, server, 'send')
2662 def on_update_motd_menuitem_activate(self, widget, account):
2663 server = gajim.config.get_per('accounts', account, 'hostname')
2664 server += '/announce/motd/update'
2665 dialogs.SingleMessageWindow(account, server, 'send')
2667 def on_delete_motd_menuitem_activate(self, widget, account):
2668 server = gajim.config.get_per('accounts', account, 'hostname')
2669 server += '/announce/motd/delete'
2670 gajim.connections[account].send_motd(server)
2672 def on_history_manager_menuitem_activate(self, widget):
2673 if os.name == 'nt':
2674 if os.path.exists('history_manager.exe'): # user is running stable
2675 helpers.exec_command('history_manager.exe')
2676 else: # user is running svn
2677 helpers.exec_command('%s history_manager.py' % sys.executable)
2678 else: # Unix user
2679 helpers.exec_command('%s history_manager.py' % sys.executable)
2681 def on_info(self, widget, contact, account):
2683 Call vcard_information_window class to display contact's information
2685 if gajim.connections[account].is_zeroconf:
2686 self.on_info_zeroconf(widget, contact, account)
2687 return
2689 info = gajim.interface.instances[account]['infos']
2690 if contact.jid in info:
2691 info[contact.jid].window.present()
2692 else:
2693 info[contact.jid] = vcard.VcardWindow(contact, account)
2695 def on_info_zeroconf(self, widget, contact, account):
2696 info = gajim.interface.instances[account]['infos']
2697 if contact.jid in info:
2698 info[contact.jid].window.present()
2699 else:
2700 contact = gajim.contacts.get_first_contact_from_jid(account,
2701 contact.jid)
2702 if contact.show in ('offline', 'error'):
2703 # don't show info on offline contacts
2704 return
2705 info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
2707 def on_roster_treeview_leave_notify_event(self, widget, event):
2708 props = widget.get_path_at_pos(int(event.x), int(event.y))
2709 if self.tooltip.timeout > 0:
2710 if not props or self.tooltip.id == props[0]:
2711 self.tooltip.hide_tooltip()
2713 def on_roster_treeview_motion_notify_event(self, widget, event):
2714 model = widget.get_model()
2715 props = widget.get_path_at_pos(int(event.x), int(event.y))
2716 if self.tooltip.timeout > 0:
2717 if not props or self.tooltip.id != props[0]:
2718 self.tooltip.hide_tooltip()
2719 if props:
2720 row = props[0]
2721 titer = None
2722 try:
2723 titer = model.get_iter(row)
2724 except Exception:
2725 self.tooltip.hide_tooltip()
2726 return
2727 if model[titer][C_TYPE] in ('contact', 'self_contact'):
2728 # we're on a contact entry in the roster
2729 account = model[titer][C_ACCOUNT].decode('utf-8')
2730 jid = model[titer][C_JID].decode('utf-8')
2731 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2732 self.tooltip.id = row
2733 contacts = gajim.contacts.get_contacts(account, jid)
2734 connected_contacts = []
2735 for c in contacts:
2736 if c.show not in ('offline', 'error'):
2737 connected_contacts.append(c)
2738 if not connected_contacts:
2739 # no connected contacts, show the ofline one
2740 connected_contacts = contacts
2741 self.tooltip.account = account
2742 self.tooltip.timeout = gobject.timeout_add(500,
2743 self.show_tooltip, connected_contacts)
2744 elif model[titer][C_TYPE] == 'groupchat':
2745 account = model[titer][C_ACCOUNT].decode('utf-8')
2746 jid = model[titer][C_JID].decode('utf-8')
2747 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2748 self.tooltip.id = row
2749 contact = gajim.contacts.get_contacts(account, jid)
2750 self.tooltip.account = account
2751 self.tooltip.timeout = gobject.timeout_add(500,
2752 self.show_tooltip, contact)
2753 elif model[titer][C_TYPE] == 'account':
2754 # we're on an account entry in the roster
2755 account = model[titer][C_ACCOUNT].decode('utf-8')
2756 if account == 'all':
2757 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2758 self.tooltip.id = row
2759 self.tooltip.account = None
2760 self.tooltip.timeout = gobject.timeout_add(500,
2761 self.show_tooltip, [])
2762 return
2763 jid = gajim.get_jid_from_account(account)
2764 contacts = []
2765 connection = gajim.connections[account]
2766 # get our current contact info
2768 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
2769 accounts = [account])
2770 account_name = account
2771 if gajim.account_is_connected(account):
2772 account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
2773 contact = gajim.contacts.create_self_contact(jid=jid,
2774 account=account, name=account_name,
2775 show=connection.get_status(), status=connection.status,
2776 resource=connection.server_resource,
2777 priority=connection.priority)
2778 if gajim.connections[account].gpg:
2779 contact.keyID = gajim.config.get_per('accounts',
2780 connection.name, 'keyid')
2781 contacts.append(contact)
2782 # if we're online ...
2783 if connection.connection:
2784 roster = connection.connection.getRoster()
2785 # in threadless connection when no roster stanza is sent,
2786 # 'roster' is None
2787 if roster and roster.getItem(jid):
2788 resources = roster.getResources(jid)
2789 # ...get the contact info for our other online resources
2790 for resource in resources:
2791 # Check if we already have this resource
2792 found = False
2793 for contact_ in contacts:
2794 if contact_.resource == resource:
2795 found = True
2796 break
2797 if found:
2798 continue
2799 show = roster.getShow(jid+'/'+resource)
2800 if not show:
2801 show = 'online'
2802 contact = gajim.contacts.create_self_contact(
2803 jid=jid, account=account, show=show,
2804 status=roster.getStatus(jid + '/' + resource),
2805 priority=roster.getPriority(
2806 jid + '/' + resource), resource=resource)
2807 contacts.append(contact)
2808 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2809 self.tooltip.id = row
2810 self.tooltip.account = None
2811 self.tooltip.timeout = gobject.timeout_add(500,
2812 self.show_tooltip, contacts)
2814 def on_agent_logging(self, widget, jid, state, account):
2816 When an agent is requested to log in or off
2818 gajim.connections[account].send_agent_status(jid, state)
2820 def on_edit_agent(self, widget, contact, account):
2822 When we want to modify the agent registration
2824 gajim.connections[account].request_register_agent_info(contact.jid)
2826 def on_remove_agent(self, widget, list_):
2828 When an agent is requested to be removed. list_ is a list of (contact,
2829 account) tuple
2831 for (contact, account) in list_:
2832 if gajim.config.get_per('accounts', account, 'hostname') == \
2833 contact.jid:
2834 # We remove the server contact
2835 # remove it from treeview
2836 gajim.connections[account].unsubscribe(contact.jid)
2837 self.remove_contact(contact.jid, account, backend=True)
2838 return
2840 def remove(list_):
2841 for (contact, account) in list_:
2842 full_jid = contact.get_full_jid()
2843 gajim.connections[account].unsubscribe_agent(full_jid)
2844 # remove transport from treeview
2845 self.remove_contact(contact.jid, account, backend=True)
2847 # Check if there are unread events from some contacts
2848 has_unread_events = False
2849 for (contact, account) in list_:
2850 for jid in gajim.events.get_events(account):
2851 if jid.endswith(contact.jid):
2852 has_unread_events = True
2853 break
2854 if has_unread_events:
2855 dialogs.ErrorDialog(_('You have unread messages'),
2856 _('You must read them before removing this transport.'))
2857 return
2858 if len(list_) == 1:
2859 pritext = _('Transport "%s" will be removed') % list_[0][0].jid
2860 sectext = _('You will no longer be able to send and receive '
2861 'messages from contacts using this transport.')
2862 else:
2863 pritext = _('Transports will be removed')
2864 jids = ''
2865 for (contact, account) in list_:
2866 jids += '\n ' + contact.get_shown_name() + ','
2867 jids = jids[:-1] + '.'
2868 sectext = _('You will no longer be able to send and receive '
2869 'messages to contacts from these transports: %s') % jids
2870 dialogs.ConfirmationDialog(pritext, sectext,
2871 on_response_ok = (remove, list_))
2873 def on_block(self, widget, list_, group=None):
2875 When clicked on the 'block' button in context menu. list_ is a list of
2876 (contact, account)
2878 def on_continue(msg, pep_dict):
2879 if msg is None:
2880 # user pressed Cancel to change status message dialog
2881 return
2882 accounts = []
2883 if group is None:
2884 for (contact, account) in list_:
2885 if account not in accounts:
2886 if not gajim.connections[account].\
2887 privacy_rules_supported:
2888 continue
2889 accounts.append(account)
2890 self.send_status(account, 'offline', msg, to=contact.jid)
2891 new_rule = {'order': u'1', 'type': u'jid',
2892 'action': u'deny', 'value' : contact.jid,
2893 'child': [u'message', u'iq', u'presence-out']}
2894 gajim.connections[account].blocked_list.append(new_rule)
2895 # needed for draw_contact:
2896 gajim.connections[account].blocked_contacts.append(
2897 contact.jid)
2898 self.draw_contact(contact.jid, account)
2899 else:
2900 for (contact, account) in list_:
2901 if account not in accounts:
2902 if not gajim.connections[account].\
2903 privacy_rules_supported:
2904 continue
2905 accounts.append(account)
2906 # needed for draw_group:
2907 gajim.connections[account].blocked_groups.append(group)
2908 self.draw_group(group, account)
2909 self.send_status(account, 'offline', msg, to=contact.jid)
2910 self.draw_contact(contact.jid, account)
2911 new_rule = {'order': u'1', 'type': u'group', 'action': u'deny',
2912 'value' : group, 'child': [u'message', u'iq',
2913 u'presence-out']}
2914 # account is the same for all when we block a group
2915 gajim.connections[list_[0][1]].blocked_list.append(new_rule)
2916 for account in accounts:
2917 connection = gajim.connections[account]
2918 connection.set_privacy_list('block', connection.blocked_list)
2919 if len(connection.blocked_list) == 1:
2920 connection.set_active_list('block')
2921 connection.set_default_list('block')
2922 connection.get_privacy_list('block')
2924 def _block_it(is_checked=None):
2925 if is_checked is not None: # dialog has been shown
2926 if is_checked: # user does not want to be asked again
2927 gajim.config.set('confirm_block', 'no')
2928 else:
2929 gajim.config.set('confirm_block', 'yes')
2930 self.get_status_message('offline', on_continue, show_pep=False)
2932 confirm_block = gajim.config.get('confirm_block')
2933 if confirm_block == 'no':
2934 _block_it()
2935 return
2936 pritext = _('You are about to block a contact. Are you sure you want'
2937 ' to continue?')
2938 sectext = _('This contact will see you offline and you will not '
2939 'receive messages he will send you.')
2940 dialogs.ConfirmationDialogCheck(pritext, sectext,
2941 _('_Do not ask me again'), on_response_ok=_block_it)
2943 def on_unblock(self, widget, list_, group=None):
2945 When clicked on the 'unblock' button in context menu.
2947 accounts = []
2948 if group is None:
2949 for (contact, account) in list_:
2950 if account not in accounts:
2951 if gajim.connections[account].privacy_rules_supported:
2952 accounts.append(account)
2953 gajim.connections[account].new_blocked_list = []
2954 gajim.connections[account].to_unblock = []
2955 gajim.connections[account].to_unblock.append(
2956 contact.jid)
2957 else:
2958 gajim.connections[account].to_unblock.append(contact.jid)
2959 # needed for draw_contact:
2960 if contact.jid in gajim.connections[account].blocked_contacts:
2961 gajim.connections[account].blocked_contacts.remove(
2962 contact.jid)
2963 self.draw_contact(contact.jid, account)
2964 for account in accounts:
2965 for rule in gajim.connections[account].blocked_list:
2966 if rule['action'] != 'deny' or rule['type'] != 'jid' \
2967 or rule['value'] not in \
2968 gajim.connections[account].to_unblock:
2969 gajim.connections[account].new_blocked_list.append(rule)
2970 else:
2971 for (contact, account) in list_:
2972 if account not in accounts:
2973 if gajim.connections[account].privacy_rules_supported:
2974 accounts.append(account)
2975 # needed for draw_group:
2976 if group in gajim.connections[account].blocked_groups:
2977 gajim.connections[account].blocked_groups.remove(
2978 group)
2979 self.draw_group(group, account)
2980 gajim.connections[account].new_blocked_list = []
2981 for rule in gajim.connections[account].blocked_list:
2982 if rule['action'] != 'deny' or \
2983 rule['type'] != 'group' or rule['value'] != group:
2984 gajim.connections[account].new_blocked_list.\
2985 append(rule)
2986 self.draw_contact(contact.jid, account)
2987 for account in accounts:
2988 gajim.connections[account].set_privacy_list('block',
2989 gajim.connections[account].new_blocked_list)
2990 gajim.connections[account].get_privacy_list('block')
2991 if len(gajim.connections[account].new_blocked_list) == 0:
2992 gajim.connections[account].blocked_list = []
2993 gajim.connections[account].blocked_contacts = []
2994 gajim.connections[account].blocked_groups = []
2995 gajim.connections[account].set_default_list('')
2996 gajim.connections[account].set_active_list('')
2997 gajim.connections[account].del_privacy_list('block')
2998 if 'privacy_list_block' in gajim.interface.instances[account]:
2999 del gajim.interface.instances[account]['privacy_list_block']
3000 for (contact, account) in list_:
3001 if not self.regroup:
3002 show = gajim.SHOW_LIST[gajim.connections[account].connected]
3003 else: # accounts merged
3004 show = helpers.get_global_show()
3005 if show == 'invisible':
3006 # Don't send our presence if we're invisible
3007 continue
3008 if account not in accounts:
3009 accounts.append(account)
3010 if gajim.connections[account].privacy_rules_supported:
3011 self.send_status(account, show,
3012 gajim.connections[account].status, to=contact.jid)
3013 else:
3014 self.send_status(account, show,
3015 gajim.connections[account].status, to=contact.jid)
3017 def on_rename(self, widget, row_type, jid, account):
3018 # this function is called either by F2 or by Rename menuitem
3019 if 'rename' in gajim.interface.instances:
3020 gajim.interface.instances['rename'].dialog.present()
3021 return
3023 # account is offline, don't allow to rename
3024 if gajim.connections[account].connected < 2:
3025 return
3026 if row_type in ('contact', 'agent'):
3027 # it's jid
3028 title = _('Rename Contact')
3029 message = _('Enter a new nickname for contact %s') % jid
3030 old_text = gajim.contacts.get_contact_with_highest_priority(account,
3031 jid).name
3032 elif row_type == 'group':
3033 if jid in helpers.special_groups + (_('General'),):
3034 return
3035 old_text = jid
3036 title = _('Rename Group')
3037 message = _('Enter a new name for group %s') % \
3038 gobject.markup_escape_text(jid)
3040 def on_renamed(new_text, account, row_type, jid, old_text):
3041 if 'rename' in gajim.interface.instances:
3042 del gajim.interface.instances['rename']
3043 if row_type in ('contact', 'agent'):
3044 if old_text == new_text:
3045 return
3046 contacts = gajim.contacts.get_contacts(account, jid)
3047 for contact in contacts:
3048 contact.name = new_text
3049 gajim.connections[account].update_contact(jid, new_text, \
3050 contacts[0].groups)
3051 self.draw_contact(jid, account)
3052 # Update opened chats
3053 for ctrl in gajim.interface.msg_win_mgr.get_controls(jid,
3054 account):
3055 ctrl.update_ui()
3056 win = gajim.interface.msg_win_mgr.get_window(jid, account)
3057 win.redraw_tab(ctrl)
3058 win.show_title()
3059 elif row_type == 'group':
3060 # in C_JID column, we hold the group name (which is not escaped)
3061 self.rename_group(old_text, new_text, account)
3063 def on_canceled():
3064 if 'rename' in gajim.interface.instances:
3065 del gajim.interface.instances['rename']
3067 gajim.interface.instances['rename'] = dialogs.InputDialog(title,
3068 message, old_text, False, (on_renamed, account, row_type, jid,
3069 old_text), on_canceled)
3071 def on_remove_group_item_activated(self, widget, group, account):
3072 def on_ok(checked):
3073 for contact in gajim.contacts.get_contacts_from_group(account,
3074 group):
3075 if not checked:
3076 self.remove_contact_from_groups(contact.jid, account,
3077 [group])
3078 else:
3079 gajim.connections[account].unsubscribe(contact.jid)
3080 self.remove_contact(contact.jid, account, backend=True)
3082 dialogs.ConfirmationDialogCheck(_('Remove Group'),
3083 _('Do you want to remove group %s from the roster?') % group,
3084 _('Also remove all contacts in this group from your roster'),
3085 on_response_ok=on_ok)
3087 def on_assign_pgp_key(self, widget, contact, account):
3088 attached_keys = gajim.config.get_per('accounts', account,
3089 'attached_gpg_keys').split()
3090 keys = {}
3091 keyID = _('None')
3092 for i in xrange(len(attached_keys)/2):
3093 keys[attached_keys[2*i]] = attached_keys[2*i+1]
3094 if attached_keys[2*i] == contact.jid:
3095 keyID = attached_keys[2*i+1]
3096 public_keys = gajim.connections[account].ask_gpg_keys()
3097 public_keys[_('None')] = _('None')
3099 def on_key_selected(keyID):
3100 if keyID is None:
3101 return
3102 if keyID[0] == _('None'):
3103 if contact.jid in keys:
3104 del keys[contact.jid]
3105 keyID = ''
3106 else:
3107 keyID = keyID[0]
3108 keys[contact.jid] = keyID
3110 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
3111 if ctrl:
3112 ctrl.update_ui()
3114 keys_str = ''
3115 for jid in keys:
3116 keys_str += jid + ' ' + keys[jid] + ' '
3117 gajim.config.set_per('accounts', account, 'attached_gpg_keys',
3118 keys_str)
3119 for u in gajim.contacts.get_contacts(account, contact.jid):
3120 u.keyID = helpers.prepare_and_validate_gpg_keyID(account,
3121 contact.jid, keyID)
3123 dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
3124 _('Select a key to apply to the contact'), public_keys,
3125 on_key_selected, selected=keyID)
3127 def on_set_custom_avatar_activate(self, widget, contact, account):
3128 def on_ok(widget, path_to_file):
3129 filesize = os.path.getsize(path_to_file) # in bytes
3130 invalid_file = False
3131 msg = ''
3132 if os.path.isfile(path_to_file):
3133 stat = os.stat(path_to_file)
3134 if stat[6] == 0:
3135 invalid_file = True
3136 msg = _('File is empty')
3137 else:
3138 invalid_file = True
3139 msg = _('File does not exist')
3140 if invalid_file:
3141 dialogs.ErrorDialog(_('Could not load image'), msg)
3142 return
3143 try:
3144 pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
3145 if filesize > 16384: # 16 kb
3146 # get the image at 'tooltip size'
3147 # and hope that user did not specify in ACE crazy size
3148 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip')
3149 except gobject.GError, msg: # unknown format
3150 # msg should be string, not object instance
3151 msg = str(msg)
3152 dialogs.ErrorDialog(_('Could not load image'), msg)
3153 return
3154 gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True)
3155 dlg.destroy()
3156 self.update_avatar_in_gui(contact.jid, account)
3158 def on_clear(widget):
3159 dlg.destroy()
3160 # Delete file:
3161 gajim.interface.remove_avatar_files(contact.jid, local=True)
3162 self.update_avatar_in_gui(contact.jid, account)
3164 dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok,
3165 on_response_clear=on_clear)
3167 def on_edit_groups(self, widget, list_):
3168 dialogs.EditGroupsDialog(list_)
3170 def on_history(self, widget, contact, account):
3172 When history menuitem is activated: call log window
3174 if 'logs' in gajim.interface.instances:
3175 gajim.interface.instances['logs'].window.present()
3176 gajim.interface.instances['logs'].open_history(contact.jid, account)
3177 else:
3178 gajim.interface.instances['logs'] = history_window.\
3179 HistoryWindow(contact.jid, account)
3181 def on_disconnect(self, widget, jid, account):
3183 When disconnect menuitem is activated: disconect from room
3185 if jid in gajim.interface.minimized_controls[account]:
3186 ctrl = gajim.interface.minimized_controls[account][jid]
3187 ctrl.shutdown()
3188 ctrl.got_disconnected()
3189 self.remove_groupchat(jid, account)
3191 def on_reconnect(self, widget, jid, account):
3193 When reconnect menuitem is activated: join the room
3195 if jid in gajim.interface.minimized_controls[account]:
3196 ctrl = gajim.interface.minimized_controls[account][jid]
3197 gajim.interface.join_gc_room(account, jid, ctrl.nick,
3198 gajim.gc_passwords.get(jid, ''))
3200 def on_send_single_message_menuitem_activate(self, widget, account,
3201 contact=None):
3202 if contact is None:
3203 dialogs.SingleMessageWindow(account, action='send')
3204 elif isinstance(contact, list):
3205 dialogs.SingleMessageWindow(account, contact, 'send')
3206 else:
3207 jid = contact.jid
3208 if contact.jid == gajim.get_jid_from_account(account):
3209 jid += '/' + contact.resource
3210 dialogs.SingleMessageWindow(account, jid, 'send')
3212 def on_send_file_menuitem_activate(self, widget, contact, account,
3213 resource=None):
3214 gajim.interface.instances['file_transfers'].show_file_send_request(
3215 account, contact)
3217 def on_add_special_notification_menuitem_activate(self, widget, jid):
3218 dialogs.AddSpecialNotificationDialog(jid)
3220 def on_invite_to_new_room(self, widget, list_, resource=None):
3222 Resource parameter MUST NOT be used if more than one contact in list
3224 account_list = []
3225 jid_list = []
3226 for (contact, account) in list_:
3227 if contact.jid not in jid_list:
3228 if resource: # we MUST have one contact only in list_
3229 fjid = contact.jid + '/' + resource
3230 jid_list.append(fjid)
3231 else:
3232 jid_list.append(contact.jid)
3233 if account not in account_list:
3234 account_list.append(account)
3235 # transform None in 'jabber'
3236 type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
3237 for account in account_list:
3238 if gajim.connections[account].muc_jid[type_]:
3239 # create the room on this muc server
3240 if 'join_gc' in gajim.interface.instances[account]:
3241 gajim.interface.instances[account]['join_gc'].window.\
3242 destroy()
3243 try:
3244 gajim.interface.instances[account]['join_gc'] = \
3245 dialogs.JoinGroupchatWindow(account,
3246 gajim.connections[account].muc_jid[type_],
3247 automatic = {'invities': jid_list})
3248 except GajimGeneralException:
3249 continue
3250 break
3252 def on_invite_to_room(self, widget, list_, room_jid, room_account,
3253 resource=None):
3255 Resource parameter MUST NOT be used if more than one contact in list
3257 for e in list_:
3258 contact = e[0]
3259 contact_jid = contact.jid
3260 if resource: # we MUST have one contact only in list_
3261 contact_jid += '/' + resource
3262 gajim.connections[room_account].send_invite(room_jid, contact_jid)
3264 def on_all_groupchat_maximized(self, widget, group_list):
3265 for (contact, account) in group_list:
3266 self.on_groupchat_maximized(widget, contact.jid, account)
3268 def on_groupchat_maximized(self, widget, jid, account):
3270 When a groupchat is maximized
3272 if not jid in gajim.interface.minimized_controls[account]:
3273 # Already opened?
3274 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
3275 account)
3276 if gc_control:
3277 mw = gajim.interface.msg_win_mgr.get_window(jid, account)
3278 mw.set_active_tab(gc_control)
3279 mw.window.window.focus(gtk.get_current_event_time())
3280 return
3281 ctrl = gajim.interface.minimized_controls[account][jid]
3282 mw = gajim.interface.msg_win_mgr.get_window(jid, account)
3283 if not mw:
3284 mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact,
3285 ctrl.account, ctrl.type_id)
3286 ctrl.parent_win = mw
3287 mw.new_tab(ctrl)
3288 mw.set_active_tab(ctrl)
3289 mw.window.window.focus(gtk.get_current_event_time())
3290 self.remove_groupchat(jid, account)
3292 def on_edit_account(self, widget, account):
3293 if 'accounts' in gajim.interface.instances:
3294 gajim.interface.instances['accounts'].window.present()
3295 else:
3296 gajim.interface.instances['accounts'] = config.AccountsWindow()
3297 gajim.interface.instances['accounts'].select_account(account)
3299 def on_zeroconf_properties(self, widget, account):
3300 if 'accounts' in gajim.interface.instances:
3301 gajim.interface.instances['accounts'].window.present()
3302 else:
3303 gajim.interface.instances['accounts'] = config.AccountsWindow()
3304 gajim.interface.instances['accounts'].select_account(account)
3306 def on_open_gmail_inbox(self, widget, account):
3307 url = gajim.connections[account].gmail_url
3308 if url:
3309 helpers.launch_browser_mailer('url', url)
3311 def on_change_status_message_activate(self, widget, account):
3312 show = gajim.SHOW_LIST[gajim.connections[account].connected]
3313 def on_response(message, pep_dict):
3314 if message is None: # None is if user pressed Cancel
3315 return
3316 self.send_status(account, show, message)
3317 self.send_pep(account, pep_dict)
3318 dialogs.ChangeStatusMessageDialog(on_response, show)
3320 def on_add_to_roster(self, widget, contact, account):
3321 dialogs.AddNewContactWindow(account, contact.jid, contact.name)
3324 def on_roster_treeview_scroll_event(self, widget, event):
3325 self.tooltip.hide_tooltip()
3327 def on_roster_treeview_key_press_event(self, widget, event):
3329 When a key is pressed in the treeviews
3331 self.tooltip.hide_tooltip()
3332 if event.keyval == gtk.keysyms.Escape:
3333 self.tree.get_selection().unselect_all()
3334 elif event.keyval == gtk.keysyms.F2:
3335 treeselection = self.tree.get_selection()
3336 model, list_of_paths = treeselection.get_selected_rows()
3337 if len(list_of_paths) != 1:
3338 return
3339 path = list_of_paths[0]
3340 type_ = model[path][C_TYPE]
3341 if type_ in ('contact', 'group', 'agent'):
3342 jid = model[path][C_JID].decode('utf-8')
3343 account = model[path][C_ACCOUNT].decode('utf-8')
3344 self.on_rename(widget, type_, jid, account)
3346 elif event.keyval == gtk.keysyms.Delete:
3347 treeselection = self.tree.get_selection()
3348 model, list_of_paths = treeselection.get_selected_rows()
3349 if not len(list_of_paths):
3350 return
3351 type_ = model[list_of_paths[0]][C_TYPE]
3352 account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8')
3353 if type_ in ('account', 'group', 'self_contact') or \
3354 account == gajim.ZEROCONF_ACC_NAME:
3355 return
3356 list_ = []
3357 for path in list_of_paths:
3358 if model[path][C_TYPE] != type_:
3359 return
3360 jid = model[path][C_JID].decode('utf-8')
3361 account = model[path][C_ACCOUNT].decode('utf-8')
3362 contact = gajim.contacts.get_contact_with_highest_priority(
3363 account, jid)
3364 list_.append((contact, account))
3365 if type_ == 'contact':
3366 self.on_req_usub(widget, list_)
3367 elif type_ == 'agent':
3368 self.on_remove_agent(widget, list_)
3370 elif gtk.gdk.keyval_to_unicode(event.keyval): # if we got unicode symbol
3371 num = gtk.gdk.keyval_to_unicode(event.keyval)
3372 self.enable_rfilter(unichr(num))
3374 def on_roster_treeview_button_release_event(self, widget, event):
3375 try:
3376 path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
3377 except TypeError:
3378 return False
3380 if event.button == 1: # Left click
3381 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3382 not event.state & gtk.gdk.CONTROL_MASK:
3383 # Check if button has been pressed on the same row
3384 if self.clicked_path == path:
3385 self.on_row_activated(widget, path)
3386 self.clicked_path = None
3388 def on_roster_treeview_button_press_event(self, widget, event):
3389 # hide tooltip, no matter the button is pressed
3390 self.tooltip.hide_tooltip()
3391 try:
3392 pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
3393 path, x = pos[0], pos[2]
3394 except TypeError:
3395 self.tree.get_selection().unselect_all()
3396 return False
3398 if event.button == 3: # Right click
3399 try:
3400 model, list_of_paths = self.tree.get_selection().\
3401 get_selected_rows()
3402 except TypeError:
3403 list_of_paths = []
3404 if path not in list_of_paths:
3405 self.tree.get_selection().unselect_all()
3406 self.tree.get_selection().select_path(path)
3407 return self.show_treeview_menu(event)
3409 elif event.button == 2: # Middle click
3410 try:
3411 model, list_of_paths = self.tree.get_selection().\
3412 get_selected_rows()
3413 except TypeError:
3414 list_of_paths = []
3415 if list_of_paths != [path]:
3416 self.tree.get_selection().unselect_all()
3417 self.tree.get_selection().select_path(path)
3418 type_ = model[path][C_TYPE]
3419 if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
3420 self.on_row_activated(widget, path)
3421 elif type_ == 'account':
3422 account = model[path][C_ACCOUNT].decode('utf-8')
3423 if account != 'all':
3424 show = gajim.connections[account].connected
3425 if show > 1: # We are connected
3426 self.on_change_status_message_activate(widget, account)
3427 return True
3428 show = helpers.get_global_show()
3429 if show == 'offline':
3430 return True
3431 def on_response(message, pep_dict):
3432 if message is None:
3433 return True
3434 for acct in gajim.connections:
3435 if not gajim.config.get_per('accounts', acct,
3436 'sync_with_global_status'):
3437 continue
3438 current_show = gajim.SHOW_LIST[gajim.connections[acct].\
3439 connected]
3440 self.send_status(acct, current_show, message)
3441 self.send_pep(acct, pep_dict)
3442 dialogs.ChangeStatusMessageDialog(on_response, show)
3443 return True
3445 elif event.button == 1: # Left click
3446 model = self.modelfilter
3447 type_ = model[path][C_TYPE]
3448 # x_min is the x start position of status icon column
3449 if gajim.config.get('avatar_position_in_roster') == 'left':
3450 x_min = gajim.config.get('roster_avatar_width')
3451 else:
3452 x_min = 0
3453 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3454 not event.state & gtk.gdk.CONTROL_MASK:
3455 # Don't handle double click if we press icon of a metacontact
3456 titer = model.get_iter(path)
3457 if x > x_min and x < x_min + 27 and type_ == 'contact' and \
3458 model.iter_has_child(titer):
3459 if (self.tree.row_expanded(path)):
3460 self.tree.collapse_row(path)
3461 else:
3462 self.tree.expand_row(path, False)
3463 return
3464 # We just save on which row we press button, and open chat
3465 # window on button release to be able to do DND without opening
3466 # chat window
3467 self.clicked_path = path
3468 return
3469 else:
3470 if type_ == 'group' and x < 27:
3471 # first cell in 1st column (the arrow SINGLE clicked)
3472 if (self.tree.row_expanded(path)):
3473 self.tree.collapse_row(path)
3474 else:
3475 self.expand_group_row(path)
3477 elif type_ == 'contact' and x > x_min and x < x_min + 27:
3478 if (self.tree.row_expanded(path)):
3479 self.tree.collapse_row(path)
3480 else:
3481 self.tree.expand_row(path, False)
3483 def expand_group_row(self, path):
3484 self.tree.expand_row(path, False)
3485 iter = self.modelfilter.get_iter(path)
3486 child_iter = self.modelfilter.iter_children(iter)
3487 while child_iter:
3488 type_ = self.modelfilter[child_iter][C_TYPE]
3489 account = self.modelfilter[child_iter][C_ACCOUNT]
3490 group = self.modelfilter[child_iter][C_JID]
3491 if type_ == 'group' and account + group not in self.collapsed_rows:
3492 self.expand_group_row(self.modelfilter.get_path(child_iter))
3493 child_iter = self.modelfilter.iter_next(child_iter)
3495 def on_req_usub(self, widget, list_):
3497 Remove a contact. list_ is a list of (contact, account) tuples
3499 def on_ok(is_checked, list_):
3500 remove_auth = True
3501 if len(list_) == 1:
3502 contact = list_[0][0]
3503 if contact.sub != 'to' and is_checked:
3504 remove_auth = False
3505 for (contact, account) in list_:
3506 if _('Not in Roster') not in contact.get_shown_groups():
3507 gajim.connections[account].unsubscribe(contact.jid,
3508 remove_auth)
3509 self.remove_contact(contact.jid, account, backend=True)
3510 if not remove_auth and contact.sub == 'both':
3511 contact.name = ''
3512 contact.groups = []
3513 contact.sub = 'from'
3514 # we can't see him, but have to set it manually in contact
3515 contact.show = 'offline'
3516 gajim.contacts.add_contact(account, contact)
3517 self.add_contact(contact.jid, account)
3518 def on_ok2(list_):
3519 on_ok(False, list_)
3521 if len(list_) == 1:
3522 contact = list_[0][0]
3523 pritext = _('Contact "%s" will be removed from your roster') % \
3524 contact.get_shown_name()
3525 sectext = _('You are about to remove "%(name)s" (%(jid)s) from '
3526 'your roster.\n') % {'name': contact.get_shown_name(),
3527 'jid': contact.jid}
3528 if contact.sub == 'to':
3529 dialogs.ConfirmationDialog(pritext, sectext + \
3530 _('By removing this contact you also remove authorization '
3531 'resulting in him or her always seeing you as offline.'),
3532 on_response_ok=(on_ok2, list_))
3533 elif _('Not in Roster') in contact.get_shown_groups():
3534 # Contact is not in roster
3535 dialogs.ConfirmationDialog(pritext, sectext + \
3536 _('Do you want to continue?'), on_response_ok=(on_ok2,
3537 list_))
3538 else:
3539 dialogs.ConfirmationDialogCheck(pritext, sectext + \
3540 _('By removing this contact you also by default remove '
3541 'authorization resulting in him or her always seeing you as'
3542 ' offline.'),
3543 _('I want this contact to know my status after removal'),
3544 on_response_ok=(on_ok, list_))
3545 else:
3546 # several contact to remove at the same time
3547 pritext = _('Contacts will be removed from your roster')
3548 jids = ''
3549 for (contact, account) in list_:
3550 jids += '\n ' + contact.get_shown_name() + ' (%s)' % \
3551 contact.jid + ','
3552 sectext = _('By removing these contacts:%s\nyou also remove '
3553 'authorization resulting in them always seeing you as '
3554 'offline.') % jids
3555 dialogs.ConfirmationDialog(pritext, sectext,
3556 on_response_ok=(on_ok2, list_))
3558 def on_send_custom_status(self, widget, contact_list, show, group=None):
3560 Send custom status
3562 # contact_list has only one element except if group != None
3563 def on_response(message, pep_dict):
3564 if message is None: # None if user pressed Cancel
3565 return
3566 account_list = []
3567 for (contact, account) in contact_list:
3568 if account not in account_list:
3569 account_list.append(account)
3570 # 1. update status_sent_to_[groups|users] list
3571 if group:
3572 for account in account_list:
3573 if account not in gajim.interface.status_sent_to_groups:
3574 gajim.interface.status_sent_to_groups[account] = {}
3575 gajim.interface.status_sent_to_groups[account][group] = show
3576 else:
3577 for (contact, account) in contact_list:
3578 if account not in gajim.interface.status_sent_to_users:
3579 gajim.interface.status_sent_to_users[account] = {}
3580 gajim.interface.status_sent_to_users[account][contact.jid] \
3581 = show
3583 # 2. update privacy lists if main status is invisible
3584 for account in account_list:
3585 if gajim.SHOW_LIST[gajim.connections[account].connected] == \
3586 'invisible':
3587 gajim.connections[account].set_invisible_rule()
3589 # 3. send directed presence
3590 for (contact, account) in contact_list:
3591 our_jid = gajim.get_jid_from_account(account)
3592 jid = contact.jid
3593 if jid == our_jid:
3594 jid += '/' + contact.resource
3595 self.send_status(account, show, message, to=jid)
3597 def send_it(is_checked=None):
3598 if is_checked is not None: # dialog has been shown
3599 if is_checked: # user does not want to be asked again
3600 gajim.config.set('confirm_custom_status', 'no')
3601 else:
3602 gajim.config.set('confirm_custom_status', 'yes')
3603 self.get_status_message(show, on_response, show_pep=False,
3604 always_ask=True)
3606 confirm_custom_status = gajim.config.get('confirm_custom_status')
3607 if confirm_custom_status == 'no':
3608 send_it()
3609 return
3610 pritext = _('You are about to send a custom status. Are you sure you '
3611 'want to continue?')
3612 sectext = _('This contact will temporarily see you as %(status)s, '
3613 'but only until you change your status. Then he or she will see '
3614 'your global status.') % {'status': show}
3615 dialogs.ConfirmationDialogCheck(pritext, sectext,
3616 _('_Do not ask me again'), on_response_ok=send_it)
3618 def on_status_combobox_changed(self, widget):
3620 When we change our status via the combobox
3622 model = self.status_combobox.get_model()
3623 active = self.status_combobox.get_active()
3624 if active == -1: # no active item
3625 return
3626 if not self.combobox_callback_active:
3627 self.previous_status_combobox_active = active
3628 return
3629 accounts = gajim.connections.keys()
3630 if len(accounts) == 0:
3631 dialogs.ErrorDialog(_('No account available'),
3632 _('You must create an account before you can chat with other '
3633 'contacts.'))
3634 self.update_status_combobox()
3635 return
3636 status = model[active][2].decode('utf-8')
3637 # status "desync'ed" or not
3638 statuses_unified = helpers.statuses_unified()
3639 if (active == 7 and statuses_unified) or (active == 9 and \
3640 not statuses_unified):
3641 # 'Change status message' selected:
3642 # do not change show, just show change status dialog
3643 status = model[self.previous_status_combobox_active][2].decode(
3644 'utf-8')
3645 def on_response(message, pep_dict):
3646 if message is not None: # None if user pressed Cancel
3647 for account in accounts:
3648 if not gajim.config.get_per('accounts', account,
3649 'sync_with_global_status'):
3650 continue
3651 current_show = gajim.SHOW_LIST[
3652 gajim.connections[account].connected]
3653 self.send_status(account, current_show, message)
3654 self.send_pep(account, pep_dict)
3655 self.combobox_callback_active = False
3656 self.status_combobox.set_active(
3657 self.previous_status_combobox_active)
3658 self.combobox_callback_active = True
3659 dialogs.ChangeStatusMessageDialog(on_response, status)
3660 return
3661 # we are about to change show, so save this new show so in case
3662 # after user chooses "Change status message" menuitem
3663 # we can return to this show
3664 self.previous_status_combobox_active = active
3665 connected_accounts = gajim.get_number_of_connected_accounts()
3667 def on_continue(message, pep_dict):
3668 if message is None:
3669 # user pressed Cancel to change status message dialog
3670 self.update_status_combobox()
3671 return
3672 global_sync_accounts = []
3673 for acct in accounts:
3674 if gajim.config.get_per('accounts', acct,
3675 'sync_with_global_status'):
3676 global_sync_accounts.append(acct)
3677 global_sync_connected_accounts = \
3678 gajim.get_number_of_connected_accounts(global_sync_accounts)
3679 for account in accounts:
3680 if not gajim.config.get_per('accounts', account,
3681 'sync_with_global_status'):
3682 continue
3683 # we are connected (so we wanna change show and status)
3684 # or no account is connected and we want to connect with new
3685 # show and status
3687 if not global_sync_connected_accounts > 0 or \
3688 gajim.connections[account].connected > 0:
3689 self.send_status(account, status, message)
3690 self.send_pep(account, pep_dict)
3691 self.update_status_combobox()
3693 if status == 'invisible':
3694 bug_user = False
3695 for account in accounts:
3696 if connected_accounts < 1 or gajim.account_is_connected(
3697 account):
3698 if not gajim.config.get_per('accounts', account,
3699 'sync_with_global_status'):
3700 continue
3701 # We're going to change our status to invisible
3702 if self.connected_rooms(account):
3703 bug_user = True
3704 break
3705 if bug_user:
3706 def on_ok():
3707 self.get_status_message(status, on_continue, show_pep=False)
3709 def on_cancel():
3710 self.update_status_combobox()
3712 dialogs.ConfirmationDialog(
3713 _('You are participating in one or more group chats'),
3714 _('Changing your status to invisible will result in '
3715 'disconnection from those group chats. Are you sure you '
3716 'want to go invisible?'), on_reponse_ok=on_ok,
3717 on_response_cancel=on_cancel)
3718 return
3720 self.get_status_message(status, on_continue)
3722 def on_preferences_menuitem_activate(self, widget):
3723 if 'preferences' in gajim.interface.instances:
3724 gajim.interface.instances['preferences'].window.present()
3725 else:
3726 gajim.interface.instances['preferences'] = config.PreferencesWindow(
3729 def on_plugins_menuitem_activate(self, widget):
3730 if gajim.interface.instances.has_key('plugins'):
3731 gajim.interface.instances['plugins'].window.present()
3732 else:
3733 gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow()
3735 def on_publish_tune_toggled(self, widget, account):
3736 active = widget.get_active()
3737 gajim.config.set_per('accounts', account, 'publish_tune', active)
3738 if active:
3739 gajim.interface.enable_music_listener()
3740 else:
3741 gajim.connections[account].retract_tune()
3742 # disable music listener only if no other account uses it
3743 for acc in gajim.connections:
3744 if gajim.config.get_per('accounts', acc, 'publish_tune'):
3745 break
3746 else:
3747 gajim.interface.disable_music_listener()
3749 helpers.update_optional_features(account)
3751 def on_publish_location_toggled(self, widget, account):
3752 active = widget.get_active()
3753 gajim.config.set_per('accounts', account, 'publish_location', active)
3754 if active:
3755 location_listener.enable()
3756 else:
3757 gajim.connections[account].retract_location()
3758 # disable music listener only if no other account uses it
3759 for acc in gajim.connections:
3760 if gajim.config.get_per('accounts', acc, 'publish_location'):
3761 break
3762 else:
3763 location_listener.disable()
3765 helpers.update_optional_features(account)
3767 def on_pep_services_menuitem_activate(self, widget, account):
3768 if 'pep_services' in gajim.interface.instances[account]:
3769 gajim.interface.instances[account]['pep_services'].window.present()
3770 else:
3771 gajim.interface.instances[account]['pep_services'] = \
3772 config.ManagePEPServicesWindow(account)
3774 def on_add_new_contact(self, widget, account):
3775 dialogs.AddNewContactWindow(account)
3777 def on_join_gc_activate(self, widget, account):
3779 When the join gc menuitem is clicked, show the join gc window
3781 invisible_show = gajim.SHOW_LIST.index('invisible')
3782 if gajim.connections[account].connected == invisible_show:
3783 dialogs.ErrorDialog(_('You cannot join a group chat while you are '
3784 'invisible'))
3785 return
3786 if 'join_gc' in gajim.interface.instances[account]:
3787 gajim.interface.instances[account]['join_gc'].window.present()
3788 else:
3789 try:
3790 gajim.interface.instances[account]['join_gc'] = \
3791 dialogs.JoinGroupchatWindow(account)
3792 except GajimGeneralException:
3793 pass
3795 def on_new_chat_menuitem_activate(self, widget, account):
3796 dialogs.NewChatDialog(account)
3798 def on_contents_menuitem_activate(self, widget):
3799 helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
3801 def on_faq_menuitem_activate(self, widget):
3802 helpers.launch_browser_mailer('url',
3803 'http://trac.gajim.org/wiki/GajimFaq')
3805 def on_features_menuitem_activate(self, widget):
3806 features_window.FeaturesWindow()
3808 def on_about_menuitem_activate(self, widget):
3809 dialogs.AboutDialog()
3811 def on_accounts_menuitem_activate(self, widget):
3812 if 'accounts' in gajim.interface.instances:
3813 gajim.interface.instances['accounts'].window.present()
3814 else:
3815 gajim.interface.instances['accounts'] = config.AccountsWindow()
3817 def on_file_transfers_menuitem_activate(self, widget):
3818 if gajim.interface.instances['file_transfers'].window.get_property(
3819 'visible'):
3820 gajim.interface.instances['file_transfers'].window.present()
3821 else:
3822 gajim.interface.instances['file_transfers'].window.show_all()
3824 def on_history_menuitem_activate(self, widget):
3825 if 'logs' in gajim.interface.instances:
3826 gajim.interface.instances['logs'].window.present()
3827 else:
3828 gajim.interface.instances['logs'] = history_window.\
3829 HistoryWindow()
3831 def on_show_transports_menuitem_activate(self, widget):
3832 gajim.config.set('show_transports_group', widget.get_active())
3833 self.refilter_shown_roster_items()
3835 def on_manage_bookmarks_menuitem_activate(self, widget):
3836 config.ManageBookmarksWindow()
3838 def on_profile_avatar_menuitem_activate(self, widget, account):
3839 gajim.interface.edit_own_details(account)
3841 def on_execute_command(self, widget, contact, account, resource=None):
3843 Execute command. Full JID needed; if it is other contact, resource is
3844 necessary. Widget is unnecessary, only to be able to make this a
3845 callback
3847 jid = contact.jid
3848 if resource is not None:
3849 jid = jid + u'/' + resource
3850 adhoc_commands.CommandWindow(account, jid)
3852 def on_roster_window_focus_in_event(self, widget, event):
3853 # roster received focus, so if we had urgency REMOVE IT
3854 # NOTE: we do not have to read the message to remove urgency
3855 # so this functions does that
3856 gtkgui_helpers.set_unset_urgency_hint(widget, False)
3858 # if a contact row is selected, update colors (eg. for status msg)
3859 # because gtk engines may differ in bg when window is selected
3860 # or not
3861 if len(self._last_selected_contact):
3862 for (jid, account) in self._last_selected_contact:
3863 self.draw_contact(jid, account, selected=True, focus=True)
3865 def on_roster_window_focus_out_event(self, widget, event):
3866 # if a contact row is selected, update colors (eg. for status msg)
3867 # because gtk engines may differ in bg when window is selected
3868 # or not
3869 if len(self._last_selected_contact):
3870 for (jid, account) in self._last_selected_contact:
3871 self.draw_contact(jid, account, selected=True, focus=False)
3873 def on_roster_window_key_press_event(self, widget, event):
3874 if event.keyval == gtk.keysyms.Escape:
3875 if gajim.interface.msg_win_mgr.mode == \
3876 MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
3877 gajim.interface.msg_win_mgr.one_window_opened():
3878 # let message window close the tab
3879 return
3880 list_of_paths = self.tree.get_selection().get_selected_rows()[1]
3881 if not len(list_of_paths) and not gajim.config.get(
3882 'quit_on_roster_x_button') and ((gajim.interface.systray_enabled and\
3883 gajim.config.get('trayicon') == 'always') or gajim.config.get(
3884 'allow_hide_roster')):
3885 self.tooltip.hide_tooltip()
3886 self.window.hide()
3887 elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \
3888 gtk.keysyms.i:
3889 treeselection = self.tree.get_selection()
3890 model, list_of_paths = treeselection.get_selected_rows()
3891 for path in list_of_paths:
3892 type_ = model[path][C_TYPE]
3893 if type_ in ('contact', 'agent'):
3894 jid = model[path][C_JID].decode('utf-8')
3895 account = model[path][C_ACCOUNT].decode('utf-8')
3896 contact = gajim.contacts.get_first_contact_from_jid(account,
3897 jid)
3898 self.on_info(widget, contact, account)
3899 elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \
3900 gtk.keysyms.h:
3901 treeselection = self.tree.get_selection()
3902 model, list_of_paths = treeselection.get_selected_rows()
3903 if len(list_of_paths) != 1:
3904 return
3905 path = list_of_paths[0]
3906 type_ = model[path][C_TYPE]
3907 if type_ in ('contact', 'agent'):
3908 jid = model[path][C_JID].decode('utf-8')
3909 account = model[path][C_ACCOUNT].decode('utf-8')
3910 contact = gajim.contacts.get_first_contact_from_jid(account,
3911 jid)
3912 self.on_history(widget, contact, account)
3914 def on_roster_window_popup_menu(self, widget):
3915 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
3916 self.show_treeview_menu(event)
3918 def on_row_activated(self, widget, path):
3920 When an iter is activated (double-click or single click if gnome is set
3921 this way)
3923 model = self.modelfilter
3924 account = model[path][C_ACCOUNT].decode('utf-8')
3925 type_ = model[path][C_TYPE]
3926 if type_ in ('group', 'account'):
3927 if self.tree.row_expanded(path):
3928 self.tree.collapse_row(path)
3929 else:
3930 self.tree.expand_row(path, False)
3931 return
3932 jid = model[path][C_JID].decode('utf-8')
3933 resource = None
3934 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
3935 titer = model.get_iter(path)
3936 if contact.is_groupchat():
3937 first_ev = gajim.events.get_first_event(account, jid)
3938 if first_ev and self.open_event(account, jid, first_ev):
3939 # We are invited to a GC
3940 # open event cares about connecting to it
3941 self.remove_groupchat(jid, account)
3942 else:
3943 self.on_groupchat_maximized(None, jid, account)
3944 return
3946 # else
3947 first_ev = gajim.events.get_first_event(account, jid)
3948 if not first_ev:
3949 # look in other resources
3950 for c in gajim.contacts.get_contacts(account, jid):
3951 fjid = c.get_full_jid()
3952 first_ev = gajim.events.get_first_event(account, fjid)
3953 if first_ev:
3954 resource = c.resource
3955 break
3956 if not first_ev and model.iter_has_child(titer):
3957 child_iter = model.iter_children(titer)
3958 while not first_ev and child_iter:
3959 child_jid = model[child_iter][C_JID].decode('utf-8')
3960 first_ev = gajim.events.get_first_event(account, child_jid)
3961 if first_ev:
3962 jid = child_jid
3963 else:
3964 child_iter = model.iter_next(child_iter)
3965 session = None
3966 if first_ev:
3967 if first_ev.type_ in ('chat', 'normal'):
3968 session = first_ev.parameters[8]
3969 fjid = jid
3970 if resource:
3971 fjid += '/' + resource
3972 if self.open_event(account, fjid, first_ev):
3973 return
3974 # else
3975 contact = gajim.contacts.get_contact(account, jid, resource)
3976 if not contact or isinstance(contact, list):
3977 contact = gajim.contacts.get_contact_with_highest_priority(account,
3978 jid)
3979 if jid == gajim.get_jid_from_account(account):
3980 resource = contact.resource
3982 gajim.interface.on_open_chat_window(None, contact, account, \
3983 resource=resource, session=session)
3985 def on_roster_treeview_row_activated(self, widget, path, col=0):
3987 When an iter is double clicked: open the first event window
3989 if not gajim.single_click:
3990 self.on_row_activated(widget, path)
3992 def on_roster_treeview_row_expanded(self, widget, titer, path):
3994 When a row is expanded change the icon of the arrow
3996 self._toggeling_row = True
3997 model = widget.get_model()
3998 child_model = model.get_model()
3999 child_iter = model.convert_iter_to_child_iter(titer)
4001 if self.regroup: # merged accounts
4002 accounts = gajim.connections.keys()
4003 else:
4004 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
4006 type_ = model[titer][C_TYPE]
4007 if type_ == 'group':
4008 group = model[titer][C_JID].decode('utf-8')
4009 child_model[child_iter][C_IMG] = \
4010 gajim.interface.jabber_state_images['16']['opened']
4011 for account in accounts:
4012 if group in gajim.groups[account]: # This account has this group
4013 gajim.groups[account][group]['expand'] = True
4014 if account + group in self.collapsed_rows:
4015 self.collapsed_rows.remove(account + group)
4016 for contact in gajim.contacts.iter_contacts(account):
4017 jid = contact.jid
4018 if group in contact.groups and \
4019 gajim.contacts.is_big_brother(account, jid, accounts) and \
4020 account + group + jid not in self.collapsed_rows:
4021 titers = self._get_contact_iter(jid, account)
4022 for titer in titers:
4023 path = model.get_path(titer)
4024 self.tree.expand_row(path, False)
4025 elif type_ == 'account':
4026 account = accounts[0] # There is only one cause we don't use merge
4027 if account in self.collapsed_rows:
4028 self.collapsed_rows.remove(account)
4029 self.draw_account(account)
4030 # When we expand, groups are collapsed. Restore expand state
4031 for group in gajim.groups[account]:
4032 if gajim.groups[account][group]['expand']:
4033 titer = self._get_group_iter(group, account)
4034 if titer:
4035 path = model.get_path(titer)
4036 self.tree.expand_row(path, False)
4037 elif type_ == 'contact':
4038 # Metacontact got toggled, update icon
4039 jid = model[titer][C_JID].decode('utf-8')
4040 account = model[titer][C_ACCOUNT].decode('utf-8')
4041 contact = gajim.contacts.get_contact(account, jid)
4042 for group in contact.groups:
4043 if account + group + jid in self.collapsed_rows:
4044 self.collapsed_rows.remove(account + group + jid)
4045 family = gajim.contacts.get_metacontacts_family(account, jid)
4046 nearby_family = \
4047 self._get_nearby_family_and_big_brother(family, account)[0]
4048 # Redraw all brothers to show pending events
4049 for data in nearby_family:
4050 self.draw_contact(data['jid'], data['account'])
4052 self._toggeling_row = False
4054 def on_roster_treeview_row_collapsed(self, widget, titer, path):
4056 When a row is collapsed change the icon of the arrow
4058 self._toggeling_row = True
4059 model = widget.get_model()
4060 child_model = model.get_model()
4061 child_iter = model.convert_iter_to_child_iter(titer)
4063 if self.regroup: # merged accounts
4064 accounts = gajim.connections.keys()
4065 else:
4066 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
4068 type_ = model[titer][C_TYPE]
4069 if type_ == 'group':
4070 child_model[child_iter][C_IMG] = gajim.interface.\
4071 jabber_state_images['16']['closed']
4072 group = model[titer][C_JID].decode('utf-8')
4073 for account in accounts:
4074 if group in gajim.groups[account]: # This account has this group
4075 gajim.groups[account][group]['expand'] = False
4076 if account + group not in self.collapsed_rows:
4077 self.collapsed_rows.append(account + group)
4078 elif type_ == 'account':
4079 account = accounts[0] # There is only one cause we don't use merge
4080 if account not in self.collapsed_rows:
4081 self.collapsed_rows.append(account)
4082 self.draw_account(account)
4083 elif type_ == 'contact':
4084 # Metacontact got toggled, update icon
4085 jid = model[titer][C_JID].decode('utf-8')
4086 account = model[titer][C_ACCOUNT].decode('utf-8')
4087 contact = gajim.contacts.get_contact(account, jid)
4088 for group in contact.groups:
4089 if account + group + jid not in self.collapsed_rows:
4090 self.collapsed_rows.append(account + group + jid)
4091 family = gajim.contacts.get_metacontacts_family(account, jid)
4092 nearby_family = \
4093 self._get_nearby_family_and_big_brother(family, account)[0]
4094 # Redraw all brothers to show pending events
4095 for data in nearby_family:
4096 self.draw_contact(data['jid'], data['account'])
4098 self._toggeling_row = False
4100 def on_modelfilter_row_has_child_toggled(self, model, path, titer):
4102 Called when a row has gotten the first or lost its last child row
4104 Expand Parent if necessary.
4106 if self._toggeling_row:
4107 # Signal is emitted when we write to our model
4108 return
4110 type_ = model[titer][C_TYPE]
4111 account = model[titer][C_ACCOUNT]
4112 if not account:
4113 return
4115 account = account.decode('utf-8')
4117 if type_ == 'contact':
4118 child_iter = model.convert_iter_to_child_iter(titer)
4119 if self.model.iter_has_child(child_iter):
4120 # we are a bigbrother metacontact
4121 # redraw us to show/hide expand icon
4122 if self.filtering:
4123 # Prevent endless loops
4124 jid = model[titer][C_JID].decode('utf-8')
4125 gobject.idle_add(self.draw_contact, jid, account)
4126 elif type_ == 'group':
4127 group = model[titer][C_JID].decode('utf-8')
4128 self._adjust_group_expand_collapse_state(group, account)
4129 elif type_ == 'account':
4130 self._adjust_account_expand_collapse_state(account)
4132 # Selection can change when the model is filtered
4133 # Only write to the model when filtering is finished!
4135 # FIXME: When we are filtering our custom colors are somehow lost
4137 # def on_treeview_selection_changed(self, selection):
4138 # '''Called when selection in TreeView has changed.
4140 # Redraw unselected rows to make status message readable
4141 # on all possible backgrounds.
4142 # '''
4143 # model, list_of_paths = selection.get_selected_rows()
4144 # if len(self._last_selected_contact):
4145 # # update unselected rows
4146 # for (jid, account) in self._last_selected_contact:
4147 # gobject.idle_add(self.draw_contact, jid,
4148 # account)
4149 # self._last_selected_contact = []
4150 # if len(list_of_paths) == 0:
4151 # return
4152 # for path in list_of_paths:
4153 # row = model[path]
4154 # if row[C_TYPE] != 'contact':
4155 # self._last_selected_contact = []
4156 # return
4157 # jid = row[C_JID].decode('utf-8')
4158 # account = row[C_ACCOUNT].decode('utf-8')
4159 # self._last_selected_contact.append((jid, account))
4160 # gobject.idle_add(self.draw_contact, jid, account, True)
4162 def on_service_disco_menuitem_activate(self, widget, account):
4163 server_jid = gajim.config.get_per('accounts', account, 'hostname')
4164 if server_jid in gajim.interface.instances[account]['disco']:
4165 gajim.interface.instances[account]['disco'][server_jid].\
4166 window.present()
4167 else:
4168 try:
4169 # Object will add itself to the window dict
4170 disco.ServiceDiscoveryWindow(account, address_entry=True)
4171 except GajimGeneralException:
4172 pass
4174 def on_show_offline_contacts_menuitem_activate(self, widget):
4176 When show offline option is changed: redraw the treeview
4178 gajim.config.set('showoffline', not gajim.config.get('showoffline'))
4179 self.refilter_shown_roster_items()
4180 w = self.xml.get_object('show_only_active_contacts_menuitem')
4181 if gajim.config.get('showoffline'):
4182 # We need to filter twice to show groups with no contacts inside
4183 # in the correct expand state
4184 self.refilter_shown_roster_items()
4185 w.set_sensitive(False)
4186 else:
4187 w.set_sensitive(True)
4189 def on_show_only_active_contacts_menuitem_activate(self, widget):
4191 When show only active contact option is changed: redraw the treeview
4193 gajim.config.set('show_only_chat_and_online', not gajim.config.get(
4194 'show_only_chat_and_online'))
4195 self.refilter_shown_roster_items()
4196 w = self.xml.get_object('show_offline_contacts_menuitem')
4197 if gajim.config.get('show_only_chat_and_online'):
4198 # We need to filter twice to show groups with no contacts inside
4199 # in the correct expand state
4200 self.refilter_shown_roster_items()
4201 w.set_sensitive(False)
4202 else:
4203 w.set_sensitive(True)
4205 def on_view_menu_activate(self, widget):
4206 # Hide the show roster menu if we are not in the right windowing mode.
4207 if self.hpaned.get_child2() is not None:
4208 self.xml.get_object('show_roster_menuitem').show()
4209 else:
4210 self.xml.get_object('show_roster_menuitem').hide()
4212 def on_show_roster_menuitem_toggled(self, widget):
4213 # when num controls is 0 this menuitem is hidden, but still need to
4214 # disable keybinding
4215 if self.hpaned.get_child2() is not None:
4216 self.show_roster_vbox(widget.get_active())
4218 def on_rfilter_entry_changed(self, widget):
4219 """ When we update the content of the filter """
4220 self.rfilter_string = widget.get_text().lower()
4221 if self.rfilter_string == '':
4222 self.disable_rfilter()
4223 self.refilter_shown_roster_items()
4225 def on_rfilter_entry_icon_press(self, widget, icon, event):
4227 Disable the roster filtering by clicking the icon in the textEntry
4229 self.disable_rfilter()
4231 def enable_rfilter(self, search_string):
4232 self.rfilter_enabled = True
4233 self.rfilter_entry.set_visible(True)
4234 self.rfilter_entry.set_editable(True)
4235 self.rfilter_entry.set_text(search_string)
4236 self.rfilter_entry.grab_focus()
4237 self.rfilter_entry.set_position(-1)
4239 def disable_rfilter(self):
4240 self.rfilter_enabled = False
4241 self.rfilter_entry.set_visible(False)
4242 self.rfilter_entry.set_editable(False)
4243 self.refilter_shown_roster_items()
4244 self.tree.grab_focus()
4246 def on_roster_hpaned_notify(self, pane, gparamspec):
4248 Keep changing the width of the roster
4249 (when a gtk.Paned widget handle is dragged)
4251 if gparamspec.name == 'position':
4252 roster_width = pane.get_child1().allocation.width
4253 gajim.config.set('roster_width', roster_width)
4255 ################################################################################
4256 ### Drag and Drop handling
4257 ################################################################################
4259 def drag_data_get_data(self, treeview, context, selection, target_id,
4260 etime):
4261 model, list_of_paths = self.tree.get_selection().get_selected_rows()
4262 if len(list_of_paths) != 1:
4263 return
4264 path = list_of_paths[0]
4265 data = ''
4266 if len(path) >= 2:
4267 data = model[path][C_JID]
4268 selection.set(selection.target, 8, data)
4270 def drag_begin(self, treeview, context):
4271 self.dragging = True
4273 def drag_end(self, treeview, context):
4274 self.dragging = False
4276 def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
4277 c_dest, was_big_brother, context, etime):
4278 gajim.connections[account_dest].send_contacts([c_source], c_dest.jid)
4280 def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
4281 c_dest, was_big_brother, context, etime):
4283 if not gajim.connections[account_source].private_storage_supported or \
4284 not gajim.connections[account_dest].private_storage_supported:
4285 dialogs.WarningDialog(_('Metacontacts storage not supported by '
4286 'your server'),
4287 _('Your server does not support storing metacontacts '
4288 'information. So those information will not be saved on next '
4289 'reconnection.'))
4291 def merge_contacts(is_checked=None):
4292 contacts = 0
4293 if is_checked is not None: # dialog has been shown
4294 if is_checked: # user does not want to be asked again
4295 gajim.config.set('confirm_metacontacts', 'no')
4296 else:
4297 gajim.config.set('confirm_metacontacts', 'yes')
4299 # We might have dropped on a metacontact.
4300 # Remove it and readd later with updated family info
4301 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
4302 c_dest.jid)
4303 if dest_family:
4304 self._remove_metacontact_family(dest_family, account_dest)
4305 source_family = gajim.contacts.get_metacontacts_family(
4306 account_source, c_source.jid)
4307 if dest_family == source_family:
4308 n = contacts = len(dest_family)
4309 for tag in source_family:
4310 if tag['jid'] == c_source.jid:
4311 tag['order'] = contacts
4312 continue
4313 if 'order' in tag:
4314 n -= 1
4315 tag['order'] = n
4316 else:
4317 self._remove_entity(c_dest, account_dest)
4319 old_family = gajim.contacts.get_metacontacts_family(account_source,
4320 c_source.jid)
4321 old_groups = c_source.groups
4323 # Remove old source contact(s)
4324 if was_big_brother:
4325 # We have got little brothers. Readd them all
4326 self._remove_metacontact_family(old_family, account_source)
4327 else:
4328 # We are only a litle brother. Simply remove us from our big
4329 # brother
4330 if self._get_contact_iter(c_source.jid, account_source):
4331 # When we have been in the group before.
4332 # Do not try to remove us again
4333 self._remove_entity(c_source, account_source)
4335 own_data = {}
4336 own_data['jid'] = c_source.jid
4337 own_data['account'] = account_source
4338 # Don't touch the rest of the family
4339 old_family = [own_data]
4341 # Apply new tag and update contact
4342 for data in old_family:
4343 if account_source != data['account'] and not self.regroup:
4344 continue
4346 _account = data['account']
4347 _jid = data['jid']
4348 _contact = gajim.contacts.get_first_contact_from_jid(_account,
4349 _jid)
4350 if not _contact:
4351 # One of the metacontacts may be not connected.
4352 continue
4354 _contact.groups = c_dest.groups[:]
4355 gajim.contacts.add_metacontact(account_dest, c_dest.jid,
4356 _account, _contact.jid, contacts)
4357 gajim.connections[account_source].update_contact(_contact.jid,
4358 _contact.name, _contact.groups)
4360 # Re-add all and update GUI
4361 new_family = gajim.contacts.get_metacontacts_family(account_source,
4362 c_source.jid)
4363 brothers = self._add_metacontact_family(new_family, account_source)
4365 for c, acc in brothers:
4366 self.draw_completely(c.jid, acc)
4368 old_groups.extend(c_dest.groups)
4369 for g in old_groups:
4370 self.draw_group(g, account_source)
4372 self.draw_account(account_source)
4373 context.finish(True, True, etime)
4375 confirm_metacontacts = gajim.config.get('confirm_metacontacts')
4376 if confirm_metacontacts == 'no':
4377 merge_contacts()
4378 return
4379 pritext = _('You are about to create a metacontact. Are you sure you '
4380 'want to continue?')
4381 sectext = _('Metacontacts are a way to regroup several contacts in one '
4382 'line. Generally it is used when the same person has several '
4383 'Jabber accounts or transport accounts.')
4384 dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
4385 _('_Do not ask me again'), on_response_ok=merge_contacts)
4386 if not confirm_metacontacts: # First time we see this window
4387 dlg.checkbutton.set_active(True)
4389 def on_drop_in_group(self, widget, account, c_source, grp_dest,
4390 is_big_brother, context, etime, grp_source = None):
4391 if is_big_brother:
4392 # add whole metacontact to new group
4393 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4394 # remove afterwards so the contact is not moved to General in the
4395 # meantime
4396 if grp_dest != grp_source:
4397 self.remove_contact_from_groups(c_source.jid, account,
4398 [grp_source])
4399 else:
4400 # Normal contact or little brother
4401 family = gajim.contacts.get_metacontacts_family(account,
4402 c_source.jid)
4403 if family:
4404 # Little brother
4405 # Remove whole family. Remove us from the family.
4406 # Then re-add other family members.
4407 self._remove_metacontact_family(family, account)
4408 gajim.contacts.remove_metacontact(account, c_source.jid)
4409 for data in family:
4410 if account != data['account'] and not self.regroup:
4411 continue
4412 if data['jid'] == c_source.jid and\
4413 data['account'] == account:
4414 continue
4415 self.add_contact(data['jid'], data['account'])
4416 break
4418 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4420 else:
4421 # Normal contact
4422 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4423 # remove afterwards so the contact is not moved to General in
4424 # the meantime
4425 if grp_dest != grp_source:
4426 self.remove_contact_from_groups(c_source.jid, account,
4427 [grp_source])
4429 if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY):
4430 context.finish(True, True, etime)
4432 def drag_drop(self, treeview, context, x, y, timestamp):
4433 target_list = treeview.drag_dest_get_target_list()
4434 target = treeview.drag_dest_find_target(context, target_list)
4435 treeview.drag_get_data(context, target)
4436 context.finish(False, True)
4437 return True
4439 def move_group(self, old_name, new_name, account):
4440 for group in gajim.groups[account].keys():
4441 if group.startswith(old_name):
4442 self.rename_group(group, group.replace(old_name, new_name),
4443 account)
4445 def drag_data_received_data(self, treeview, context, x, y, selection, info,
4446 etime):
4447 treeview.stop_emission('drag_data_received')
4448 drop_info = treeview.get_dest_row_at_pos(x, y)
4449 if not drop_info:
4450 return
4451 if not selection.data:
4452 return # prevents tb when several entrys are dragged
4453 model = treeview.get_model()
4454 data = selection.data
4455 path_dest, position = drop_info
4457 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \
4458 and path_dest[1] == 0: # dropped before the first group
4459 return
4460 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
4461 # dropped before a group: we drop it in the previous group every
4462 # time
4463 path_dest = (path_dest[0], path_dest[1]-1)
4464 # destination: the row something got dropped on
4465 iter_dest = model.get_iter(path_dest)
4466 type_dest = model[iter_dest][C_TYPE].decode('utf-8')
4467 jid_dest = model[iter_dest][C_JID].decode('utf-8')
4468 account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
4470 # drop on account row in merged mode, we cannot know the desired account
4471 if account_dest == 'all':
4472 return
4473 # nothing can be done, if destination account is offline
4474 if gajim.connections[account_dest].connected < 2:
4475 return
4477 # A file got dropped on the roster
4478 if info == self.TARGET_TYPE_URI_LIST:
4479 if len(path_dest) < 3:
4480 return
4481 if type_dest != 'contact':
4482 return
4483 c_dest = gajim.contacts.get_contact_with_highest_priority(
4484 account_dest, jid_dest)
4485 if not c_dest.supports(NS_FILE):
4486 return
4487 uri = data.strip()
4488 uri_splitted = uri.split() # we may have more than one file dropped
4489 try:
4490 # This is always the last element in windows
4491 uri_splitted.remove('\0')
4492 except ValueError:
4493 pass
4494 nb_uri = len(uri_splitted)
4495 # Check the URIs
4496 bad_uris = []
4497 for a_uri in uri_splitted:
4498 path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
4499 if not os.path.isfile(path):
4500 bad_uris.append(a_uri)
4501 if len(bad_uris):
4502 dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
4503 return
4504 def _on_send_files(account, jid, uris):
4505 c = gajim.contacts.get_contact_with_highest_priority(account,
4506 jid)
4507 for uri in uris:
4508 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
4509 if os.path.isfile(path): # is it file?
4510 gajim.interface.instances['file_transfers'].send_file(
4511 account, c, path)
4512 # Popup dialog to confirm sending
4513 prim_text = 'Send file?'
4514 sec_text = i18n.ngettext('Do you want to send this file to %s:',
4515 'Do you want to send these files to %s:', nb_uri) %\
4516 c_dest.get_shown_name()
4517 for uri in uri_splitted:
4518 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
4519 sec_text += '\n' + os.path.basename(path)
4520 dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
4521 on_response_ok=(_on_send_files, account_dest, jid_dest,
4522 uri_splitted))
4523 dialog.popup()
4524 return
4526 # a roster entry was dragged and dropped somewhere in the roster
4528 # source: the row that was dragged
4529 path_source = treeview.get_selection().get_selected_rows()[1][0]
4530 iter_source = model.get_iter(path_source)
4531 type_source = model[iter_source][C_TYPE]
4532 account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
4534 if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
4535 return
4537 if type_dest == 'self_contact':
4538 # drop on self contact row
4539 return
4541 if type_dest == 'groupchat':
4542 # drop on a minimized groupchat
4543 # TODO: Invite to groupchat if type_dest = contact
4544 return
4546 if type_source == 'group':
4547 if account_source != account_dest:
4548 # drop on another account
4549 return
4550 grp_source = model[iter_source][C_JID].decode('utf-8')
4551 delimiter = gajim.connections[account_source].nested_group_delimiter
4552 grp_source_list = grp_source.split(delimiter)
4553 new_grp = None
4554 if type_dest == 'account':
4555 new_grp = grp_source_list[-1]
4556 elif type_dest == 'group':
4557 new_grp = model[iter_dest][C_JID].decode('utf-8') + delimiter +\
4558 grp_source_list[-1]
4559 if new_grp:
4560 self.move_group(grp_source, new_grp, account_source)
4562 # Only normal contacts and group can be dragged
4563 if type_source != 'contact':
4564 return
4566 # A contact was dropped
4567 if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
4568 # drop on zeroconf account, adding not possible
4569 return
4571 if type_dest == 'account' and account_source == account_dest:
4572 # drop on the account it was dragged from
4573 return
4575 # Get valid source group, jid and contact
4576 it = iter_source
4577 while model[it][C_TYPE] == 'contact':
4578 it = model.iter_parent(it)
4579 grp_source = model[it][C_JID].decode('utf-8')
4580 if grp_source in helpers.special_groups and \
4581 grp_source not in ('Not in Roster', 'Observers'):
4582 # a transport or a minimized groupchat was dragged
4583 # we can add it to other accounts but not move it to another group,
4584 # see below
4585 return
4586 jid_source = data.decode('utf-8')
4587 c_source = gajim.contacts.get_contact_with_highest_priority(
4588 account_source, jid_source)
4590 # Get destination group
4591 grp_dest = None
4592 if type_dest == 'group':
4593 grp_dest = model[iter_dest][C_JID].decode('utf-8')
4594 elif type_dest in ('contact', 'agent'):
4595 it = iter_dest
4596 while model[it][C_TYPE] != 'group':
4597 it = model.iter_parent(it)
4598 grp_dest = model[it][C_JID].decode('utf-8')
4599 if grp_dest in helpers.special_groups:
4600 return
4602 if jid_source == jid_dest:
4603 if grp_source == grp_dest and account_source == account_dest:
4604 # Drop on self
4605 return
4607 # contact drop somewhere in or on a foreign account
4608 if (type_dest == 'account' or not self.regroup) and \
4609 account_source != account_dest:
4610 # add to account in specified group
4611 dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
4612 user_nick=c_source.name, group=grp_dest)
4613 return
4615 # we may not add contacts from special_groups
4616 if grp_source in helpers.special_groups :
4617 return
4619 # Is the contact we drag a meta contact?
4620 accounts = (self.regroup and gajim.contacts.get_accounts()) or \
4621 account_source
4622 is_big_brother = gajim.contacts.is_big_brother(account_source,
4623 jid_source, accounts)
4625 drop_in_middle_of_meta = False
4626 if type_dest == 'contact':
4627 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4:
4628 drop_in_middle_of_meta = True
4629 if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or\
4630 self.modelfilter.iter_has_child(iter_dest)):
4631 drop_in_middle_of_meta = True
4632 # Contact drop on group row or between two contacts that are
4633 # not metacontacts
4634 if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE,
4635 gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta:
4636 self.on_drop_in_group(None, account_source, c_source, grp_dest,
4637 is_big_brother, context, etime, grp_source)
4638 return
4640 # Contact drop on another contact, make meta contacts
4641 if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \
4642 position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta:
4643 c_dest = gajim.contacts.get_contact_with_highest_priority(
4644 account_dest, jid_dest)
4645 if not c_dest:
4646 # c_dest is None if jid_dest doesn't belong to account
4647 return
4648 menu = gtk.Menu()
4649 item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(),
4650 c_dest.get_shown_name()))
4651 item.connect('activate', self.on_drop_rosterx, account_source,
4652 c_source, account_dest, c_dest, is_big_brother, context, etime)
4653 menu.append(item)
4655 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
4656 c_dest.jid)
4657 source_family = gajim.contacts.get_metacontacts_family(
4658 account_source, c_source.jid)
4659 if dest_family == source_family:
4660 item = gtk.MenuItem(_('Make %s first contact') % (
4661 c_source.get_shown_name()))
4662 else:
4663 item = gtk.MenuItem(_('Make %s and %s metacontacts') % (
4664 c_source.get_shown_name(), c_dest.get_shown_name()))
4666 item.connect('activate', self.on_drop_in_contact, account_source,
4667 c_source, account_dest, c_dest, is_big_brother, context, etime)
4669 menu.append(item)
4671 menu.attach_to_widget(self.tree, None)
4672 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
4673 menu.show_all()
4674 menu.popup(None, None, None, 1, etime)
4676 ################################################################################
4677 ### Everything about images and icons....
4678 ### Cleanup assigned to Jim++ :-)
4679 ################################################################################
4681 def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
4683 Check jid and return the appropriate state images dict for the demanded
4684 size. icon_name is taken into account when jid is from transport:
4685 transport iconset doesn't contain all icons, so we fall back to jabber
4688 transport = gajim.get_transport_name_from_jid(jid)
4689 if transport and size in self.transports_state_images:
4690 if transport not in self.transports_state_images[size]:
4691 # we don't have iconset for this transport loaded yet. Let's do
4692 # it
4693 self.make_transport_state_images(transport)
4694 if transport in self.transports_state_images[size] and \
4695 icon_name in self.transports_state_images[size][transport]:
4696 return self.transports_state_images[size][transport]
4697 return gajim.interface.jabber_state_images[size]
4699 def make_transport_state_images(self, transport):
4701 Initialize opened and closed 'transport' iconset dict
4703 if gajim.config.get('use_transports_iconsets'):
4704 folder = os.path.join(helpers.get_transport_path(transport),
4705 '16x16')
4706 pixo, pixc = gtkgui_helpers.load_icons_meta()
4707 self.transports_state_images['opened'][transport] = \
4708 gtkgui_helpers.load_iconset(folder, pixo, transport=True)
4709 self.transports_state_images['closed'][transport] = \
4710 gtkgui_helpers.load_iconset(folder, pixc, transport=True)
4711 folder = os.path.join(helpers.get_transport_path(transport),
4712 '32x32')
4713 self.transports_state_images['32'][transport] = \
4714 gtkgui_helpers.load_iconset(folder, transport=True)
4715 folder = os.path.join(helpers.get_transport_path(transport),
4716 '16x16')
4717 self.transports_state_images['16'][transport] = \
4718 gtkgui_helpers.load_iconset(folder, transport=True)
4720 def update_jabber_state_images(self):
4721 # Update the roster
4722 self.setup_and_draw_roster()
4723 # Update the status combobox
4724 model = self.status_combobox.get_model()
4725 titer = model.get_iter_root()
4726 while titer:
4727 if model[titer][2] != '':
4728 # If it's not change status message iter
4729 # eg. if it has show parameter not ''
4730 model[titer][1] = gajim.interface.jabber_state_images['16'][
4731 model[titer][2]]
4732 titer = model.iter_next(titer)
4733 # Update the systray
4734 if gajim.interface.systray_enabled:
4735 gajim.interface.systray.set_img()
4737 for win in gajim.interface.msg_win_mgr.windows():
4738 for ctrl in win.controls():
4739 ctrl.update_ui()
4740 win.redraw_tab(ctrl)
4742 self.update_status_combobox()
4744 def set_account_status_icon(self, account):
4745 status = gajim.connections[account].connected
4746 child_iterA = self._get_account_iter(account, self.model)
4747 if not child_iterA:
4748 return
4749 if not self.regroup:
4750 show = gajim.SHOW_LIST[status]
4751 else: # accounts merged
4752 show = helpers.get_global_show()
4753 self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[
4754 '16'][show]
4756 ################################################################################
4757 ### Style and theme related methods
4758 ################################################################################
4760 def show_title(self):
4761 change_title_allowed = gajim.config.get('change_roster_title')
4762 if not change_title_allowed:
4763 return
4765 if gajim.config.get('one_message_window') == 'always_with_roster':
4766 # always_with_roster mode defers to the MessageWindow
4767 if not gajim.interface.msg_win_mgr.one_window_opened():
4768 # No MessageWindow to defer to
4769 self.window.set_title('Gajim')
4770 return
4772 nb_unread = 0
4773 start = ''
4774 for account in gajim.connections:
4775 # Count events in roster title only if we don't auto open them
4776 if not helpers.allow_popup_window(account):
4777 nb_unread += gajim.events.get_nb_events(['chat', 'normal',
4778 'file-request', 'file-error', 'file-completed',
4779 'file-request-error', 'file-send-error', 'file-stopped',
4780 'printed_chat'], account)
4781 if nb_unread > 1:
4782 start = '[' + str(nb_unread) + '] '
4783 elif nb_unread == 1:
4784 start = '* '
4786 self.window.set_title(start + 'Gajim')
4788 gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
4790 def _change_style(self, model, path, titer, option):
4791 if option is None or model[titer][C_TYPE] == option:
4792 # We changed style for this type of row
4793 model[titer][C_NAME] = model[titer][C_NAME]
4795 def change_roster_style(self, option):
4796 self.model.foreach(self._change_style, option)
4797 for win in gajim.interface.msg_win_mgr.windows():
4798 win.repaint_themed_widgets()
4800 def repaint_themed_widgets(self):
4802 Notify windows that contain themed widgets to repaint them
4804 for win in gajim.interface.msg_win_mgr.windows():
4805 win.repaint_themed_widgets()
4806 for account in gajim.connections:
4807 for addr in gajim.interface.instances[account]['disco']:
4808 gajim.interface.instances[account]['disco'][addr].paint_banner()
4809 for ctrl in gajim.interface.minimized_controls[account].values():
4810 ctrl.repaint_themed_widgets()
4812 def update_avatar_in_gui(self, jid, account):
4813 # Update roster
4814 self.draw_avatar(jid, account)
4815 # Update chat window
4817 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
4818 if ctrl:
4819 ctrl.show_avatar()
4821 def set_renderer_color(self, renderer, style, set_background=True):
4823 Set style for treeview cell, using PRELIGHT system color
4825 if set_background:
4826 bgcolor = self.tree.style.bg[style]
4827 renderer.set_property('cell-background-gdk', bgcolor)
4828 else:
4829 fgcolor = self.tree.style.fg[style]
4830 renderer.set_property('foreground-gdk', fgcolor)
4832 def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
4834 When a row is added, set properties for icon renderer
4836 type_ = model[titer][C_TYPE]
4837 if type_ == 'account':
4838 self._set_account_row_background_color(renderer)
4839 renderer.set_property('xalign', 0)
4840 elif type_ == 'group':
4841 self._set_group_row_background_color(renderer)
4842 parent_iter = model.iter_parent(titer)
4843 if model[parent_iter][C_TYPE] == 'group':
4844 renderer.set_property('xalign', 0.4)
4845 else:
4846 renderer.set_property('xalign', 0.2)
4847 elif type_:
4848 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4849 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4850 # This can append when at the moment we add the row
4851 return
4852 jid = model[titer][C_JID].decode('utf-8')
4853 account = model[titer][C_ACCOUNT].decode('utf-8')
4854 self._set_contact_row_background_color(renderer, jid, account)
4855 parent_iter = model.iter_parent(titer)
4856 if model[parent_iter][C_TYPE] == 'contact':
4857 renderer.set_property('xalign', 1)
4858 else:
4859 renderer.set_property('xalign', 0.6)
4860 renderer.set_property('width', 26)
4862 def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
4864 When a row is added, set properties for name renderer
4866 theme = gajim.config.get('roster_theme')
4867 type_ = model[titer][C_TYPE]
4868 if type_ == 'account':
4869 color = gajim.config.get_per('themes', theme, 'accounttextcolor')
4870 if color:
4871 renderer.set_property('foreground', color)
4872 else:
4873 self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False)
4874 renderer.set_property('font',
4875 gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
4876 renderer.set_property('xpad', 0)
4877 renderer.set_property('width', 3)
4878 self._set_account_row_background_color(renderer)
4879 elif type_ == 'group':
4880 color = gajim.config.get_per('themes', theme, 'grouptextcolor')
4881 if color:
4882 renderer.set_property('foreground', color)
4883 else:
4884 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
4885 renderer.set_property('font',
4886 gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
4887 parent_iter = model.iter_parent(titer)
4888 if model[parent_iter][C_TYPE] == 'group':
4889 renderer.set_property('xpad', 8)
4890 else:
4891 renderer.set_property('xpad', 4)
4892 self._set_group_row_background_color(renderer)
4893 elif type_:
4894 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4895 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4896 # This can append when at the moment we add the row
4897 return
4898 jid = model[titer][C_JID].decode('utf-8')
4899 account = model[titer][C_ACCOUNT].decode('utf-8')
4900 color = None
4901 if type_ == 'groupchat':
4902 ctrl = gajim.interface.minimized_controls[account].get(jid,
4903 None)
4904 if ctrl and ctrl.attention_flag:
4905 color = gajim.config.get_per('themes', theme,
4906 'state_muc_directed_msg_color')
4907 renderer.set_property('foreground', 'red')
4908 if not color:
4909 color = gajim.config.get_per('themes', theme,
4910 'contacttextcolor')
4911 if color:
4912 renderer.set_property('foreground', color)
4913 else:
4914 renderer.set_property('foreground', None)
4915 self._set_contact_row_background_color(renderer, jid, account)
4916 renderer.set_property('font',
4917 gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
4918 parent_iter = model.iter_parent(titer)
4919 if model[parent_iter][C_TYPE] == 'contact':
4920 renderer.set_property('xpad', 16)
4921 else:
4922 renderer.set_property('xpad', 12)
4924 def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
4925 data=None):
4927 When a row is added, draw the respective pep icon
4929 type_ = model[titer][C_TYPE]
4931 # allocate space for the icon only if needed
4932 if not model[titer][data]:
4933 renderer.set_property('visible', False)
4934 else:
4935 renderer.set_property('visible', True)
4937 if type_ == 'account':
4938 self._set_account_row_background_color(renderer)
4939 renderer.set_property('xalign', 1)
4940 elif type_:
4941 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4942 # This can append at the moment we add the row
4943 return
4944 jid = model[titer][C_JID].decode('utf-8')
4945 account = model[titer][C_ACCOUNT].decode('utf-8')
4946 self._set_contact_row_background_color(renderer, jid, account)
4948 def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer,
4949 data=None):
4951 When a row is added, set properties for avatar renderer
4953 type_ = model[titer][C_TYPE]
4954 if type_ in ('group', 'account'):
4955 renderer.set_property('visible', False)
4956 return
4958 # allocate space for the icon only if needed
4959 if model[titer][C_AVATAR_PIXBUF] or \
4960 gajim.config.get('avatar_position_in_roster') == 'left':
4961 renderer.set_property('visible', True)
4962 if type_:
4963 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4964 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4965 # This can append at the moment we add the row
4966 return
4967 jid = model[titer][C_JID].decode('utf-8')
4968 account = model[titer][C_ACCOUNT].decode('utf-8')
4969 self._set_contact_row_background_color(renderer, jid, account)
4970 else:
4971 renderer.set_property('visible', False)
4973 if gajim.config.get('avatar_position_in_roster') == 'left':
4974 renderer.set_property('width', gajim.config.get(
4975 'roster_avatar_width'))
4976 renderer.set_property('xalign', 0.5)
4977 else:
4978 renderer.set_property('xalign', 1) # align pixbuf to the right
4980 def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer,
4981 data=None):
4983 When a row is added, set properties for padlock renderer
4985 type_ = model[titer][C_TYPE]
4986 # allocate space for the icon only if needed
4987 if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]:
4988 renderer.set_property('visible', True)
4989 self._set_account_row_background_color(renderer)
4990 renderer.set_property('xalign', 1) # align pixbuf to the right
4991 else:
4992 renderer.set_property('visible', False)
4994 def _set_account_row_background_color(self, renderer):
4995 theme = gajim.config.get('roster_theme')
4996 color = gajim.config.get_per('themes', theme, 'accountbgcolor')
4997 if color:
4998 renderer.set_property('cell-background', color)
4999 else:
5000 self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
5002 def _set_contact_row_background_color(self, renderer, jid, account):
5003 theme = gajim.config.get('roster_theme')
5004 if jid in gajim.newly_added[account]:
5005 renderer.set_property('cell-background', gajim.config.get(
5006 'just_connected_bg_color'))
5007 elif jid in gajim.to_be_removed[account]:
5008 renderer.set_property('cell-background', gajim.config.get(
5009 'just_disconnected_bg_color'))
5010 else:
5011 color = gajim.config.get_per('themes', theme, 'contactbgcolor')
5012 renderer.set_property('cell-background', color if color else None)
5014 def _set_group_row_background_color(self, renderer):
5015 theme = gajim.config.get('roster_theme')
5016 color = gajim.config.get_per('themes', theme, 'groupbgcolor')
5017 if color:
5018 renderer.set_property('cell-background', color)
5019 else:
5020 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
5022 ################################################################################
5023 ### Everything about building menus
5024 ### FIXME: We really need to make it simpler! 1465 lines are a few to much....
5025 ################################################################################
5027 def make_menu(self, force=False):
5029 Create the main window's menus
5031 if not force and not self.actions_menu_needs_rebuild:
5032 return
5033 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
5034 single_message_menuitem = self.xml.get_object(
5035 'send_single_message_menuitem')
5036 join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
5037 muc_icon = gtkgui_helpers.load_icon('muc_active')
5038 if muc_icon:
5039 join_gc_menuitem.set_image(muc_icon)
5040 add_new_contact_menuitem = self.xml.get_object(
5041 'add_new_contact_menuitem')
5042 service_disco_menuitem = self.xml.get_object('service_disco_menuitem')
5043 advanced_menuitem = self.xml.get_object('advanced_menuitem')
5044 profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem')
5046 # destroy old advanced menus
5047 for m in self.advanced_menus:
5048 m.destroy()
5050 # make it sensitive. it is insensitive only if no accounts are
5051 # *available*
5052 advanced_menuitem.set_sensitive(True)
5054 if self.add_new_contact_handler_id:
5055 add_new_contact_menuitem.handler_disconnect(
5056 self.add_new_contact_handler_id)
5057 self.add_new_contact_handler_id = None
5059 if self.service_disco_handler_id:
5060 service_disco_menuitem.handler_disconnect(
5061 self.service_disco_handler_id)
5062 self.service_disco_handler_id = None
5064 if self.single_message_menuitem_handler_id:
5065 single_message_menuitem.handler_disconnect(
5066 self.single_message_menuitem_handler_id)
5067 self.single_message_menuitem_handler_id = None
5069 if self.profile_avatar_menuitem_handler_id:
5070 profile_avatar_menuitem.handler_disconnect(
5071 self.profile_avatar_menuitem_handler_id)
5072 self.profile_avatar_menuitem_handler_id = None
5074 # remove the existing submenus
5075 add_new_contact_menuitem.remove_submenu()
5076 service_disco_menuitem.remove_submenu()
5077 join_gc_menuitem.remove_submenu()
5078 single_message_menuitem.remove_submenu()
5079 advanced_menuitem.remove_submenu()
5080 profile_avatar_menuitem.remove_submenu()
5082 gc_sub_menu = gtk.Menu() # gc is always a submenu
5083 join_gc_menuitem.set_submenu(gc_sub_menu)
5085 connected_accounts = gajim.get_number_of_connected_accounts()
5087 connected_accounts_with_private_storage = 0
5089 # items that get shown whether an account is zeroconf or not
5090 accounts_list = sorted(gajim.contacts.get_accounts())
5091 if connected_accounts > 2 or \
5092 (connected_accounts > 1 and not gajim.zeroconf_is_connected()):
5093 # 2 or more "real" (no zeroconf) accounts? make submenus
5094 new_chat_sub_menu = gtk.Menu()
5096 for account in accounts_list:
5097 if gajim.connections[account].connected <= 1 or \
5098 gajim.config.get_per('accounts', account, 'is_zeroconf'):
5099 # if offline or connecting or zeroconf
5100 continue
5102 # new chat
5103 new_chat_item = gtk.MenuItem(_('using account %s') % account,
5104 False)
5105 new_chat_sub_menu.append(new_chat_item)
5106 new_chat_item.connect('activate',
5107 self.on_new_chat_menuitem_activate, account)
5109 new_chat_menuitem.set_submenu(new_chat_sub_menu)
5110 new_chat_sub_menu.show_all()
5112 # menu items that don't apply to zeroconf connections
5113 if connected_accounts == 1 or (connected_accounts == 2 and \
5114 gajim.zeroconf_is_connected()):
5115 # only one 'real' (non-zeroconf) account is connected, don't need
5116 # submenus
5118 for account in accounts_list:
5119 if gajim.account_is_connected(account) and \
5120 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
5121 # gc
5122 if gajim.connections[account].private_storage_supported:
5123 connected_accounts_with_private_storage += 1
5124 self.add_bookmarks_list(gc_sub_menu, account)
5125 gc_sub_menu.show_all()
5126 # add
5127 if not self.add_new_contact_handler_id:
5128 self.add_new_contact_handler_id = \
5129 add_new_contact_menuitem.connect(
5130 'activate', self.on_add_new_contact, account)
5131 # disco
5132 if not self.service_disco_handler_id:
5133 self.service_disco_handler_id = service_disco_menuitem.\
5134 connect('activate',
5135 self.on_service_disco_menuitem_activate, account)
5137 # single message
5138 if not self.single_message_menuitem_handler_id:
5139 self.single_message_menuitem_handler_id = \
5140 single_message_menuitem.connect('activate', \
5141 self.on_send_single_message_menuitem_activate, account)
5143 break # No other account connected
5144 else:
5145 # 2 or more 'real' accounts are connected, make submenus
5146 single_message_sub_menu = gtk.Menu()
5147 add_sub_menu = gtk.Menu()
5148 disco_sub_menu = gtk.Menu()
5150 for account in accounts_list:
5151 if gajim.connections[account].connected <= 1 or \
5152 gajim.config.get_per('accounts', account, 'is_zeroconf'):
5153 # skip account if it's offline or connecting or is zeroconf
5154 continue
5156 # single message
5157 single_message_item = gtk.MenuItem(_('using account %s') % \
5158 account, False)
5159 single_message_sub_menu.append(single_message_item)
5160 single_message_item.connect('activate',
5161 self.on_send_single_message_menuitem_activate, account)
5163 # join gc
5164 if gajim.connections[account].private_storage_supported:
5165 connected_accounts_with_private_storage += 1
5166 gc_item = gtk.MenuItem(_('using account %s') % account, False)
5167 gc_sub_menu.append(gc_item)
5168 gc_menuitem_menu = gtk.Menu()
5169 self.add_bookmarks_list(gc_menuitem_menu, account)
5170 gc_item.set_submenu(gc_menuitem_menu)
5172 # add
5173 add_item = gtk.MenuItem(_('to %s account') % account, False)
5174 add_sub_menu.append(add_item)
5175 add_item.connect('activate', self.on_add_new_contact, account)
5177 # disco
5178 disco_item = gtk.MenuItem(_('using %s account') % account,
5179 False)
5180 disco_sub_menu.append(disco_item)
5181 disco_item.connect('activate',
5182 self.on_service_disco_menuitem_activate, account)
5184 single_message_menuitem.set_submenu(single_message_sub_menu)
5185 single_message_sub_menu.show_all()
5186 gc_sub_menu.show_all()
5187 add_new_contact_menuitem.set_submenu(add_sub_menu)
5188 add_sub_menu.show_all()
5189 service_disco_menuitem.set_submenu(disco_sub_menu)
5190 disco_sub_menu.show_all()
5192 if connected_accounts == 0:
5193 # no connected accounts, make the menuitems insensitive
5194 for item in (new_chat_menuitem, join_gc_menuitem,
5195 add_new_contact_menuitem, service_disco_menuitem,
5196 single_message_menuitem):
5197 item.set_sensitive(False)
5198 else: # we have one or more connected accounts
5199 for item in (new_chat_menuitem, join_gc_menuitem,
5200 add_new_contact_menuitem, service_disco_menuitem,
5201 single_message_menuitem):
5202 item.set_sensitive(True)
5203 # disable some fields if only local account is there
5204 if connected_accounts == 1:
5205 for account in gajim.connections:
5206 if gajim.account_is_connected(account) and \
5207 gajim.connections[account].is_zeroconf:
5208 for item in (new_chat_menuitem, join_gc_menuitem,
5209 add_new_contact_menuitem, service_disco_menuitem,
5210 single_message_menuitem):
5211 item.set_sensitive(False)
5213 # Manage GC bookmarks
5214 newitem = gtk.SeparatorMenuItem() # separator
5215 gc_sub_menu.append(newitem)
5217 newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
5218 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5219 gtk.ICON_SIZE_MENU)
5220 newitem.set_image(img)
5221 newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
5222 gc_sub_menu.append(newitem)
5223 gc_sub_menu.show_all()
5224 if connected_accounts_with_private_storage == 0:
5225 newitem.set_sensitive(False)
5227 connected_accounts_with_vcard = []
5228 for account in gajim.connections:
5229 if gajim.account_is_connected(account) and \
5230 gajim.connections[account].vcard_supported:
5231 connected_accounts_with_vcard.append(account)
5232 if len(connected_accounts_with_vcard) > 1:
5233 # 2 or more accounts? make submenus
5234 profile_avatar_sub_menu = gtk.Menu()
5235 for account in connected_accounts_with_vcard:
5236 # profile, avatar
5237 profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
5238 False)
5239 profile_avatar_sub_menu.append(profile_avatar_item)
5240 profile_avatar_item.connect('activate',
5241 self.on_profile_avatar_menuitem_activate, account)
5242 profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
5243 profile_avatar_sub_menu.show_all()
5244 elif len(connected_accounts_with_vcard) == 1:
5245 # user has only one account
5246 account = connected_accounts_with_vcard[0]
5247 # profile, avatar
5248 if not self.profile_avatar_menuitem_handler_id:
5249 self.profile_avatar_menuitem_handler_id = \
5250 profile_avatar_menuitem.connect('activate',
5251 self.on_profile_avatar_menuitem_activate, account)
5253 if len(connected_accounts_with_vcard) == 0:
5254 profile_avatar_menuitem.set_sensitive(False)
5255 else:
5256 profile_avatar_menuitem.set_sensitive(True)
5258 # Advanced Actions
5259 if len(gajim.connections) == 0: # user has no accounts
5260 advanced_menuitem.set_sensitive(False)
5261 elif len(gajim.connections) == 1: # we have one acccount
5262 account = gajim.connections.keys()[0]
5263 advanced_menuitem_menu = \
5264 self.get_and_connect_advanced_menuitem_menu(account)
5265 self.advanced_menus.append(advanced_menuitem_menu)
5267 self.add_history_manager_menuitem(advanced_menuitem_menu)
5269 advanced_menuitem.set_submenu(advanced_menuitem_menu)
5270 advanced_menuitem_menu.show_all()
5271 else: # user has *more* than one account : build advanced submenus
5272 advanced_sub_menu = gtk.Menu()
5273 accounts = [] # Put accounts in a list to sort them
5274 for account in gajim.connections:
5275 accounts.append(account)
5276 accounts.sort()
5277 for account in accounts:
5278 advanced_item = gtk.MenuItem(_('for account %s') % account,
5279 False)
5280 advanced_sub_menu.append(advanced_item)
5281 advanced_menuitem_menu = \
5282 self.get_and_connect_advanced_menuitem_menu(account)
5283 self.advanced_menus.append(advanced_menuitem_menu)
5284 advanced_item.set_submenu(advanced_menuitem_menu)
5286 self.add_history_manager_menuitem(advanced_sub_menu)
5288 advanced_menuitem.set_submenu(advanced_sub_menu)
5289 advanced_sub_menu.show_all()
5291 self.actions_menu_needs_rebuild = False
5293 def build_account_menu(self, account):
5294 # we have to create our own set of icons for the menu
5295 # using self.jabber_status_images is poopoo
5296 iconset = gajim.config.get('iconset')
5297 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5298 state_images = gtkgui_helpers.load_iconset(path)
5300 if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
5301 xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui')
5302 account_context_menu = xml.get_object('account_context_menu')
5304 status_menuitem = xml.get_object('status_menuitem')
5305 start_chat_menuitem = xml.get_object('start_chat_menuitem')
5306 join_group_chat_menuitem = xml.get_object(
5307 'join_group_chat_menuitem')
5308 muc_icon = gtkgui_helpers.load_icon('muc_active')
5309 if muc_icon:
5310 join_group_chat_menuitem.set_image(muc_icon)
5311 open_gmail_inbox_menuitem = xml.get_object(
5312 'open_gmail_inbox_menuitem')
5313 add_contact_menuitem = xml.get_object('add_contact_menuitem')
5314 service_discovery_menuitem = xml.get_object(
5315 'service_discovery_menuitem')
5316 execute_command_menuitem = xml.get_object(
5317 'execute_command_menuitem')
5318 edit_account_menuitem = xml.get_object('edit_account_menuitem')
5319 sub_menu = gtk.Menu()
5320 status_menuitem.set_submenu(sub_menu)
5322 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
5323 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5324 item = gtk.ImageMenuItem(uf_show)
5325 icon = state_images[show]
5326 item.set_image(icon)
5327 sub_menu.append(item)
5328 con = gajim.connections[account]
5329 if show == 'invisible' and con.connected > 1 and \
5330 not con.privacy_rules_supported:
5331 item.set_sensitive(False)
5332 else:
5333 item.connect('activate', self.change_status, account, show)
5335 item = gtk.SeparatorMenuItem()
5336 sub_menu.append(item)
5338 item = gtk.ImageMenuItem(_('_Change Status Message'))
5339 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5340 sub_menu.append(item)
5341 item.connect('activate', self.on_change_status_message_activate,
5342 account)
5343 if gajim.connections[account].connected < 2:
5344 item.set_sensitive(False)
5346 item = gtk.SeparatorMenuItem()
5347 sub_menu.append(item)
5349 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5350 item = gtk.ImageMenuItem(uf_show)
5351 icon = state_images['offline']
5352 item.set_image(icon)
5353 sub_menu.append(item)
5354 item.connect('activate', self.change_status, account, 'offline')
5356 pep_menuitem = xml.get_object('pep_menuitem')
5357 if gajim.connections[account].pep_supported:
5358 pep_submenu = gtk.Menu()
5359 pep_menuitem.set_submenu(pep_submenu)
5360 def add_item(label, opt_name, func):
5361 item = gtk.CheckMenuItem(label)
5362 pep_submenu.append(item)
5363 if not dbus_support.supported:
5364 item.set_sensitive(False)
5365 else:
5366 activ = gajim.config.get_per('accounts', account,
5367 opt_name)
5368 item.set_active(activ)
5369 item.connect('toggled', func, account)
5371 add_item(_('Publish Tune'), 'publish_tune',
5372 self.on_publish_tune_toggled)
5373 add_item(_('Publish Location'), 'publish_location',
5374 self.on_publish_location_toggled)
5376 pep_config = gtk.ImageMenuItem(_('Configure Services...'))
5377 item = gtk.SeparatorMenuItem()
5378 pep_submenu.append(item)
5379 pep_config.set_sensitive(True)
5380 pep_submenu.append(pep_config)
5381 pep_config.connect('activate',
5382 self.on_pep_services_menuitem_activate, account)
5383 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5384 gtk.ICON_SIZE_MENU)
5385 pep_config.set_image(img)
5387 else:
5388 pep_menuitem.set_sensitive(False)
5390 if not gajim.connections[account].gmail_url:
5391 open_gmail_inbox_menuitem.set_no_show_all(True)
5392 open_gmail_inbox_menuitem.hide()
5393 else:
5394 open_gmail_inbox_menuitem.connect('activate',
5395 self.on_open_gmail_inbox, account)
5397 edit_account_menuitem.connect('activate', self.on_edit_account,
5398 account)
5399 add_contact_menuitem.connect('activate', self.on_add_new_contact,
5400 account)
5401 service_discovery_menuitem.connect('activate',
5402 self.on_service_disco_menuitem_activate, account)
5403 hostname = gajim.config.get_per('accounts', account, 'hostname')
5404 contact = gajim.contacts.create_contact(jid=hostname,
5405 account=account) # Fake contact
5406 execute_command_menuitem.connect('activate',
5407 self.on_execute_command, contact, account)
5409 start_chat_menuitem.connect('activate',
5410 self.on_new_chat_menuitem_activate, account)
5412 gc_sub_menu = gtk.Menu() # gc is always a submenu
5413 join_group_chat_menuitem.set_submenu(gc_sub_menu)
5414 self.add_bookmarks_list(gc_sub_menu, account)
5416 # make some items insensitive if account is offline
5417 if gajim.connections[account].connected < 2:
5418 for widget in (add_contact_menuitem, service_discovery_menuitem,
5419 join_group_chat_menuitem, execute_command_menuitem,
5420 pep_menuitem, start_chat_menuitem):
5421 widget.set_sensitive(False)
5422 else:
5423 xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui')
5424 account_context_menu = xml.get_object('zeroconf_context_menu')
5426 status_menuitem = xml.get_object('status_menuitem')
5427 zeroconf_properties_menuitem = xml.get_object(
5428 'zeroconf_properties_menuitem')
5429 sub_menu = gtk.Menu()
5430 status_menuitem.set_submenu(sub_menu)
5432 for show in ('online', 'away', 'dnd', 'invisible'):
5433 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5434 item = gtk.ImageMenuItem(uf_show)
5435 icon = state_images[show]
5436 item.set_image(icon)
5437 sub_menu.append(item)
5438 item.connect('activate', self.change_status, account, show)
5440 item = gtk.SeparatorMenuItem()
5441 sub_menu.append(item)
5443 item = gtk.ImageMenuItem(_('_Change Status Message'))
5444 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5445 sub_menu.append(item)
5446 item.connect('activate', self.on_change_status_message_activate,
5447 account)
5448 if gajim.connections[account].connected < 2:
5449 item.set_sensitive(False)
5451 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5452 item = gtk.ImageMenuItem(uf_show)
5453 icon = state_images['offline']
5454 item.set_image(icon)
5455 sub_menu.append(item)
5456 item.connect('activate', self.change_status, account, 'offline')
5458 zeroconf_properties_menuitem.connect('activate',
5459 self.on_zeroconf_properties, account)
5461 return account_context_menu
5463 def make_account_menu(self, event, titer):
5465 Make account's popup menu
5467 model = self.modelfilter
5468 account = model[titer][C_ACCOUNT].decode('utf-8')
5470 if account != 'all': # not in merged mode
5471 menu = self.build_account_menu(account)
5472 else:
5473 menu = gtk.Menu()
5474 iconset = gajim.config.get('iconset')
5475 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5476 accounts = [] # Put accounts in a list to sort them
5477 for account in gajim.connections:
5478 accounts.append(account)
5479 accounts.sort()
5480 for account in accounts:
5481 state_images = gtkgui_helpers.load_iconset(path)
5482 item = gtk.ImageMenuItem(account)
5483 show = gajim.SHOW_LIST[gajim.connections[account].connected]
5484 icon = state_images[show]
5485 item.set_image(icon)
5486 account_menu = self.build_account_menu(account)
5487 item.set_submenu(account_menu)
5488 menu.append(item)
5490 event_button = gtkgui_helpers.get_possible_button_event(event)
5492 menu.attach_to_widget(self.tree, None)
5493 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5494 menu.show_all()
5495 menu.popup(None, None, None, event_button, event.time)
5497 def make_group_menu(self, event, titer):
5499 Make group's popup menu
5501 model = self.modelfilter
5502 path = model.get_path(titer)
5503 group = model[titer][C_JID].decode('utf-8')
5504 account = model[titer][C_ACCOUNT].decode('utf-8')
5506 list_ = [] # list of (jid, account) tuples
5507 list_online = [] # list of (jid, account) tuples
5509 group = model[titer][C_JID]
5510 for jid in gajim.contacts.get_jid_list(account):
5511 contact = gajim.contacts.get_contact_with_highest_priority(account,
5512 jid)
5513 if group in contact.get_shown_groups():
5514 if contact.show not in ('offline', 'error'):
5515 list_online.append((contact, account))
5516 list_.append((contact, account))
5517 menu = gtk.Menu()
5519 # Make special context menu if group is Groupchats
5520 if group == _('Groupchats'):
5521 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All'))
5522 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
5523 gtk.ICON_SIZE_MENU)
5524 maximize_menuitem.set_image(icon)
5525 maximize_menuitem.connect('activate',
5526 self.on_all_groupchat_maximized, list_)
5527 menu.append(maximize_menuitem)
5528 else:
5529 # Send Group Message
5530 send_group_message_item = gtk.ImageMenuItem(
5531 _('Send Group M_essage'))
5532 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5533 send_group_message_item.set_image(icon)
5535 send_group_message_submenu = gtk.Menu()
5536 send_group_message_item.set_submenu(send_group_message_submenu)
5537 menu.append(send_group_message_item)
5539 group_message_to_all_item = gtk.MenuItem(_('To all users'))
5540 send_group_message_submenu.append(group_message_to_all_item)
5542 group_message_to_all_online_item = gtk.MenuItem(
5543 _('To all online users'))
5544 send_group_message_submenu.append(group_message_to_all_online_item)
5546 group_message_to_all_online_item.connect('activate',
5547 self.on_send_single_message_menuitem_activate, account,
5548 list_online)
5549 group_message_to_all_item.connect('activate',
5550 self.on_send_single_message_menuitem_activate, account, list_)
5552 # Invite to
5553 invite_menuitem = gtk.ImageMenuItem(_('In_vite to'))
5554 muc_icon = gtkgui_helpers.load_icon('muc_active')
5555 if muc_icon:
5556 invite_menuitem.set_image(muc_icon)
5558 gui_menu_builder.build_invite_submenu(invite_menuitem, list_online)
5559 menu.append(invite_menuitem)
5561 # Send Custom Status
5562 send_custom_status_menuitem = gtk.ImageMenuItem(
5563 _('Send Cus_tom Status'))
5564 # add a special img for this menuitem
5565 if helpers.group_is_blocked(account, group):
5566 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5567 'offline'))
5568 send_custom_status_menuitem.set_sensitive(False)
5569 else:
5570 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5571 gtk.ICON_SIZE_MENU)
5572 send_custom_status_menuitem.set_image(icon)
5573 status_menuitems = gtk.Menu()
5574 send_custom_status_menuitem.set_submenu(status_menuitems)
5575 iconset = gajim.config.get('iconset')
5576 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5577 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5578 # icon MUST be different instance for every item
5579 state_images = gtkgui_helpers.load_iconset(path)
5580 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5581 status_menuitem.connect('activate', self.on_send_custom_status,
5582 list_, s, group)
5583 icon = state_images[s]
5584 status_menuitem.set_image(icon)
5585 status_menuitems.append(status_menuitem)
5586 menu.append(send_custom_status_menuitem)
5588 # there is no singlemessage and custom status for zeroconf
5589 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
5590 send_custom_status_menuitem.set_sensitive(False)
5591 send_group_message_item.set_sensitive(False)
5593 if not group in helpers.special_groups:
5594 item = gtk.SeparatorMenuItem() # separator
5595 menu.append(item)
5597 # Rename
5598 rename_item = gtk.ImageMenuItem(_('Re_name'))
5599 # add a special img for rename menuitem
5600 gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input')
5601 menu.append(rename_item)
5602 rename_item.connect('activate', self.on_rename, 'group', group,
5603 account)
5605 # Block group
5606 is_blocked = False
5607 if self.regroup:
5608 for g_account in gajim.connections:
5609 if helpers.group_is_blocked(g_account, group):
5610 is_blocked = True
5611 else:
5612 if helpers.group_is_blocked(account, group):
5613 is_blocked = True
5615 if is_blocked and gajim.connections[account].\
5616 privacy_rules_supported:
5617 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5618 icon = gtk.image_new_from_stock(gtk.STOCK_STOP,
5619 gtk.ICON_SIZE_MENU)
5620 unblock_menuitem.set_image(icon)
5621 unblock_menuitem.connect('activate', self.on_unblock, list_,
5622 group)
5623 menu.append(unblock_menuitem)
5624 else:
5625 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5626 icon = gtk.image_new_from_stock(gtk.STOCK_STOP,
5627 gtk.ICON_SIZE_MENU)
5628 block_menuitem.set_image(icon)
5629 block_menuitem.connect('activate', self.on_block, list_, group)
5630 menu.append(block_menuitem)
5631 if not gajim.connections[account].privacy_rules_supported:
5632 block_menuitem.set_sensitive(False)
5634 # Remove group
5635 remove_item = gtk.ImageMenuItem(_('_Remove'))
5636 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE,
5637 gtk.ICON_SIZE_MENU)
5638 remove_item.set_image(icon)
5639 menu.append(remove_item)
5640 remove_item.connect('activate', self.on_remove_group_item_activated,
5641 group, account)
5643 # unsensitive if account is not connected
5644 if gajim.connections[account].connected < 2:
5645 rename_item.set_sensitive(False)
5647 # General group cannot be changed
5648 if group == _('General'):
5649 rename_item.set_sensitive(False)
5650 remove_item.set_sensitive(False)
5652 event_button = gtkgui_helpers.get_possible_button_event(event)
5654 menu.attach_to_widget(self.tree, None)
5655 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5656 menu.show_all()
5657 menu.popup(None, None, None, event_button, event.time)
5659 def make_contact_menu(self, event, titer):
5661 Make contact's popup menu
5663 model = self.modelfilter
5664 jid = model[titer][C_JID].decode('utf-8')
5665 account = model[titer][C_ACCOUNT].decode('utf-8')
5666 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5667 menu = gui_menu_builder.get_contact_menu(contact, account)
5668 event_button = gtkgui_helpers.get_possible_button_event(event)
5669 menu.attach_to_widget(self.tree, None)
5670 menu.popup(None, None, None, event_button, event.time)
5672 def make_multiple_contact_menu(self, event, iters):
5674 Make group's popup menu
5676 model = self.modelfilter
5677 list_ = [] # list of (jid, account) tuples
5678 one_account_offline = False
5679 is_blocked = True
5680 privacy_rules_supported = True
5681 for titer in iters:
5682 jid = model[titer][C_JID].decode('utf-8')
5683 account = model[titer][C_ACCOUNT].decode('utf-8')
5684 if gajim.connections[account].connected < 2:
5685 one_account_offline = True
5686 if not gajim.connections[account].privacy_rules_supported:
5687 privacy_rules_supported = False
5688 contact = gajim.contacts.get_contact_with_highest_priority(account,
5689 jid)
5690 if helpers.jid_is_blocked(account, jid):
5691 is_blocked = False
5692 list_.append((contact, account))
5694 menu = gtk.Menu()
5695 account = None
5696 for (contact, current_account) in list_:
5697 # check that we use the same account for every sender
5698 if account is not None and account != current_account:
5699 account = None
5700 break
5701 account = current_account
5702 if account is not None:
5703 send_group_message_item = gtk.ImageMenuItem(
5704 _('Send Group M_essage'))
5705 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5706 send_group_message_item.set_image(icon)
5707 menu.append(send_group_message_item)
5708 send_group_message_item.connect('activate',
5709 self.on_send_single_message_menuitem_activate, account, list_)
5711 # Invite to Groupchat
5712 invite_item = gtk.ImageMenuItem(_('In_vite to'))
5713 muc_icon = gtkgui_helpers.load_icon('muc_active')
5714 if muc_icon:
5715 invite_item.set_image(muc_icon)
5717 gui_menu_builder.build_invite_submenu(invite_item, list_)
5718 menu.append(invite_item)
5720 item = gtk.SeparatorMenuItem() # separator
5721 menu.append(item)
5723 # Manage Transport submenu
5724 item = gtk.ImageMenuItem(_('_Manage Contacts'))
5725 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES,
5726 gtk.ICON_SIZE_MENU)
5727 item.set_image(icon)
5728 manage_contacts_submenu = gtk.Menu()
5729 item.set_submenu(manage_contacts_submenu)
5730 menu.append(item)
5732 # Edit Groups
5733 edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups'))
5734 icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
5735 edit_groups_item.set_image(icon)
5736 manage_contacts_submenu.append(edit_groups_item)
5737 edit_groups_item.connect('activate', self.on_edit_groups, list_)
5739 item = gtk.SeparatorMenuItem() # separator
5740 manage_contacts_submenu.append(item)
5742 # Block
5743 if is_blocked and privacy_rules_supported:
5744 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5745 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5746 unblock_menuitem.set_image(icon)
5747 unblock_menuitem.connect('activate', self.on_unblock, list_)
5748 manage_contacts_submenu.append(unblock_menuitem)
5749 else:
5750 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5751 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5752 block_menuitem.set_image(icon)
5753 block_menuitem.connect('activate', self.on_block, list_)
5754 manage_contacts_submenu.append(block_menuitem)
5756 if not privacy_rules_supported:
5757 block_menuitem.set_sensitive(False)
5759 # Remove
5760 remove_item = gtk.ImageMenuItem(_('_Remove'))
5761 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5762 remove_item.set_image(icon)
5763 manage_contacts_submenu.append(remove_item)
5764 remove_item.connect('activate', self.on_req_usub, list_)
5765 # unsensitive remove if one account is not connected
5766 if one_account_offline:
5767 remove_item.set_sensitive(False)
5769 event_button = gtkgui_helpers.get_possible_button_event(event)
5771 menu.attach_to_widget(self.tree, None)
5772 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5773 menu.show_all()
5774 menu.popup(None, None, None, event_button, event.time)
5776 def make_transport_menu(self, event, titer):
5778 Make transport's popup menu
5780 model = self.modelfilter
5781 jid = model[titer][C_JID].decode('utf-8')
5782 path = model.get_path(titer)
5783 account = model[titer][C_ACCOUNT].decode('utf-8')
5784 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5785 menu = gtk.Menu()
5787 # Send single message
5788 item = gtk.ImageMenuItem(_('Send Single Message'))
5789 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5790 item.set_image(icon)
5791 item.connect('activate',
5792 self.on_send_single_message_menuitem_activate, account, contact)
5793 menu.append(item)
5795 blocked = False
5796 if helpers.jid_is_blocked(account, jid):
5797 blocked = True
5799 # Send Custom Status
5800 send_custom_status_menuitem = gtk.ImageMenuItem(
5801 _('Send Cus_tom Status'))
5802 # add a special img for this menuitem
5803 if blocked:
5804 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5805 'offline'))
5806 send_custom_status_menuitem.set_sensitive(False)
5807 else:
5808 if account in gajim.interface.status_sent_to_users and \
5809 jid in gajim.interface.status_sent_to_users[account]:
5810 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5811 gajim.interface.status_sent_to_users[account][jid]))
5812 else:
5813 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5814 gtk.ICON_SIZE_MENU)
5815 send_custom_status_menuitem.set_image(icon)
5816 status_menuitems = gtk.Menu()
5817 send_custom_status_menuitem.set_submenu(status_menuitems)
5818 iconset = gajim.config.get('iconset')
5819 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5820 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5821 # icon MUST be different instance for every item
5822 state_images = gtkgui_helpers.load_iconset(path)
5823 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5824 status_menuitem.connect('activate', self.on_send_custom_status,
5825 [(contact, account)], s)
5826 icon = state_images[s]
5827 status_menuitem.set_image(icon)
5828 status_menuitems.append(status_menuitem)
5829 menu.append(send_custom_status_menuitem)
5831 item = gtk.SeparatorMenuItem() # separator
5832 menu.append(item)
5834 # Execute Command
5835 item = gtk.ImageMenuItem(_('Execute Command...'))
5836 icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
5837 item.set_image(icon)
5838 menu.append(item)
5839 item.connect('activate', self.on_execute_command, contact, account,
5840 contact.resource)
5841 if gajim.account_is_disconnected(account):
5842 item.set_sensitive(False)
5844 # Manage Transport submenu
5845 item = gtk.ImageMenuItem(_('_Manage Transport'))
5846 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES,
5847 gtk.ICON_SIZE_MENU)
5848 item.set_image(icon)
5849 manage_transport_submenu = gtk.Menu()
5850 item.set_submenu(manage_transport_submenu)
5851 menu.append(item)
5853 # Modify Transport
5854 item = gtk.ImageMenuItem(_('_Modify Transport'))
5855 icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5856 gtk.ICON_SIZE_MENU)
5857 item.set_image(icon)
5858 manage_transport_submenu.append(item)
5859 item.connect('activate', self.on_edit_agent, contact, account)
5860 if gajim.account_is_disconnected(account):
5861 item.set_sensitive(False)
5863 # Rename
5864 item = gtk.ImageMenuItem(_('_Rename'))
5865 # add a special img for rename menuitem
5866 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5867 manage_transport_submenu.append(item)
5868 item.connect('activate', self.on_rename, 'agent', jid, account)
5869 if gajim.account_is_disconnected(account):
5870 item.set_sensitive(False)
5872 item = gtk.SeparatorMenuItem() # separator
5873 manage_transport_submenu.append(item)
5875 # Block
5876 if blocked:
5877 item = gtk.ImageMenuItem(_('_Unblock'))
5878 item.connect('activate', self.on_unblock, [(contact, account)])
5879 else:
5880 item = gtk.ImageMenuItem(_('_Block'))
5881 item.connect('activate', self.on_block, [(contact, account)])
5883 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5884 item.set_image(icon)
5885 manage_transport_submenu.append(item)
5886 if gajim.account_is_disconnected(account):
5887 item.set_sensitive(False)
5889 # Remove
5890 item = gtk.ImageMenuItem(_('_Remove'))
5891 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5892 item.set_image(icon)
5893 manage_transport_submenu.append(item)
5894 item.connect('activate', self.on_remove_agent, [(contact, account)])
5895 if gajim.account_is_disconnected(account):
5896 item.set_sensitive(False)
5898 item = gtk.SeparatorMenuItem() # separator
5899 menu.append(item)
5901 # Information
5902 information_menuitem = gtk.ImageMenuItem(_('_Information'))
5903 icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
5904 information_menuitem.set_image(icon)
5905 menu.append(information_menuitem)
5906 information_menuitem.connect('activate', self.on_info, contact, account)
5908 event_button = gtkgui_helpers.get_possible_button_event(event)
5910 menu.attach_to_widget(self.tree, None)
5911 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5912 menu.show_all()
5913 menu.popup(None, None, None, event_button, event.time)
5915 def make_groupchat_menu(self, event, titer):
5916 model = self.modelfilter
5918 jid = model[titer][C_JID].decode('utf-8')
5919 account = model[titer][C_ACCOUNT].decode('utf-8')
5920 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5921 menu = gtk.Menu()
5923 if jid in gajim.interface.minimized_controls[account]:
5924 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize'))
5925 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
5926 gtk.ICON_SIZE_MENU)
5927 maximize_menuitem.set_image(icon)
5928 maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
5929 jid, account)
5930 menu.append(maximize_menuitem)
5932 if not gajim.gc_connected[account].get(jid, False):
5933 connect_menuitem = gtk.ImageMenuItem(_('_Reconnect'))
5934 connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \
5935 gtk.ICON_SIZE_MENU)
5936 connect_menuitem.set_image(connect_icon)
5937 connect_menuitem.connect('activate', self.on_reconnect, jid,
5938 account)
5939 menu.append(connect_menuitem)
5940 disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect'))
5941 disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \
5942 gtk.ICON_SIZE_MENU)
5943 disconnect_menuitem.set_image(disconnect_icon)
5944 disconnect_menuitem.connect('activate', self.on_disconnect, jid,
5945 account)
5946 menu.append(disconnect_menuitem)
5948 item = gtk.SeparatorMenuItem() # separator
5949 menu.append(item)
5951 history_menuitem = gtk.ImageMenuItem(_('_History'))
5952 history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \
5953 gtk.ICON_SIZE_MENU)
5954 history_menuitem.set_image(history_icon)
5955 history_menuitem .connect('activate', self.on_history, contact, account)
5956 menu.append(history_menuitem)
5958 event_button = gtkgui_helpers.get_possible_button_event(event)
5960 menu.attach_to_widget(self.tree, None)
5961 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5962 menu.show_all()
5963 menu.popup(None, None, None, event_button, event.time)
5965 def get_and_connect_advanced_menuitem_menu(self, account):
5967 Add FOR ACCOUNT options
5969 xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui')
5970 advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
5972 xml_console_menuitem = xml.get_object('xml_console_menuitem')
5973 archiving_preferences_menuitem = xml.get_object(
5974 'archiving_preferences_menuitem')
5975 privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
5976 administrator_menuitem = xml.get_object('administrator_menuitem')
5977 send_server_message_menuitem = xml.get_object(
5978 'send_server_message_menuitem')
5979 set_motd_menuitem = xml.get_object('set_motd_menuitem')
5980 update_motd_menuitem = xml.get_object('update_motd_menuitem')
5981 delete_motd_menuitem = xml.get_object('delete_motd_menuitem')
5983 xml_console_menuitem.connect('activate',
5984 self.on_xml_console_menuitem_activate, account)
5986 if gajim.connections[account]:
5987 if gajim.connections[account].privacy_rules_supported:
5988 privacy_lists_menuitem.connect('activate',
5989 self.on_privacy_lists_menuitem_activate, account)
5990 else:
5991 privacy_lists_menuitem.set_sensitive(False)
5992 if gajim.connections[account].archive_pref_supported:
5993 archiving_preferences_menuitem.connect('activate',
5994 self.on_archiving_preferences_menuitem_activate, account)
5995 else:
5996 archiving_preferences_menuitem.set_sensitive(False)
5998 if gajim.connections[account].is_zeroconf:
5999 administrator_menuitem.set_sensitive(False)
6000 send_server_message_menuitem.set_sensitive(False)
6001 set_motd_menuitem.set_sensitive(False)
6002 update_motd_menuitem.set_sensitive(False)
6003 delete_motd_menuitem.set_sensitive(False)
6004 else:
6005 send_server_message_menuitem.connect('activate',
6006 self.on_send_server_message_menuitem_activate, account)
6008 set_motd_menuitem.connect('activate',
6009 self.on_set_motd_menuitem_activate, account)
6011 update_motd_menuitem.connect('activate',
6012 self.on_update_motd_menuitem_activate, account)
6014 delete_motd_menuitem.connect('activate',
6015 self.on_delete_motd_menuitem_activate, account)
6017 advanced_menuitem_menu.show_all()
6019 return advanced_menuitem_menu
6021 def add_history_manager_menuitem(self, menu):
6023 Add a seperator and History Manager menuitem BELOW for account menuitems
6025 item = gtk.SeparatorMenuItem() # separator
6026 menu.append(item)
6028 # History manager
6029 item = gtk.ImageMenuItem(_('History Manager'))
6030 icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
6031 gtk.ICON_SIZE_MENU)
6032 item.set_image(icon)
6033 menu.append(item)
6034 item.connect('activate', self.on_history_manager_menuitem_activate)
6036 def add_bookmarks_list(self, gc_sub_menu, account):
6038 Show join new group chat item and bookmarks list for an account
6040 item = gtk.ImageMenuItem(_('_Join New Group Chat'))
6041 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
6042 item.set_image(icon)
6043 item.connect('activate', self.on_join_gc_activate, account)
6045 gc_sub_menu.append(item)
6047 # User has at least one bookmark.
6048 if gajim.connections[account].bookmarks:
6049 item = gtk.SeparatorMenuItem()
6050 gc_sub_menu.append(item)
6052 for bookmark in gajim.connections[account].bookmarks:
6053 # Do not use underline.
6054 item = gtk.MenuItem(bookmark['name'], False)
6055 item.connect('activate', self.on_bookmark_menuitem_activate,
6056 account, bookmark)
6057 gc_sub_menu.append(item)
6059 def set_actions_menu_needs_rebuild(self):
6060 self.actions_menu_needs_rebuild = True
6061 # Just handle new_chat_menuitem to have ctrl+N working even if we don't
6062 # open the menu
6063 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
6064 ag = gtk.accel_groups_from_object(self.window)[0]
6066 if self.new_chat_menuitem_handler_id:
6067 new_chat_menuitem.handler_disconnect(
6068 self.new_chat_menuitem_handler_id)
6069 self.new_chat_menuitem_handler_id = None
6071 new_chat_menuitem.remove_submenu()
6073 connected_accounts = gajim.get_number_of_connected_accounts()
6074 if connected_accounts == 1 or (connected_accounts == 2 and \
6075 gajim.zeroconf_is_connected()):
6076 # only one 'real' (non-zeroconf) account is connected, don't need
6077 # submenus
6078 accounts_list = sorted(gajim.contacts.get_accounts())
6079 for account in accounts_list:
6080 if gajim.account_is_connected(account) and \
6081 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
6082 if not self.new_chat_menuitem_handler_id:
6083 self.new_chat_menuitem_handler_id = new_chat_menuitem.\
6084 connect('activate',
6085 self.on_new_chat_menuitem_activate, account)
6087 def show_appropriate_context_menu(self, event, iters):
6088 # iters must be all of the same type
6089 model = self.modelfilter
6090 type_ = model[iters[0]][C_TYPE]
6091 for titer in iters[1:]:
6092 if model[titer][C_TYPE] != type_:
6093 return
6094 if type_ == 'group' and len(iters) == 1:
6095 self.make_group_menu(event, iters[0])
6096 if type_ == 'groupchat' and len(iters) == 1:
6097 self.make_groupchat_menu(event, iters[0])
6098 elif type_ == 'agent' and len(iters) == 1:
6099 self.make_transport_menu(event, iters[0])
6100 elif type_ in ('contact', 'self_contact') and len(iters) == 1:
6101 self.make_contact_menu(event, iters[0])
6102 elif type_ == 'contact':
6103 self.make_multiple_contact_menu(event, iters)
6104 elif type_ == 'account' and len(iters) == 1:
6105 self.make_account_menu(event, iters[0])
6107 def show_treeview_menu(self, event):
6108 try:
6109 model, list_of_paths = self.tree.get_selection().get_selected_rows()
6110 except TypeError:
6111 self.tree.get_selection().unselect_all()
6112 return
6113 if not len(list_of_paths):
6114 # no row is selected
6115 return
6116 if len(list_of_paths) > 1:
6117 iters = []
6118 for path in list_of_paths:
6119 iters.append(model.get_iter(path))
6120 else:
6121 path = list_of_paths[0]
6122 iters = [model.get_iter(path)]
6123 self.show_appropriate_context_menu(event, iters)
6125 return True
6127 def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier):
6129 Bring up the conference join dialog, when CTRL+J accelerator is being
6130 activated
6132 # find a connected account:
6133 for account in gajim.connections:
6134 if gajim.account_is_connected(account):
6135 break
6136 self.on_join_gc_activate(None, account)
6137 return True
6139 def fill_column(self, col):
6140 for rend in self.renderers_list:
6141 col.pack_start(rend[1], expand=rend[2])
6142 col.add_attribute(rend[1], rend[3], rend[4])
6143 col.set_cell_data_func(rend[1], rend[5], rend[6])
6144 # set renderers propertys
6145 for renderer in self.renderers_propertys.keys():
6146 renderer.set_property(self.renderers_propertys[renderer][0],
6147 self.renderers_propertys[renderer][1])
6149 ################################################################################
6151 ################################################################################
6153 def __init__(self):
6154 self.filtering = False
6155 # Number of renderers plugins added
6156 self.nb_ext_renderers = 0
6157 # [icon, name, type, jid, account, editable, mood_pixbuf,
6158 # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
6159 # padlock_pixbuf]
6160 self.columns = [gtk.Image, str, str, str, str,
6161 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
6162 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf]
6163 self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
6164 self.window = self.xml.get_object('roster_window')
6165 self.hpaned = self.xml.get_object('roster_hpaned')
6166 gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
6167 gajim.interface.msg_win_mgr.connect('window-delete',
6168 self.on_message_window_delete)
6169 self.advanced_menus = [] # We keep them to destroy them
6170 if gajim.config.get('roster_window_skip_taskbar'):
6171 self.window.set_property('skip-taskbar-hint', True)
6172 self.tree = self.xml.get_object('roster_treeview')
6173 sel = self.tree.get_selection()
6174 sel.set_mode(gtk.SELECTION_MULTIPLE)
6175 # sel.connect('changed',
6176 # self.on_treeview_selection_changed)
6178 self._iters = {}
6179 # for merged mode
6180 self._iters['MERGED'] = {'account': None, 'groups': {}}
6181 # holds a list of (jid, account) tupples
6182 self._last_selected_contact = []
6183 self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
6184 'closed': {}}
6186 self.last_save_dir = None
6187 self.editing_path = None # path of row with cell in edit mode
6188 self.add_new_contact_handler_id = False
6189 self.service_disco_handler_id = False
6190 self.new_chat_menuitem_handler_id = False
6191 self.single_message_menuitem_handler_id = False
6192 self.profile_avatar_menuitem_handler_id = False
6193 #FIXME: When list_accel_closures will be wrapped in pygtk
6194 # no need of this variable
6195 self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
6196 self.set_actions_menu_needs_rebuild()
6197 self.regroup = gajim.config.get('mergeaccounts')
6198 self.clicked_path = None # Used remember on wich row we clicked
6199 if len(gajim.connections) < 2:
6200 # Do not merge accounts if only one exists
6201 self.regroup = False
6202 gtkgui_helpers.resize_window(self.window,
6203 gajim.config.get('roster_width'),
6204 gajim.config.get('roster_height'))
6205 if gajim.config.get('save-roster-position'):
6206 gtkgui_helpers.move_window(self.window,
6207 gajim.config.get('roster_x-position'),
6208 gajim.config.get('roster_y-position'))
6210 self.popups_notification_height = 0
6211 self.popup_notification_windows = []
6213 # Remove contact from roster when last event opened
6214 # { (contact, account): { backend: boolean }
6215 self.contacts_to_be_removed = {}
6216 gajim.events.event_removed_subscribe(self.on_event_removed)
6218 # when this value become 0 we quit main application. If it's more than 0
6219 # it means we are waiting for this number of accounts to disconnect
6220 # before quitting
6221 self.quit_on_next_offline = -1
6223 # uf_show, img, show, sensitive
6224 liststore = gtk.ListStore(str, gtk.Image, str, bool)
6225 self.status_combobox = self.xml.get_object('status_combobox')
6227 cell = cell_renderer_image.CellRendererImage(0, 1)
6228 self.status_combobox.pack_start(cell, False)
6230 # img to show is in in 2nd column of liststore
6231 self.status_combobox.add_attribute(cell, 'image', 1)
6232 # if it will be sensitive or not it is in the fourth column
6233 # all items in the 'row' must have sensitive to False
6234 # if we want False (so we add it for img_cell too)
6235 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6237 cell = gtk.CellRendererText()
6238 cell.set_property('xpad', 5) # padding for status text
6239 self.status_combobox.pack_start(cell, True)
6240 # text to show is in in first column of liststore
6241 self.status_combobox.add_attribute(cell, 'text', 0)
6242 # if it will be sensitive or not it is in the fourth column
6243 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6245 self.status_combobox.set_row_separator_func(self._iter_is_separator)
6247 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
6248 uf_show = helpers.get_uf_show(show)
6249 liststore.append([uf_show,
6250 gajim.interface.jabber_state_images['16'][show], show, True])
6251 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6252 liststore.append(['SEPARATOR', None, '', True])
6254 path = gtkgui_helpers.get_icon_path('gajim-kbd_input')
6255 img = gtk.Image()
6256 img.set_from_file(path)
6257 # sensitivity to False because by default we're offline
6258 self.status_message_menuitem_iter = liststore.append(
6259 [_('Change Status Message...'), img, '', False])
6260 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6261 liststore.append(['SEPARATOR', None, '', True])
6263 uf_show = helpers.get_uf_show('offline')
6264 liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
6265 'offline'], 'offline', True])
6267 status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
6268 'invisible', 'separator1', 'change_status_msg', 'separator2',
6269 'offline']
6270 self.status_combobox.set_model(liststore)
6272 # default to offline
6273 number_of_menuitem = status_combobox_items.index('offline')
6274 self.status_combobox.set_active(number_of_menuitem)
6276 # holds index to previously selected item so if
6277 # "change status message..." is selected we can fallback to previously
6278 # selected item and not stay with that item selected
6279 self.previous_status_combobox_active = number_of_menuitem
6281 showOffline = gajim.config.get('showoffline')
6282 showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online')
6284 w = self.xml.get_object('show_offline_contacts_menuitem')
6285 w.set_active(showOffline)
6286 if showOnlyChatAndOnline:
6287 w.set_sensitive(False)
6289 w = self.xml.get_object('show_only_active_contacts_menuitem')
6290 w.set_active(showOnlyChatAndOnline)
6291 if showOffline:
6292 w.set_sensitive(False)
6294 show_transports_group = gajim.config.get('show_transports_group')
6295 self.xml.get_object('show_transports_menuitem').set_active(
6296 show_transports_group)
6298 self.xml.get_object('show_roster_menuitem').set_active(True)
6300 # columns
6301 col = gtk.TreeViewColumn()
6302 # list of renderers with attributes / properties in the form:
6303 # (name, renderer_object, expand?, attribute_name, attribute_value,
6304 # cell_data_func, func_arg)
6305 self.renderers_list = []
6306 self.renderers_propertys ={}
6307 self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF,
6308 'activity': C_ACTIVITY_PIXBUF, 'tune': C_TUNE_PIXBUF,
6309 'location': C_LOCATION_PIXBUF}
6311 renderer_text = gtk.CellRendererText()
6312 self.renderers_propertys[renderer_text] = ('ellipsize',
6313 pango.ELLIPSIZE_END)
6315 def add_avatar_renderer():
6316 self.renderers_list.append(('avatar', gtk.CellRendererPixbuf(),
6317 False, 'pixbuf', C_AVATAR_PIXBUF,
6318 self._fill_avatar_pixbuf_renderer, None))
6320 if gajim.config.get('avatar_position_in_roster') == 'left':
6321 add_avatar_renderer()
6323 self.renderers_list += (
6324 ('icon', cell_renderer_image.CellRendererImage(0, 0), False,
6325 'image', C_IMG, self._iconCellDataFunc, None),
6327 ('name', renderer_text, True,
6328 'markup', C_NAME, self._nameCellDataFunc, None),
6330 ('mood', gtk.CellRendererPixbuf(), False,
6331 'pixbuf', C_MOOD_PIXBUF,
6332 self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF),
6334 ('activity', gtk.CellRendererPixbuf(), False,
6335 'pixbuf', C_ACTIVITY_PIXBUF,
6336 self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF),
6338 ('tune', gtk.CellRendererPixbuf(), False,
6339 'pixbuf', C_TUNE_PIXBUF,
6340 self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF),
6342 ('location', gtk.CellRendererPixbuf(), False,
6343 'pixbuf', C_LOCATION_PIXBUF,
6344 self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF))
6346 if gajim.config.get('avatar_position_in_roster') == 'right':
6347 add_avatar_renderer()
6349 self.renderers_list.append(('padlock', gtk.CellRendererPixbuf(), False,
6350 'pixbuf', C_PADLOCK_PIXBUF,
6351 self._fill_padlock_pixbuf_renderer, None))
6353 # fill and append column
6354 self.fill_column(col)
6355 self.tree.append_column(col)
6357 # do not show gtk arrows workaround
6358 col = gtk.TreeViewColumn()
6359 render_pixbuf = gtk.CellRendererPixbuf()
6360 col.pack_start(render_pixbuf, expand=False)
6361 self.tree.append_column(col)
6362 col.set_visible(False)
6363 self.tree.set_expander_column(col)
6365 # signals
6366 self.TARGET_TYPE_URI_LIST = 80
6367 TARGETS = [('MY_TREE_MODEL_ROW',
6368 gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)]
6369 TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
6370 ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)]
6371 self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS,
6372 gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY)
6373 self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT)
6374 self.tree.connect('drag_begin', self.drag_begin)
6375 self.tree.connect('drag_end', self.drag_end)
6376 self.tree.connect('drag_drop', self.drag_drop)
6377 self.tree.connect('drag_data_get', self.drag_data_get_data)
6378 self.tree.connect('drag_data_received', self.drag_data_received_data)
6379 self.dragging = False
6380 self.xml.connect_signals(self)
6381 self.combobox_callback_active = True
6383 self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
6384 self.tooltip = tooltips.RosterTooltip()
6385 # Workaroung: For strange reasons signal is behaving like row-changed
6386 self._toggeling_row = False
6387 self.setup_and_draw_roster()
6389 if gajim.config.get('show_roster_on_startup') == 'always':
6390 self.window.show_all()
6391 elif gajim.config.get('show_roster_on_startup') == 'never':
6392 if gajim.config.get('trayicon') != 'always':
6393 # Without trayicon, user should see the roster!
6394 self.window.show_all()
6395 gajim.config.set('last_roster_visible', True)
6396 else:
6397 if gajim.config.get('last_roster_visible') or \
6398 gajim.config.get('trayicon') != 'always':
6399 self.window.show_all()
6401 if not gajim.config.get_per('accounts') or \
6402 gajim.config.get_per('accounts') == ['Local'] and not \
6403 gajim.config.get_per('accounts', 'Local', 'active'):
6404 # if we have no account configured or only Local account but not enabled
6405 def _open_wizard():
6406 gajim.interface.instances['account_creation_wizard'] = \
6407 config.AccountCreationWizardWindow()
6408 # Open wizard only after roster is created, so we can make it
6409 # transient for the roster window
6410 gobject.idle_add(_open_wizard)
6411 if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
6412 # Create zeroconf in config file
6413 from common.zeroconf import connection_zeroconf
6414 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
6416 # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
6417 # conference.
6418 accel_group = gtk.accel_groups_from_object(self.window)[0]
6419 accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK,
6420 gtk.ACCEL_MASK, self.on_ctrl_j)
6422 # Setting CTRL+N to be the shortcut for show Start chat dialog
6423 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
6424 new_chat_menuitem.add_accelerator('activate', accel_group,
6425 gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
6427 # Setting the search stuff
6428 self.rfilter_entry = self.xml.get_object('rfilter_entry')
6429 self.rfilter_string = ''
6430 self.rfilter_enabled = False
6432 gajim.ged.register_event_handler('presence-received', ged.GUI1,
6433 self._nec_presence_received)
6434 # presence has to be fully handled so that contact is added to occupant
6435 # list before roster can be correctly updated
6436 gajim.ged.register_event_handler('gc-presence-received', ged.GUI2,
6437 self._nec_gc_presence_received)
6438 gajim.ged.register_event_handler('roster-received', ged.GUI1,
6439 self._nec_roster_received)
6440 gajim.ged.register_event_handler('anonymous-auth', ged.GUI1,
6441 self._nec_anonymous_auth)
6442 gajim.ged.register_event_handler('our-show', ged.GUI1,
6443 self._nec_our_show)
6444 gajim.ged.register_event_handler('connection-type', ged.GUI1,
6445 self._nec_connection_type)
6446 gajim.ged.register_event_handler('agent-removed', ged.GUI1,
6447 self._nec_agent_removed)
6448 gajim.ged.register_event_handler('pep-received', ged.GUI1,
6449 self._nec_pep_received)
6450 gajim.ged.register_event_handler('vcard-received', ged.GUI1,
6451 self._nec_vcard_received)
6452 gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
6453 self._nec_gc_subject_received)
6454 gajim.ged.register_event_handler('metacontacts-received', ged.GUI2,
6455 self._nec_metacontacts_received)
6456 gajim.ged.register_event_handler('signed-in', ged.GUI1,
6457 self._nec_signed_in)
6458 gajim.ged.register_event_handler('decrypted-message-received', ged.GUI2,
6459 self._nec_decrypted_message_received)