don't open twice the same subscription request dialog. see #6762
[gajim.git] / src / roster_window.py
blobd2cca86900c274f22969b313aacd0f59fb6d15df
1 # -*- coding: utf-8 -*-
2 ## src/roster_window.py
3 ##
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
6 ## Stéphan Kochen <stephan AT kochen.nl>
7 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
8 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
9 ## Nikos Kouremenos <kourem AT gmail.com>
10 ## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
11 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
12 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
13 ## James Newton <redshodan AT gmail.com>
14 ## Tomasz Melcer <liori AT exroot.org>
15 ## Julien Pivotto <roidelapluie AT gmail.com>
16 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
17 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
18 ## Jonathan Schleifer <js-gajim AT webkeks.org>
20 ## This file is part of Gajim.
22 ## Gajim is free software; you can redistribute it and/or modify
23 ## it under the terms of the GNU General Public License as published
24 ## by the Free Software Foundation; version 3 only.
26 ## Gajim is distributed in the hope that it will be useful,
27 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
28 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 ## GNU General Public License for more details.
31 ## You should have received a copy of the GNU General Public License
32 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
35 import gtk
36 import pango
37 import gobject
38 import os
39 import sys
40 import time
41 import locale
43 import common.sleepy
44 import history_window
45 import dialogs
46 import vcard
47 import config
48 import disco
49 import gtkgui_helpers
50 import gui_menu_builder
51 import cell_renderer_image
52 import tooltips
53 import message_control
54 import adhoc_commands
55 import features_window
56 import plugins
57 import plugins.gui
59 from common import gajim
60 from common import helpers
61 from common.exceptions import GajimGeneralException
62 from common import i18n
63 from common import pep
64 from common import location_listener
65 from common import ged
67 from message_window import MessageWindowMgr
69 from common import dbus_support
70 if dbus_support.supported:
71 import dbus
73 from common.xmpp.protocol import NS_FILE
74 from common.pep import MOODS, ACTIVITIES
76 #(icon, name, type, jid, account, editable, second pixbuf)
78 C_IMG, # image to show state (online, new message etc)
79 C_NAME, # cellrenderer text that holds contact nickame
80 C_TYPE, # account, group or contact?
81 C_JID, # the jid of the row
82 C_ACCOUNT, # cellrenderer text that holds account name
83 C_MOOD_PIXBUF,
84 C_ACTIVITY_PIXBUF,
85 C_TUNE_PIXBUF,
86 C_LOCATION_PIXBUF,
87 C_AVATAR_PIXBUF, # avatar_pixbuf
88 C_PADLOCK_PIXBUF, # use for account row only
89 ) = range(11)
91 class RosterWindow:
92 """
93 Class for main window of the GTK+ interface
94 """
96 def _get_account_iter(self, name, model=None):
97 """
98 Return the gtk.TreeIter of the given account or None if not found
100 Keyword arguments:
101 name -- the account name
102 model -- the data model (default TreeFilterModel)
104 if model is None:
105 model = self.modelfilter
106 if model is None:
107 return
109 if self.regroup:
110 name = 'MERGED'
111 it = self._iters[name]['account']
113 if model == self.model or it is None:
114 return it
115 try:
116 return self.modelfilter.convert_child_iter_to_iter(it)
117 except RuntimeError:
118 return None
121 def _get_group_iter(self, name, account, model=None):
123 Return the gtk.TreeIter of the given group or None if not found
125 Keyword arguments:
126 name -- the group name
127 account -- the account name
128 model -- the data model (default TreeFilterModel)
130 if model is None:
131 model = self.modelfilter
132 if model is None:
133 return
135 if self.regroup:
136 account = 'MERGED'
138 if name not in self._iters[account]['groups']:
139 return None
141 it = self._iters[account]['groups'][name]
142 if model == self.model or it is None:
143 return it
144 try:
145 return self.modelfilter.convert_child_iter_to_iter(it)
146 except RuntimeError:
147 return None
150 def _get_self_contact_iter(self, account, model=None):
152 Return the gtk.TreeIter of SelfContact or None if not found
154 Keyword arguments:
155 account -- the account of SelfContact
156 model -- the data model (default TreeFilterModel)
158 jid = gajim.get_jid_from_account(account)
159 its = self._get_contact_iter(jid, account, model=model)
160 if its:
161 return its[0]
162 return None
165 def _get_contact_iter(self, jid, account, contact=None, model=None):
167 Return a list of gtk.TreeIter of the given contact
169 Keyword arguments:
170 jid -- the jid without resource
171 account -- the account
172 contact -- the contact (default None)
173 model -- the data model (default TreeFilterModel)
175 if model is None:
176 model = self.modelfilter
177 # when closing Gajim model can be none (async pbs?)
178 if model is None:
179 return []
181 if not contact:
182 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
183 if not contact:
184 # We don't know this contact
185 return []
187 if account not in self._iters:
188 return []
190 if jid not in self._iters[account]['contacts']:
191 return []
193 its = self._iters[account]['contacts'][jid]
195 if not its:
196 return []
198 if model == self.model:
199 return its
201 its2 = []
202 for it in its:
203 try:
204 its2.append(self.modelfilter.convert_child_iter_to_iter(it))
205 except RuntimeError:
206 pass
207 return its2
210 def _iter_is_separator(self, model, titer):
212 Return True if the given iter is a separator
214 Keyword arguments:
215 model -- the data model
216 iter -- the gtk.TreeIter to test
218 if model[titer][0] == 'SEPARATOR':
219 return True
220 return False
223 #############################################################################
224 ### Methods for adding and removing roster window items
225 #############################################################################
227 def add_account(self, account):
229 Add account to roster and draw it. Do nothing if it is already in
231 if self._get_account_iter(account):
232 # Will happen on reconnect or for merged accounts
233 return
235 if self.regroup:
236 # Merged accounts view
237 show = helpers.get_global_show()
238 it = self.model.append(None, [
239 gajim.interface.jabber_state_images['16'][show],
240 _('Merged accounts'), 'account', '', 'all', None, None, None,
241 None, None, None] + [None] * self.nb_ext_renderers)
242 self._iters['MERGED']['account'] = it
243 else:
244 show = gajim.SHOW_LIST[gajim.connections[account].connected]
245 our_jid = gajim.get_jid_from_account(account)
247 tls_pixbuf = None
248 if gajim.account_is_securely_connected(account):
249 # the only way to create a pixbuf from stock
250 tls_pixbuf = self.window.render_icon(
251 gtk.STOCK_DIALOG_AUTHENTICATION,
252 gtk.ICON_SIZE_MENU)
254 it = self.model.append(None, [
255 gajim.interface.jabber_state_images['16'][show],
256 gobject.markup_escape_text(account), 'account', our_jid,
257 account, None, None, None, None, None, tls_pixbuf] +
258 [None] * self.nb_ext_renderers)
259 self._iters[account]['account'] = it
261 self.draw_account(account)
264 def add_account_contacts(self, account):
266 Add all contacts and groups of the given account to roster, draw them
267 and account
269 self.starting = True
270 jids = gajim.contacts.get_jid_list(account)
272 for jid in jids:
273 self.add_contact(jid, account)
275 # Do not freeze the GUI when drawing the contacts
276 if jids:
277 # Overhead is big, only invoke when needed
278 self._idle_draw_jids_of_account(jids, account)
280 # Draw all known groups
281 for group in gajim.groups[account]:
282 self.draw_group(group, account)
283 self.draw_account(account)
285 self.starting = False
287 def _add_group_iter(self, account, group):
289 Add a group iter in roster and return the newly created iter
291 if self.regroup:
292 account_group = 'MERGED'
293 else:
294 account_group = account
295 delimiter = gajim.connections[account].nested_group_delimiter
296 group_splited = group.split(delimiter)
297 parent_group = delimiter.join(group_splited[:-1])
298 if parent_group in self._iters[account_group]['groups']:
299 iter_parent = self._iters[account_group]['groups'][parent_group]
300 elif parent_group:
301 iter_parent = self._add_group_iter(account, parent_group)
302 if parent_group not in gajim.groups[account]:
303 if account + parent_group in self.collapsed_rows:
304 is_expanded = False
305 else:
306 is_expanded = True
307 gajim.groups[account][parent_group] = {'expand': is_expanded}
308 else:
309 iter_parent = self._get_account_iter(account, self.model)
310 iter_group = self.model.append(iter_parent,
311 [gajim.interface.jabber_state_images['16']['closed'],
312 gobject.markup_escape_text(group), 'group', group, account, None,
313 None, None, None, None, None] + [None] * self.nb_ext_renderers)
314 self.draw_group(group, account)
315 self._iters[account_group]['groups'][group] = iter_group
316 return iter_group
318 def _add_entity(self, contact, account, groups=None,
319 big_brother_contact=None, big_brother_account=None):
321 Add the given contact to roster data model
323 Contact is added regardless if he is already in roster or not. Return
324 list of newly added iters.
326 Keyword arguments:
327 contact -- the contact to add
328 account -- the contacts account
329 groups -- list of groups to add the contact to.
330 (default groups in contact.get_shown_groups()).
331 Parameter ignored when big_brother_contact is specified.
332 big_brother_contact -- if specified contact is added as child
333 big_brother_contact. (default None)
335 added_iters = []
336 if big_brother_contact:
337 # Add contact under big brother
339 parent_iters = self._get_contact_iter(
340 big_brother_contact.jid, big_brother_account,
341 big_brother_contact, self.model)
342 assert len(parent_iters) > 0, 'Big brother is not yet in roster!'
344 # Do not confuse get_contact_iter: Sync groups of family members
345 contact.groups = big_brother_contact.get_shown_groups()[:]
347 for child_iter in parent_iters:
348 it = self.model.append(child_iter, [None,
349 contact.get_shown_name(), 'contact', contact.jid, account,
350 None, None, None, None, None, None] + \
351 [None] * self.nb_ext_renderers)
352 added_iters.append(it)
353 if contact.jid in self._iters[account]['contacts']:
354 self._iters[account]['contacts'][contact.jid].append(it)
355 else:
356 self._iters[account]['contacts'][contact.jid] = [it]
357 else:
358 # We are a normal contact. Add us to our groups.
359 if not groups:
360 groups = contact.get_shown_groups()
361 for group in groups:
362 child_iterG = self._get_group_iter(group, account,
363 model=self.model)
364 if not child_iterG:
365 # Group is not yet in roster, add it!
366 child_iterG = self._add_group_iter(account, group)
368 if contact.is_transport():
369 typestr = 'agent'
370 elif contact.is_groupchat():
371 typestr = 'groupchat'
372 else:
373 typestr = 'contact'
375 # we add some values here. see draw_contact
376 # for more
377 i_ = self.model.append(child_iterG, [None,
378 contact.get_shown_name(), typestr, contact.jid, account,
379 None, None, None, None, None, None] + \
380 [None] * self.nb_ext_renderers)
381 added_iters.append(i_)
382 if contact.jid in self._iters[account]['contacts']:
383 self._iters[account]['contacts'][contact.jid].append(i_)
384 else:
385 self._iters[account]['contacts'][contact.jid] = [i_]
387 # Restore the group expand state
388 if account + group in self.collapsed_rows:
389 is_expanded = False
390 else:
391 is_expanded = True
392 if group not in gajim.groups[account]:
393 gajim.groups[account][group] = {'expand': is_expanded}
395 assert len(added_iters), '%s has not been added to roster!' % \
396 contact.jid
397 return added_iters
399 def _remove_entity(self, contact, account, groups=None):
401 Remove the given contact from roster data model
403 Empty groups after contact removal are removed too.
404 Return False if contact still has children and deletion was
405 not performed.
406 Return True on success.
408 Keyword arguments:
409 contact -- the contact to add
410 account -- the contacts account
411 groups -- list of groups to remove the contact from.
413 iters = self._get_contact_iter(contact.jid, account, contact,
414 self.model)
415 assert iters, '%s shall be removed but is not in roster' % contact.jid
417 parent_iter = self.model.iter_parent(iters[0])
418 parent_type = self.model[parent_iter][C_TYPE]
420 if groups:
421 # Only remove from specified groups
422 all_iters = iters[:]
423 group_iters = [self._get_group_iter(group, account)
424 for group in groups]
425 iters = [titer for titer in all_iters
426 if self.model.iter_parent(titer) in group_iters]
428 iter_children = self.model.iter_children(iters[0])
430 if iter_children:
431 # We have children. We cannot be removed!
432 return False
433 # Remove us and empty groups from the model
434 for i in iters:
435 assert self.model[i][C_JID] == contact.jid and \
436 self.model[i][C_ACCOUNT] == account, \
437 "Invalidated iters of %s" % contact.jid
439 parent_i = self.model.iter_parent(i)
440 parent_type = self.model[parent_i][C_TYPE]
442 to_be_removed = i
443 while parent_type == 'group' and \
444 self.model.iter_n_children(parent_i) == 1:
445 if self.regroup:
446 account_group = 'MERGED'
447 else:
448 account_group = account
449 group = self.model[parent_i][C_JID].decode('utf-8')
450 if group in gajim.groups[account]:
451 del gajim.groups[account][group]
452 to_be_removed = parent_i
453 del self._iters[account_group]['groups'][group]
454 parent_i = self.model.iter_parent(parent_i)
455 parent_type = self.model[parent_i][C_TYPE]
456 self.model.remove(to_be_removed)
458 del self._iters[account]['contacts'][contact.jid]
459 return True
461 def _add_metacontact_family(self, family, account):
463 Add the give Metacontact family to roster data model
465 Add Big Brother to his groups and all others under him.
466 Return list of all added (contact, account) tuples with
467 Big Brother as first element.
469 Keyword arguments:
470 family -- the family, see Contacts.get_metacontacts_family()
473 nearby_family, big_brother_jid, big_brother_account = \
474 self._get_nearby_family_and_big_brother(family, account)
475 big_brother_contact = gajim.contacts.get_first_contact_from_jid(
476 big_brother_account, big_brother_jid)
478 assert len(self._get_contact_iter(big_brother_jid,
479 big_brother_account, big_brother_contact, self.model)) == 0, \
480 'Big brother %s already in roster\n Family: %s' \
481 % (big_brother_jid, family)
482 self._add_entity(big_brother_contact, big_brother_account)
484 brothers = []
485 # Filter family members
486 for data in nearby_family:
487 _account = data['account']
488 _jid = data['jid']
489 _contact = gajim.contacts.get_first_contact_from_jid(
490 _account, _jid)
492 if not _contact or _contact == big_brother_contact:
493 # Corresponding account is not connected
494 # or brother already added
495 continue
497 assert len(self._get_contact_iter(_jid, _account,
498 _contact, self.model)) == 0, \
499 "%s already in roster.\n Family: %s" % (_jid, nearby_family)
500 self._add_entity(_contact, _account,
501 big_brother_contact = big_brother_contact,
502 big_brother_account = big_brother_account)
503 brothers.append((_contact, _account))
505 brothers.insert(0, (big_brother_contact, big_brother_account))
506 return brothers
508 def _remove_metacontact_family(self, family, account):
510 Remove the given Metacontact family from roster data model
512 See Contacts.get_metacontacts_family() and
513 RosterWindow._remove_entity()
515 nearby_family = self._get_nearby_family_and_big_brother(
516 family, account)[0]
518 # Family might has changed (actual big brother not on top).
519 # Remove childs first then big brother
520 family_in_roster = False
521 for data in nearby_family:
522 _account = data['account']
523 _jid = data['jid']
524 _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
526 iters = self._get_contact_iter(_jid, _account, _contact, self.model)
527 if not iters or not _contact:
528 # Family might not be up to date.
529 # Only try to remove what is actually in the roster
530 continue
531 assert iters, '%s shall be removed but is not in roster \
532 \n Family: %s' % (_jid, family)
534 family_in_roster = True
536 parent_iter = self.model.iter_parent(iters[0])
537 parent_type = self.model[parent_iter][C_TYPE]
539 if parent_type != 'contact':
540 # The contact on top
541 old_big_account = _account
542 old_big_contact = _contact
543 old_big_jid = _jid
544 continue
546 ok = self._remove_entity(_contact, _account)
547 assert ok, '%s was not removed' % _jid
548 assert len(self._get_contact_iter(_jid, _account, _contact,
549 self.model)) == 0, '%s is removed but still in roster' % _jid
551 if not family_in_roster:
552 return False
554 assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \
555 (nearby_family, family)
556 iters = self._get_contact_iter(old_big_jid, old_big_account,
557 old_big_contact, self.model)
558 assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
559 old_big_jid
560 assert not self.model.iter_children(iters[0]), \
561 'Old Big Brother %s still has children' % old_big_jid
563 ok = self._remove_entity(old_big_contact, old_big_account)
564 assert ok, "Old Big Brother %s not removed" % old_big_jid
565 assert len(self._get_contact_iter(old_big_jid, old_big_account,
566 old_big_contact, self.model)) == 0, \
567 'Old Big Brother %s is removed but still in roster' % old_big_jid
569 return True
571 def _recalibrate_metacontact_family(self, family, account):
573 Regroup metacontact family if necessary
576 brothers = []
577 nearby_family, big_brother_jid, big_brother_account = \
578 self._get_nearby_family_and_big_brother(family, account)
579 big_brother_contact = gajim.contacts.get_contact(big_brother_account,
580 big_brother_jid)
581 child_iters = self._get_contact_iter(big_brother_jid,
582 big_brother_account, model=self.model)
583 if child_iters:
584 parent_iter = self.model.iter_parent(child_iters[0])
585 parent_type = self.model[parent_iter][C_TYPE]
587 # Check if the current BigBrother has even been before.
588 if parent_type == 'contact':
589 for data in nearby_family:
590 # recalibrate after remove to keep highlight
591 if data['jid'] in gajim.to_be_removed[data['account']]:
592 return
594 self._remove_metacontact_family(family, account)
595 brothers = self._add_metacontact_family(family, account)
597 for c, acc in brothers:
598 self.draw_completely(c.jid, acc)
600 # Check is small brothers are under the big brother
601 for child in nearby_family:
602 _jid = child['jid']
603 _account = child['account']
604 if _account == big_brother_account and _jid == big_brother_jid:
605 continue
606 child_iters = self._get_contact_iter(_jid, _account,
607 model=self.model)
608 if not child_iters:
609 continue
610 parent_iter = self.model.iter_parent(child_iters[0])
611 parent_type = self.model[parent_iter][C_TYPE]
612 if parent_type != 'contact':
613 _contact = gajim.contacts.get_contact(_account, _jid)
614 self._remove_entity(_contact, _account)
615 self._add_entity(_contact, _account, groups=None,
616 big_brother_contact=big_brother_contact,
617 big_brother_account=big_brother_account)
619 def _get_nearby_family_and_big_brother(self, family, account):
620 return gajim.contacts.get_nearby_family_and_big_brother(family, account)
622 def _add_self_contact(self, account):
624 Add account's SelfContact to roster and draw it and the account
626 Return the SelfContact contact instance
628 jid = gajim.get_jid_from_account(account)
629 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
631 assert len(self._get_contact_iter(jid, account, contact,
632 self.model)) == 0, 'Self contact %s already in roster' % jid
634 child_iterA = self._get_account_iter(account, self.model)
635 self._iters[account]['contacts'][jid] = [self.model.append(child_iterA,
636 [None, gajim.nicks[account], 'self_contact', jid, account, None,
637 None, None, None, None, None] + [None] * self.nb_ext_renderers)]
639 self.draw_completely(jid, account)
640 self.draw_account(account)
642 return contact
644 def redraw_metacontacts(self, account):
645 for family in gajim.contacts.iter_metacontacts_families(account):
646 self._recalibrate_metacontact_family(family, account)
648 def add_contact(self, jid, account):
650 Add contact to roster and draw him
652 Add contact to all its group and redraw the groups, the contact and the
653 account. If it's a Metacontact, add and draw the whole family.
654 Do nothing if the contact is already in roster.
656 Return the added contact instance. If it is a Metacontact return
657 Big Brother.
659 Keyword arguments:
660 jid -- the contact's jid or SelfJid to add SelfContact
661 account -- the corresponding account.
663 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
664 if len(self._get_contact_iter(jid, account, contact, self.model)):
665 # If contact already in roster, do nothing
666 return
668 if jid == gajim.get_jid_from_account(account):
669 show_self_contact = gajim.config.get('show_self_contact')
670 if show_self_contact == 'never':
671 return
672 if (contact.resource != gajim.connections[account].server_resource \
673 and show_self_contact == 'when_other_resource') or \
674 show_self_contact == 'always':
675 return self._add_self_contact(account)
676 return
678 is_observer = contact.is_observer()
679 if is_observer:
680 # if he has a tag, remove it
681 gajim.contacts.remove_metacontact(account, jid)
683 # Add contact to roster
684 family = gajim.contacts.get_metacontacts_family(account, jid)
685 contacts = []
686 if family:
687 # We have a family. So we are a metacontact.
688 # Add all family members that we shall be grouped with
689 if self.regroup:
690 # remove existing family members to regroup them
691 self._remove_metacontact_family(family, account)
692 contacts = self._add_metacontact_family(family, account)
693 else:
694 # We are a normal contact
695 contacts = [(contact, account), ]
696 self._add_entity(contact, account)
698 # Draw the contact and its groups contact
699 if not self.starting:
700 for c, acc in contacts:
701 self.draw_completely(c.jid, acc)
702 for group in contact.get_shown_groups():
703 self.draw_group(group, account)
704 self._adjust_group_expand_collapse_state(group, account)
705 self.draw_account(account)
707 return contacts[0][0] # it's contact/big brother with highest priority
709 def remove_contact(self, jid, account, force=False, backend=False):
711 Remove contact from roster
713 Remove contact from all its group. Remove empty groups or redraw
714 otherwise.
715 Draw the account.
716 If it's a Metacontact, remove the whole family.
717 Do nothing if the contact is not in roster.
719 Keyword arguments:
720 jid -- the contact's jid or SelfJid to remove SelfContact
721 account -- the corresponding account.
722 force -- remove contact even it has pending evens (Default False)
723 backend -- also remove contact instance (Default False)
725 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
726 if not contact:
727 return
729 if not force and (self.contact_has_pending_roster_events(contact,
730 account) or gajim.interface.msg_win_mgr.get_control(jid, account)):
731 # Contact has pending events or window
732 #TODO: or single message windows? Bur they are not listed for the
733 # moment
734 key = (jid, account)
735 if not key in self.contacts_to_be_removed:
736 self.contacts_to_be_removed[key] = {'backend': backend}
737 # if more pending event, don't remove from roster
738 if self.contact_has_pending_roster_events(contact, account):
739 return False
741 iters = self._get_contact_iter(jid, account, contact, self.model)
742 if iters:
743 # no more pending events
744 # Remove contact from roster directly
745 family = gajim.contacts.get_metacontacts_family(account, jid)
746 if family:
747 # We have a family. So we are a metacontact.
748 self._remove_metacontact_family(family, account)
749 else:
750 self._remove_entity(contact, account)
752 if backend and (not gajim.interface.msg_win_mgr.get_control(jid,
753 account) or force):
754 # If a window is still opened: don't remove contact instance
755 # Remove contact before redrawing, otherwise the old
756 # numbers will still be show
757 gajim.contacts.remove_jid(account, jid, remove_meta=True)
758 if iters:
759 rest_of_family = [data for data in family
760 if account != data['account'] or jid != data['jid']]
761 if rest_of_family:
762 # reshow the rest of the family
763 brothers = self._add_metacontact_family(rest_of_family,
764 account)
765 for c, acc in brothers:
766 self.draw_completely(c.jid, acc)
768 if iters:
769 # Draw all groups of the contact
770 for group in contact.get_shown_groups():
771 self.draw_group(group, account)
772 self.draw_account(account)
774 return True
776 def rename_self_contact(self, old_jid, new_jid, account):
778 Rename the self_contact jid
780 Keyword arguments:
781 old_jid -- our old jid
782 new_jid -- our new jid
783 account -- the corresponding account.
785 gajim.contacts.change_contact_jid(old_jid, new_jid, account)
786 self_iter = self._get_self_contact_iter(account, model=self.model)
787 if not self_iter:
788 return
789 self.model[self_iter][C_JID] = new_jid
790 self.draw_contact(new_jid, account)
792 def add_groupchat(self, jid, account, status=''):
794 Add groupchat to roster and draw it. Return the added contact instance
796 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
797 # Do not show gc if we are disconnected and minimize it
798 if gajim.account_is_connected(account):
799 show = 'online'
800 else:
801 show = 'offline'
802 status = ''
804 if contact is None:
805 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
806 account)
807 if gc_control:
808 # there is a window that we can minimize
809 gajim.interface.minimized_controls[account][jid] = gc_control
810 name = gc_control.name
811 elif jid in gajim.interface.minimized_controls[account]:
812 name = gajim.interface.minimized_controls[account][jid].name
813 else:
814 name = jid.split('@')[0]
815 # New groupchat
816 contact = gajim.contacts.create_contact(jid=jid, account=account,
817 name=name, groups=[_('Groupchats')], show=show, status=status,
818 sub='none')
819 gajim.contacts.add_contact(account, contact)
820 self.add_contact(jid, account)
821 else:
822 if jid not in gajim.interface.minimized_controls[account]:
823 # there is a window that we can minimize
824 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
825 account)
826 gajim.interface.minimized_controls[account][jid] = gc_control
827 contact.show = show
828 contact.status = status
829 self.adjust_and_draw_contact_context(jid, account)
831 return contact
834 def remove_groupchat(self, jid, account):
836 Remove groupchat from roster and redraw account and group
838 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
839 if contact.is_groupchat():
840 if jid in gajim.interface.minimized_controls[account]:
841 del gajim.interface.minimized_controls[account][jid]
842 self.remove_contact(jid, account, force=True, backend=True)
843 return True
844 else:
845 return False
848 # FIXME: This function is yet unused! Port to new API
849 def add_transport(self, jid, account):
851 Add transport to roster and draw it. Return the added contact instance
853 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
854 if contact is None:
855 contact = gajim.contacts.create_contact(jid=jid, account=account,
856 name=jid, groups=[_('Transports')], show='offline',
857 status='offline', sub='from')
858 gajim.contacts.add_contact(account, contact)
859 self.add_contact(jid, account)
860 return contact
862 def remove_transport(self, jid, account):
864 Remove transport from roster and redraw account and group
866 self.remove_contact(jid, account, force=True, backend=True)
867 return True
869 def rename_group(self, old_name, new_name, account):
871 Rename a roster group
873 if old_name == new_name:
874 return
876 # Groups may not change name from or to a special groups
877 for g in helpers.special_groups:
878 if g in (new_name, old_name):
879 return
881 # update all contacts in the given group
882 if self.regroup:
883 accounts = gajim.connections.keys()
884 else:
885 accounts = [account, ]
887 for acc in accounts:
888 changed_contacts = []
889 for jid in gajim.contacts.get_jid_list(acc):
890 contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
891 if old_name not in contact.groups:
892 continue
894 self.remove_contact(jid, acc, force=True)
896 contact.groups.remove(old_name)
897 if new_name not in contact.groups:
898 contact.groups.append(new_name)
900 changed_contacts.append({'jid': jid, 'name': contact.name,
901 'groups':contact.groups})
903 gajim.connections[acc].update_contacts(changed_contacts)
905 for c in changed_contacts:
906 self.add_contact(c['jid'], acc)
908 self._adjust_group_expand_collapse_state(new_name, acc)
910 self.draw_group(old_name, acc)
911 self.draw_group(new_name, acc)
914 def add_contact_to_groups(self, jid, account, groups, update=True):
916 Add contact to given groups and redraw them
918 Contact on server is updated too. When the contact has a family,
919 the action will be performed for all members.
921 Keyword Arguments:
922 jid -- the jid
923 account -- the corresponding account
924 groups -- list of Groups to add the contact to.
925 update -- update contact on the server
927 self.remove_contact(jid, account, force=True)
928 for contact in gajim.contacts.get_contacts(account, jid):
929 for group in groups:
930 if group not in contact.groups:
931 # we might be dropped from meta to group
932 contact.groups.append(group)
933 if update:
934 gajim.connections[account].update_contact(jid, contact.name,
935 contact.groups)
937 self.add_contact(jid, account)
939 for group in groups:
940 self._adjust_group_expand_collapse_state(group, account)
942 def remove_contact_from_groups(self, jid, account, groups, update=True):
944 Remove contact from given groups and redraw them
946 Contact on server is updated too. When the contact has a family,
947 the action will be performed for all members.
949 Keyword Arguments:
950 jid -- the jid
951 account -- the corresponding account
952 groups -- list of Groups to remove the contact from
953 update -- update contact on the server
955 self.remove_contact(jid, account, force=True)
956 for contact in gajim.contacts.get_contacts(account, jid):
957 for group in groups:
958 if group in contact.groups:
959 # Needed when we remove from "General" or "Observers"
960 contact.groups.remove(group)
961 if update:
962 gajim.connections[account].update_contact(jid, contact.name,
963 contact.groups)
964 self.add_contact(jid, account)
966 # Also redraw old groups
967 for group in groups:
968 self.draw_group(group, account)
970 # FIXME: maybe move to gajim.py
971 def remove_newly_added(self, jid, account):
972 if jid in gajim.newly_added[account]:
973 gajim.newly_added[account].remove(jid)
974 self.draw_contact(jid, account)
976 # FIXME: maybe move to gajim.py
977 def remove_to_be_removed(self, jid, account):
978 if account not in gajim.interface.instances:
979 # Account has been deleted during the timeout that called us
980 return
981 if jid in gajim.newly_added[account]:
982 return
983 if jid in gajim.to_be_removed[account]:
984 gajim.to_be_removed[account].remove(jid)
985 family = gajim.contacts.get_metacontacts_family(account, jid)
986 if family:
987 # Peform delayed recalibration
988 self._recalibrate_metacontact_family(family, account)
989 self.draw_contact(jid, account)
991 # FIXME: integrate into add_contact()
992 def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
993 keyID = ''
994 attached_keys = gajim.config.get_per('accounts', account,
995 'attached_gpg_keys').split()
996 if jid in attached_keys:
997 keyID = attached_keys[attached_keys.index(jid) + 1]
998 contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
999 account=account, resource=resource, name=nick, keyID=keyID)
1000 gajim.contacts.add_contact(account, contact)
1001 self.add_contact(contact.jid, account)
1002 return contact
1005 ################################################################################
1006 ### Methods for adding and removing roster window items
1007 ################################################################################
1009 def draw_account(self, account):
1010 child_iter = self._get_account_iter(account, self.model)
1011 if not child_iter:
1012 assert False, 'Account iter of %s could not be found.' % account
1013 return
1015 num_of_accounts = gajim.get_number_of_connected_accounts()
1016 num_of_secured = gajim.get_number_of_securely_connected_accounts()
1018 if gajim.account_is_securely_connected(account) and not self.regroup or\
1019 self.regroup and num_of_secured and num_of_secured == num_of_accounts:
1020 # the only way to create a pixbuf from stock
1021 tls_pixbuf = self.window.render_icon(
1022 gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
1023 self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf
1024 else:
1025 self.model[child_iter][C_PADLOCK_PIXBUF] = None
1027 if self.regroup:
1028 account_name = _('Merged accounts')
1029 accounts = []
1030 else:
1031 account_name = account
1032 accounts = [account]
1034 if account in self.collapsed_rows and \
1035 self.model.iter_has_child(child_iter):
1036 account_name = '[%s]' % account_name
1038 if (gajim.account_is_connected(account) or (self.regroup and \
1039 gajim.get_number_of_connected_accounts())) and gajim.config.get(
1040 'show_contacts_number'):
1041 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
1042 accounts = accounts)
1043 account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
1045 self.model[child_iter][C_NAME] = account_name
1047 pep_dict = gajim.connections[account].pep
1048 if gajim.config.get('show_mood_in_roster') and 'mood' in pep_dict:
1049 self.model[child_iter][C_MOOD_PIXBUF] = pep_dict['mood'].\
1050 asPixbufIcon()
1051 else:
1052 self.model[child_iter][C_MOOD_PIXBUF] = None
1054 if gajim.config.get('show_activity_in_roster') and 'activity' in \
1055 pep_dict:
1056 self.model[child_iter][C_ACTIVITY_PIXBUF] = pep_dict['activity'].\
1057 asPixbufIcon()
1058 else:
1059 self.model[child_iter][C_ACTIVITY_PIXBUF] = None
1061 if gajim.config.get('show_tunes_in_roster') and 'tune' in pep_dict:
1062 self.model[child_iter][C_TUNE_PIXBUF] = pep_dict['tune'].\
1063 asPixbufIcon()
1064 else:
1065 self.model[child_iter][C_TUNE_PIXBUF] = None
1067 if gajim.config.get('show_location_in_roster') and 'location' in \
1068 pep_dict:
1069 self.model[child_iter][C_LOCATION_PIXBUF] = pep_dict['location'].\
1070 asPixbufIcon()
1071 else:
1072 self.model[child_iter][C_LOCATION_PIXBUF] = None
1073 return False
1075 def draw_group(self, group, account):
1076 child_iter = self._get_group_iter(group, account, model=self.model)
1077 if not child_iter:
1078 # Eg. We redraw groups after we removed a entitiy
1079 # and its empty groups
1080 return
1081 if self.regroup:
1082 accounts = []
1083 else:
1084 accounts = [account]
1085 text = gobject.markup_escape_text(group)
1086 if helpers.group_is_blocked(account, group):
1087 text = '<span strikethrough="true">%s</span>' % text
1088 if gajim.config.get('show_contacts_number'):
1089 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
1090 accounts = accounts, groups = [group])
1091 text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
1093 self.model[child_iter][C_NAME] = text
1094 return False
1096 def draw_parent_contact(self, jid, account):
1097 child_iters = self._get_contact_iter(jid, account, model=self.model)
1098 if not child_iters:
1099 return False
1100 parent_iter = self.model.iter_parent(child_iters[0])
1101 if self.model[parent_iter][C_TYPE] != 'contact':
1102 # parent is not a contact
1103 return
1104 parent_jid = self.model[parent_iter][C_JID].decode('utf-8')
1105 parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8')
1106 self.draw_contact(parent_jid, parent_account)
1107 return False
1109 def draw_contact(self, jid, account, selected=False, focus=False):
1111 Draw the correct state image, name BUT not avatar
1113 # focus is about if the roster window has toplevel-focus or not
1114 # FIXME: We really need a custom cell_renderer
1116 contact_instances = gajim.contacts.get_contacts(account, jid)
1117 contact = gajim.contacts.get_highest_prio_contact_from_contacts(
1118 contact_instances)
1119 if not contact:
1120 return False
1122 child_iters = self._get_contact_iter(jid, account, contact, self.model)
1123 if not child_iters:
1124 return False
1126 name = gobject.markup_escape_text(contact.get_shown_name())
1128 # gets number of unread gc marked messages
1129 if jid in gajim.interface.minimized_controls[account] and \
1130 gajim.interface.minimized_controls[account][jid]:
1131 nb_unread = len(gajim.events.get_events(account, jid,
1132 ['printed_marked_gc_msg']))
1133 nb_unread += gajim.interface.minimized_controls \
1134 [account][jid].get_nb_unread_pm()
1136 if nb_unread == 1:
1137 name = '%s *' % name
1138 elif nb_unread > 1:
1139 name = '%s [%s]' % (name, str(nb_unread))
1141 # Strike name if blocked
1142 strike = False
1143 if helpers.jid_is_blocked(account, jid):
1144 strike = True
1145 else:
1146 for group in contact.get_shown_groups():
1147 if helpers.group_is_blocked(account, group):
1148 strike = True
1149 break
1150 if strike:
1151 name = '<span strikethrough="true">%s</span>' % name
1153 # Show resource counter
1154 nb_connected_contact = 0
1155 for c in contact_instances:
1156 if c.show not in ('error', 'offline'):
1157 nb_connected_contact += 1
1158 if nb_connected_contact > 1:
1159 # switch back to default writing direction
1160 name += i18n.paragraph_direction_mark(unicode(name))
1161 name += u' (%d)' % nb_connected_contact
1163 # show (account_name) if there are 2 contact with same jid
1164 # in merged mode
1165 if self.regroup:
1166 add_acct = False
1167 # look through all contacts of all accounts
1168 for account_ in gajim.connections:
1169 # useless to add account name
1170 if account_ == account:
1171 continue
1172 for jid_ in gajim.contacts.get_jid_list(account_):
1173 contact_ = gajim.contacts.get_first_contact_from_jid(
1174 account_, jid_)
1175 if contact_.get_shown_name() == contact.get_shown_name() \
1176 and (jid_, account_) != (jid, account):
1177 add_acct = True
1178 break
1179 if add_acct:
1180 # No need to continue in other account
1181 # if we already found one
1182 break
1183 if add_acct:
1184 name += ' (' + account + ')'
1186 # add status msg, if not empty, under contact name in
1187 # the treeview
1188 if contact.status and gajim.config.get('show_status_msgs_in_roster'):
1189 status = contact.status.strip()
1190 if status != '':
1191 status = helpers.reduce_chars_newlines(status,
1192 max_lines = 1)
1193 # escape markup entities and make them small
1194 # italic and fg color color is calcuted to be
1195 # always readable
1196 color = gtkgui_helpers.get_fade_color(self.tree, selected,
1197 focus)
1198 colorstring = '#%04x%04x%04x' % (color.red, color.green,
1199 color.blue)
1200 name += '\n<span size="small" style="italic" ' \
1201 'foreground="%s">%s</span>' % (colorstring,
1202 gobject.markup_escape_text(status))
1204 icon_name = helpers.get_icon_name_to_show(contact, account)
1205 # look if another resource has awaiting events
1206 for c in contact_instances:
1207 c_icon_name = helpers.get_icon_name_to_show(c, account)
1208 if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
1209 icon_name = c_icon_name
1210 break
1212 # Check for events of collapsed (hidden) brothers
1213 family = gajim.contacts.get_metacontacts_family(account, jid)
1214 is_big_brother = False
1215 have_visible_children = False
1216 if family:
1217 bb_jid, bb_account = \
1218 self._get_nearby_family_and_big_brother(family, account)[1:]
1219 is_big_brother = (jid, account) == (bb_jid, bb_account)
1220 iters = self._get_contact_iter(jid, account)
1221 have_visible_children = iters and \
1222 self.modelfilter.iter_has_child(iters[0])
1224 if have_visible_children:
1225 # We are the big brother and have a visible family
1226 for child_iter in child_iters:
1227 child_path = self.model.get_path(child_iter)
1228 path = self.modelfilter.convert_child_path_to_path(child_path)
1230 if not path:
1231 continue
1233 if not self.tree.row_expanded(path) and icon_name != 'event':
1234 iterC = self.model.iter_children(child_iter)
1235 while iterC:
1236 # a child has awaiting messages?
1237 jidC = self.model[iterC][C_JID].decode('utf-8')
1238 accountC = self.model[iterC][C_ACCOUNT].decode('utf-8')
1239 if len(gajim.events.get_events(accountC, jidC)):
1240 icon_name = 'event'
1241 break
1242 iterC = self.model.iter_next(iterC)
1244 if self.tree.row_expanded(path):
1245 state_images = self.get_appropriate_state_images(
1246 jid, size = 'opened',
1247 icon_name = icon_name)
1248 else:
1249 state_images = self.get_appropriate_state_images(
1250 jid, size = 'closed',
1251 icon_name = icon_name)
1253 # Expand/collapse icon might differ per iter
1254 # (group)
1255 img = state_images[icon_name]
1256 self.model[child_iter][C_IMG] = img
1257 self.model[child_iter][C_NAME] = name
1258 else:
1259 # A normal contact or little brother
1260 state_images = self.get_appropriate_state_images(jid,
1261 icon_name = icon_name)
1263 # All iters have the same icon (no expand/collapse)
1264 img = state_images[icon_name]
1265 for child_iter in child_iters:
1266 self.model[child_iter][C_IMG] = img
1267 self.model[child_iter][C_NAME] = name
1269 # We are a little brother
1270 if family and not is_big_brother and not self.starting:
1271 self.draw_parent_contact(jid, account)
1273 delimiter = gajim.connections[account].nested_group_delimiter
1274 for group in contact.get_shown_groups():
1275 # We need to make sure that _visible_func is called for
1276 # our groups otherwise we might not be shown
1277 group_splited = group.split(delimiter)
1278 i = 1
1279 while i < len(group_splited) + 1:
1280 g = delimiter.join(group_splited[:i])
1281 iterG = self._get_group_iter(g, account, model=self.model)
1282 if iterG:
1283 # it's not self contact
1284 self.model[iterG][C_JID] = self.model[iterG][C_JID]
1285 i += 1
1287 gajim.plugin_manager.gui_extension_point('roster_draw_contact', self,
1288 jid, account, contact)
1290 return False
1292 def _is_pep_shown_in_roster(self, pep_type):
1293 if pep_type == 'mood':
1294 return gajim.config.get('show_mood_in_roster')
1295 elif pep_type == 'activity':
1296 return gajim.config.get('show_activity_in_roster')
1297 elif pep_type == 'tune':
1298 return gajim.config.get('show_tunes_in_roster')
1299 elif pep_type == 'location':
1300 return gajim.config.get('show_location_in_roster')
1301 else:
1302 return False
1304 def draw_all_pep_types(self, jid, account):
1305 for pep_type in self._pep_type_to_model_column:
1306 self.draw_pep(jid, account, pep_type)
1308 def draw_pep(self, jid, account, pep_type):
1309 if pep_type not in self._pep_type_to_model_column:
1310 return
1311 if not self._is_pep_shown_in_roster(pep_type):
1312 return
1314 model_column = self._pep_type_to_model_column[pep_type]
1315 iters = self._get_contact_iter(jid, account, model=self.model)
1316 if not iters:
1317 return
1318 contact = gajim.contacts.get_contact(account, jid)
1319 if pep_type in contact.pep:
1320 pixbuf = contact.pep[pep_type].asPixbufIcon()
1321 else:
1322 pixbuf = None
1323 for child_iter in iters:
1324 self.model[child_iter][model_column] = pixbuf
1326 def draw_avatar(self, jid, account):
1327 iters = self._get_contact_iter(jid, account, model=self.model)
1328 if not iters or not gajim.config.get('show_avatars_in_roster'):
1329 return
1330 jid = self.model[iters[0]][C_JID].decode('utf-8')
1331 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
1332 if pixbuf in (None, 'ask'):
1333 scaled_pixbuf = None
1334 else:
1335 scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
1336 for child_iter in iters:
1337 self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf
1338 return False
1340 def draw_completely(self, jid, account):
1341 self.draw_contact(jid, account)
1342 self.draw_all_pep_types(jid, account)
1343 self.draw_avatar(jid, account)
1345 def adjust_and_draw_contact_context(self, jid, account):
1347 Draw contact, account and groups of given jid Show contact if it has
1348 pending events
1350 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
1351 if not contact:
1352 # idle draw or just removed SelfContact
1353 return
1355 family = gajim.contacts.get_metacontacts_family(account, jid)
1356 if family:
1357 # There might be a new big brother
1358 self._recalibrate_metacontact_family(family, account)
1359 self.draw_contact(jid, account)
1360 self.draw_account(account)
1362 for group in contact.get_shown_groups():
1363 self.draw_group(group, account)
1364 self._adjust_group_expand_collapse_state(group, account)
1366 def _idle_draw_jids_of_account(self, jids, account):
1368 Draw given contacts and their avatars in a lazy fashion
1370 Keyword arguments:
1371 jids -- a list of jids to draw
1372 account -- the corresponding account
1374 def _draw_all_contacts(jids, account):
1375 for jid in jids:
1376 family = gajim.contacts.get_metacontacts_family(account, jid)
1377 if family:
1378 # For metacontacts over several accounts:
1379 # When we connect a new account existing brothers
1380 # must be redrawn (got removed and readded)
1381 for data in family:
1382 self.draw_completely(data['jid'], data['account'])
1383 else:
1384 self.draw_completely(jid, account)
1385 yield True
1386 self.refilter_shown_roster_items()
1387 yield False
1389 task = _draw_all_contacts(jids, account)
1390 gobject.idle_add(task.next)
1392 def _before_fill(self):
1393 self.tree.freeze_child_notify()
1394 self.tree.set_model(None)
1395 # disable sorting
1396 self.model.set_sort_column_id(-2, gtk.SORT_ASCENDING)
1398 def _after_fill(self):
1399 self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
1400 self.tree.set_model(self.modelfilter)
1401 self.tree.thaw_child_notify()
1403 def setup_and_draw_roster(self):
1405 Create new empty model and draw roster
1407 self.modelfilter = None
1408 self.model = gtk.TreeStore(*self.columns)
1410 self.model.set_sort_func(1, self._compareIters)
1411 self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
1412 self.modelfilter = self.model.filter_new()
1413 self.modelfilter.set_visible_func(self._visible_func)
1414 self.modelfilter.connect('row-has-child-toggled',
1415 self.on_modelfilter_row_has_child_toggled)
1416 self.tree.set_model(self.modelfilter)
1418 self._iters = {}
1419 # for merged mode
1420 self._iters['MERGED'] = {'account': None, 'groups': {}}
1422 for acct in gajim.contacts.get_accounts():
1423 self._iters[acct] = {'account': None, 'groups': {}, 'contacts': {}}
1424 self.add_account(acct)
1425 self.add_account_contacts(acct)
1427 # Recalculate column width for ellipsizing
1428 self.tree.columns_autosize()
1431 def select_contact(self, jid, account):
1433 Select contact in roster. If contact is hidden but has events, show him
1435 # Refiltering SHOULD NOT be needed:
1436 # When a contact gets a new event he will be redrawn and his
1437 # icon changes, so _visible_func WILL be called on him anyway
1438 iters = self._get_contact_iter(jid, account)
1439 if not iters:
1440 # Not visible in roster
1441 return
1442 path = self.modelfilter.get_path(iters[0])
1443 if self.dragging or not gajim.config.get(
1444 'scroll_roster_to_last_message'):
1445 # do not change selection while DND'ing
1446 return
1447 # Expand his parent, so this path is visible, don't expand it.
1448 self.tree.expand_to_path(path[:-1])
1449 self.tree.scroll_to_cell(path)
1450 self.tree.set_cursor(path)
1453 def _adjust_account_expand_collapse_state(self, account):
1455 Expand/collapse account row based on self.collapsed_rows
1457 if not self.tree.get_model():
1458 return
1459 iterA = self._get_account_iter(account)
1460 if not iterA:
1461 # thank you modelfilter
1462 return
1463 path = self.modelfilter.get_path(iterA)
1464 if account in self.collapsed_rows:
1465 self.tree.collapse_row(path)
1466 else:
1467 self.tree.expand_row(path, False)
1468 return False
1471 def _adjust_group_expand_collapse_state(self, group, account):
1473 Expand/collapse group row based on self.collapsed_rows
1475 if not self.tree.get_model():
1476 return
1477 delimiter = gajim.connections[account].nested_group_delimiter
1478 group_splited = group.split(delimiter)
1479 i = 1
1480 while i < len(group_splited) + 1:
1481 g = delimiter.join(group_splited[:i])
1482 iterG = self._get_group_iter(g, account)
1483 if not iterG:
1484 # Group not visible
1485 return
1486 path = self.modelfilter.get_path(iterG)
1487 if account + g in self.collapsed_rows:
1488 self.tree.collapse_row(path)
1489 else:
1490 self.tree.expand_row(path, False)
1491 i += 1
1493 ##############################################################################
1494 ### Roster and Modelfilter handling
1495 ##############################################################################
1497 def _search_roster_func(self, model, column, key, titer):
1498 key = key.decode('utf-8').lower()
1499 name = model[titer][C_NAME].decode('utf-8').lower()
1500 return not (key in name)
1502 def refilter_shown_roster_items(self):
1503 self.filtering = True
1504 self.modelfilter.refilter()
1505 self.filtering = False
1507 def contact_has_pending_roster_events(self, contact, account):
1509 Return True if the contact or one if it resources has pending events
1511 # jid has pending events
1512 if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
1513 return True
1514 # check events of all resources
1515 for contact_ in gajim.contacts.get_contacts(account, contact.jid):
1516 if contact_.resource and gajim.events.get_nb_roster_events(account,
1517 contact_.get_full_jid()) > 0:
1518 return True
1519 return False
1521 def contact_is_visible(self, contact, account):
1522 if self.contact_has_pending_roster_events(contact, account):
1523 return True
1525 if contact.show in ('offline', 'error'):
1526 if contact.jid in gajim.to_be_removed[account]:
1527 return True
1528 return False
1529 if gajim.config.get('show_only_chat_and_online') and contact.show in (
1530 'away', 'xa', 'busy'):
1531 return False
1532 return True
1534 def _visible_func(self, model, titer):
1536 Determine whether iter should be visible in the treeview
1538 type_ = model[titer][C_TYPE]
1539 if not type_:
1540 return False
1541 if type_ == 'account':
1542 # Always show account
1543 return True
1545 account = model[titer][C_ACCOUNT]
1546 if not account:
1547 return False
1549 account = account.decode('utf-8')
1550 jid = model[titer][C_JID]
1551 if not jid:
1552 return False
1553 jid = jid.decode('utf-8')
1554 if type_ == 'group':
1555 group = jid
1556 if group == _('Transports'):
1557 if self.regroup:
1558 accounts = gajim.contacts.get_accounts()
1559 else:
1560 accounts = [account]
1561 for _acc in accounts:
1562 for contact in gajim.contacts.iter_contacts(_acc):
1563 if group in contact.get_shown_groups() and \
1564 self.contact_has_pending_roster_events(contact, _acc):
1565 return True
1566 return gajim.config.get('show_transports_group') and \
1567 (gajim.account_is_connected(account) or \
1568 gajim.config.get('showoffline'))
1569 if gajim.config.get('showoffline'):
1570 return True
1572 if self.regroup:
1573 # C_ACCOUNT for groups depends on the order
1574 # accounts were connected
1575 # Check all accounts for online group contacts
1576 accounts = gajim.contacts.get_accounts()
1577 else:
1578 accounts = [account]
1579 for _acc in accounts:
1580 delimiter = gajim.connections[_acc].nested_group_delimiter
1581 for contact in gajim.contacts.iter_contacts(_acc):
1582 if not self.contact_is_visible(contact, _acc):
1583 continue
1584 # Is this contact in this group?
1585 for grp in contact.get_shown_groups():
1586 while grp:
1587 if group == grp:
1588 return True
1589 grp = delimiter.join(grp.split(delimiter)[:-1])
1590 return False
1591 if type_ == 'contact':
1592 if self.rfilter_enabled:
1593 return self.rfilter_string in model[titer][C_NAME].lower()
1594 if gajim.config.get('showoffline'):
1595 return True
1596 bb_jid = None
1597 bb_account = None
1598 family = gajim.contacts.get_metacontacts_family(account, jid)
1599 if family:
1600 nearby_family, bb_jid, bb_account = \
1601 self._get_nearby_family_and_big_brother(family, account)
1602 if (bb_jid, bb_account) == (jid, account):
1603 # Show the big brother if a child has pending events
1604 for data in nearby_family:
1605 jid = data['jid']
1606 account = data['account']
1607 contact = gajim.contacts.get_contact_with_highest_priority(
1608 account, jid)
1609 if contact and self.contact_is_visible(contact, account):
1610 return True
1611 return False
1612 else:
1613 contact = gajim.contacts.get_contact_with_highest_priority(
1614 account, jid)
1615 return self.contact_is_visible(contact, account)
1616 if type_ == 'agent':
1617 contact = gajim.contacts.get_contact_with_highest_priority(account,
1618 jid)
1619 return self.contact_has_pending_roster_events(contact, account) or \
1620 (gajim.config.get('show_transports_group') and \
1621 (gajim.account_is_connected(account) or \
1622 gajim.config.get('showoffline')))
1623 return True
1625 def _compareIters(self, model, iter1, iter2, data=None):
1627 Compare two iters to sort them
1629 name1 = model[iter1][C_NAME]
1630 name2 = model[iter2][C_NAME]
1631 if not name1 or not name2:
1632 return 0
1633 name1 = name1.decode('utf-8')
1634 name2 = name2.decode('utf-8')
1635 type1 = model[iter1][C_TYPE]
1636 type2 = model[iter2][C_TYPE]
1637 if type1 == 'self_contact':
1638 return -1
1639 if type2 == 'self_contact':
1640 return 1
1641 if type1 == 'group':
1642 name1 = model[iter1][C_JID]
1643 name2 = model[iter2][C_JID]
1644 if name1 == _('Transports'):
1645 return 1
1646 if name2 == _('Transports'):
1647 return -1
1648 if name1 == _('Not in Roster'):
1649 return 1
1650 if name2 == _('Not in Roster'):
1651 return -1
1652 if name1 == _('Groupchats'):
1653 return 1
1654 if name2 == _('Groupchats'):
1655 return -1
1656 account1 = model[iter1][C_ACCOUNT]
1657 account2 = model[iter2][C_ACCOUNT]
1658 if not account1 or not account2:
1659 return 0
1660 account1 = account1.decode('utf-8')
1661 account2 = account2.decode('utf-8')
1662 if type1 == 'account':
1663 return locale.strcoll(account1, account2)
1664 jid1 = model[iter1][C_JID].decode('utf-8')
1665 jid2 = model[iter2][C_JID].decode('utf-8')
1666 if type1 == 'contact':
1667 lcontact1 = gajim.contacts.get_contacts(account1, jid1)
1668 contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
1669 if not contact1:
1670 return 0
1671 name1 = contact1.get_shown_name()
1672 if type2 == 'contact':
1673 lcontact2 = gajim.contacts.get_contacts(account2, jid2)
1674 contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
1675 if not contact2:
1676 return 0
1677 name2 = contact2.get_shown_name()
1678 # We first compare by show if sort_by_show_in_roster is True or if it's
1679 # a child contact
1680 if type1 == 'contact' and type2 == 'contact' and \
1681 gajim.config.get('sort_by_show_in_roster'):
1682 cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
1683 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
1684 s = self.get_show(lcontact1)
1685 show1 = cshow.get(s, 9)
1686 s = self.get_show(lcontact2)
1687 show2 = cshow.get(s, 9)
1688 removing1 = False
1689 removing2 = False
1690 if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
1691 removing1 = True
1692 if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
1693 removing2 = True
1694 if removing1 and not removing2:
1695 return 1
1696 if removing2 and not removing1:
1697 return -1
1698 sub1 = contact1.sub
1699 sub2 = contact2.sub
1700 # none and from goes after
1701 if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
1702 return -1
1703 if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
1704 return 1
1705 if show1 < show2:
1706 return -1
1707 elif show1 > show2:
1708 return 1
1709 # We compare names
1710 cmp_result = locale.strcoll(name1.lower(), name2.lower())
1711 if cmp_result < 0:
1712 return -1
1713 if cmp_result > 0:
1714 return 1
1715 if type1 == 'contact' and type2 == 'contact':
1716 # We compare account names
1717 cmp_result = locale.strcoll(account1.lower(), account2.lower())
1718 if cmp_result < 0:
1719 return -1
1720 if cmp_result > 0:
1721 return 1
1722 # We compare jids
1723 cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
1724 if cmp_result < 0:
1725 return -1
1726 if cmp_result > 0:
1727 return 1
1728 return 0
1730 ################################################################################
1731 ### FIXME: Methods that don't belong to roster window...
1732 ### ... atleast not in there current form
1733 ################################################################################
1735 def fire_up_unread_messages_events(self, account):
1737 Read from db the unread messages, and fire them up, and if we find very
1738 old unread messages, delete them from unread table
1740 results = gajim.logger.get_unread_msgs()
1741 for result in results:
1742 jid = result[4]
1743 shown = result[5]
1744 if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
1745 shown:
1746 # We have this jid in our contacts list
1747 # XXX unread messages should probably have their session saved
1748 # with them
1749 session = gajim.connections[account].make_new_session(jid)
1751 tim = time.localtime(float(result[2]))
1752 session.roster_message(jid, result[1], tim, msg_type='chat',
1753 msg_id=result[0])
1754 gajim.logger.set_shown_unread_msgs(result[0])
1756 elif (time.time() - result[2]) > 2592000:
1757 # ok, here we see that we have a message in unread messages
1758 # table that is older than a month. It is probably from someone
1759 # not in our roster for accounts we usually launch, so we will
1760 # delete this id from unread message tables.
1761 gajim.logger.set_read_messages([result[0]])
1763 def fill_contacts_and_groups_dicts(self, array, account):
1765 Fill gajim.contacts and gajim.groups
1767 # FIXME: This function needs to be splitted
1768 # Most of the logic SHOULD NOT be done at GUI level
1769 if account not in gajim.contacts.get_accounts():
1770 gajim.contacts.add_account(account)
1771 if not account in self._iters:
1772 self._iters[account] = {'account': None, 'groups': {},
1773 'contacts': {}}
1774 if account not in gajim.groups:
1775 gajim.groups[account] = {}
1776 if gajim.config.get('show_self_contact') == 'always':
1777 self_jid = gajim.get_jid_from_account(account)
1778 if gajim.connections[account].server_resource:
1779 self_jid += '/' + gajim.connections[account].server_resource
1780 array[self_jid] = {'name': gajim.nicks[account],
1781 'groups': ['self_contact'], 'subscription': 'both',
1782 'ask': 'none'}
1783 # .keys() is needed
1784 for jid in array.keys():
1785 # Remove the contact in roster. It might has changed
1786 self.remove_contact(jid, account, force=True)
1787 # Remove old Contact instances
1788 gajim.contacts.remove_jid(account, jid, remove_meta=False)
1789 jids = jid.split('/')
1790 # get jid
1791 ji = jids[0]
1792 # get resource
1793 resource = ''
1794 if len(jids) > 1:
1795 resource = '/'.join(jids[1:])
1796 # get name
1797 name = array[jid]['name'] or ''
1798 show = 'offline' # show is offline by default
1799 status = '' # no status message by default
1801 keyID = ''
1802 attached_keys = gajim.config.get_per('accounts', account,
1803 'attached_gpg_keys').split()
1804 if jid in attached_keys:
1805 keyID = attached_keys[attached_keys.index(jid) + 1]
1807 if gajim.jid_is_transport(jid):
1808 array[jid]['groups'] = [_('Transports')]
1809 #TRANSP - potential
1810 contact1 = gajim.contacts.create_contact(jid=ji, account=account,
1811 name=name, groups=array[jid]['groups'], show=show,
1812 status=status, sub=array[jid]['subscription'],
1813 ask=array[jid]['ask'], resource=resource, keyID=keyID)
1814 gajim.contacts.add_contact(account, contact1)
1816 if gajim.config.get('ask_avatars_on_startup'):
1817 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
1818 if pixbuf == 'ask':
1819 transport = gajim.get_transport_name_from_jid(contact1.jid)
1820 if not transport or gajim.jid_is_transport(contact1.jid):
1821 jid_with_resource = contact1.jid
1822 if contact1.resource:
1823 jid_with_resource += '/' + contact1.resource
1824 gajim.connections[account].request_vcard(
1825 jid_with_resource)
1826 else:
1827 host = gajim.get_server_from_jid(contact1.jid)
1828 if host not in gajim.transport_avatar[account]:
1829 gajim.transport_avatar[account][host] = \
1830 [contact1.jid]
1831 else:
1832 gajim.transport_avatar[account][host].append(
1833 contact1.jid)
1835 # If we already have chat windows opened, update them with new
1836 # contact instance
1837 chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
1838 if chat_control:
1839 chat_control.contact = contact1
1841 def connected_rooms(self, account):
1842 if account in gajim.gc_connected[account].values():
1843 return True
1844 return False
1846 def on_event_removed(self, event_list):
1848 Remove contacts on last events removed
1850 Only performed if removal was requested before but the contact still had
1851 pending events
1853 contact_list = ((event.jid.split('/')[0], event.account) for event in \
1854 event_list)
1856 for jid, account in contact_list:
1857 self.draw_contact(jid, account)
1858 # Remove contacts in roster if removal was requested
1859 key = (jid, account)
1860 if key in self.contacts_to_be_removed.keys():
1861 backend = self.contacts_to_be_removed[key]['backend']
1862 del self.contacts_to_be_removed[key]
1863 # Remove contact will delay removal if there are more events
1864 # pending
1865 self.remove_contact(jid, account, backend=backend)
1866 self.show_title()
1868 def open_event(self, account, jid, event):
1870 If an event was handled, return True, else return False
1872 data = event.parameters
1873 ft = gajim.interface.instances['file_transfers']
1874 event = gajim.events.get_first_event(account, jid, event.type_)
1875 if event.type_ == 'normal':
1876 dialogs.SingleMessageWindow(account, jid,
1877 action='receive', from_whom=jid, subject=data[1],
1878 message=data[0], resource=data[5], session=data[8],
1879 form_node=data[9])
1880 gajim.events.remove_events(account, jid, event)
1881 return True
1882 elif event.type_ == 'file-request':
1883 contact = gajim.contacts.get_contact_with_highest_priority(account,
1884 jid)
1885 ft.show_file_request(account, contact, data)
1886 gajim.events.remove_events(account, jid, event)
1887 return True
1888 elif event.type_ in ('file-request-error', 'file-send-error'):
1889 ft.show_send_error(data)
1890 gajim.events.remove_events(account, jid, event)
1891 return True
1892 elif event.type_ in ('file-error', 'file-stopped'):
1893 msg_err = ''
1894 if data['error'] == -1:
1895 msg_err = _('Remote contact stopped transfer')
1896 elif data['error'] == -6:
1897 msg_err = _('Error opening file')
1898 ft.show_stopped(jid, data, error_msg=msg_err)
1899 gajim.events.remove_events(account, jid, event)
1900 return True
1901 elif event.type_ == 'file-completed':
1902 ft.show_completed(jid, data)
1903 gajim.events.remove_events(account, jid, event)
1904 return True
1905 elif event.type_ == 'gc-invitation':
1906 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1907 data[1])
1908 gajim.events.remove_events(account, jid, event)
1909 return True
1910 elif event.type_ == 'subscription_request':
1911 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1912 gajim.events.remove_events(account, jid, event)
1913 return True
1914 elif event.type_ == 'unsubscribed':
1915 gajim.interface.show_unsubscribed_dialog(account, data)
1916 gajim.events.remove_events(account, jid, event)
1917 return True
1918 elif event.type_ == 'jingle-incoming':
1919 peerjid, sid, content_types = data
1920 dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
1921 gajim.events.remove_events(account, jid, event)
1922 return True
1923 return False
1925 ################################################################################
1926 ### This and that... random.
1927 ################################################################################
1929 def show_roster_vbox(self, active):
1930 vb = self.xml.get_object('roster_vbox2')
1931 if active:
1932 vb.set_no_show_all(False)
1933 vb.show()
1934 else:
1935 vb.hide()
1936 vb.set_no_show_all(True)
1938 def show_tooltip(self, contact):
1939 pointer = self.tree.get_pointer()
1940 props = self.tree.get_path_at_pos(pointer[0], pointer[1])
1941 # check if the current pointer is at the same path
1942 # as it was before setting the timeout
1943 if props and self.tooltip.id == props[0]:
1944 # bounding rectangle of coordinates for the cell within the treeview
1945 rect = self.tree.get_cell_area(props[0], props[1])
1947 # position of the treeview on the screen
1948 position = self.tree.window.get_origin()
1949 self.tooltip.show_tooltip(contact, rect.height, position[1] + \
1950 rect.y)
1951 else:
1952 self.tooltip.hide_tooltip()
1955 def authorize(self, widget, jid, account):
1957 Authorize a contact (by re-sending auth menuitem)
1959 gajim.connections[account].send_authorization(jid)
1960 dialogs.InformationDialog(_('Authorization has been sent'),
1961 _('Now "%s" will know your status.') %jid)
1963 def req_sub(self, widget, jid, txt, account, groups=None, nickname=None,
1964 auto_auth=False):
1966 Request subscription to a contact
1968 groups_list = groups or []
1969 gajim.connections[account].request_subscription(jid, txt, nickname,
1970 groups_list, auto_auth, gajim.nicks[account])
1971 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
1972 if not contact:
1973 keyID = ''
1974 attached_keys = gajim.config.get_per('accounts', account,
1975 'attached_gpg_keys').split()
1976 if jid in attached_keys:
1977 keyID = attached_keys[attached_keys.index(jid) + 1]
1978 contact = gajim.contacts.create_contact(jid=jid, account=account,
1979 name=nickname, groups=groups_list, show='requested', status='',
1980 ask='none', sub='subscribe', keyID=keyID)
1981 gajim.contacts.add_contact(account, contact)
1982 else:
1983 if not _('Not in Roster') in contact.get_shown_groups():
1984 dialogs.InformationDialog(_('Subscription request has been '
1985 'sent'), _('If "%s" accepts this request you will know his '
1986 'or her status.') % jid)
1987 return
1988 self.remove_contact(contact.jid, account, force=True)
1989 contact.groups = groups_list
1990 if nickname:
1991 contact.name = nickname
1992 self.add_contact(jid, account)
1994 def revoke_auth(self, widget, jid, account):
1996 Revoke a contact's authorization
1998 gajim.connections[account].refuse_authorization(jid)
1999 dialogs.InformationDialog(_('Authorization has been removed'),
2000 _('Now "%s" will always see you as offline.') %jid)
2002 def set_state(self, account, state):
2003 child_iterA = self._get_account_iter(account, self.model)
2004 if child_iterA:
2005 self.model[child_iterA][0] = \
2006 gajim.interface.jabber_state_images['16'][state]
2007 if gajim.interface.systray_enabled:
2008 gajim.interface.systray.change_status(state)
2010 def set_connecting_state(self, account):
2011 self.set_state(account, 'connecting')
2013 def send_status(self, account, status, txt, auto=False, to=None):
2014 if status != 'offline':
2015 if to is None:
2016 if status == gajim.connections[account].get_status() and \
2017 txt == gajim.connections[account].status:
2018 return
2019 gajim.config.set_per('accounts', account, 'last_status', status)
2020 gajim.config.set_per('accounts', account, 'last_status_msg',
2021 helpers.to_one_line(txt))
2022 if gajim.connections[account].connected < 2:
2023 self.set_connecting_state(account)
2025 keyid = gajim.config.get_per('accounts', account, 'keyid')
2026 if keyid and not gajim.connections[account].gpg:
2027 dialogs.WarningDialog(_('GPG is not usable'),
2028 _('You will be connected to %s without OpenPGP.') % \
2029 account)
2031 self.send_status_continue(account, status, txt, auto, to)
2033 def send_pep(self, account, pep_dict):
2034 connection = gajim.connections[account]
2036 if 'activity' in pep_dict:
2037 activity = pep_dict['activity']
2038 subactivity = pep_dict.get('subactivity', None)
2039 activity_text = pep_dict.get('activity_text', None)
2040 connection.send_activity(activity, subactivity, activity_text)
2041 else:
2042 connection.retract_activity()
2044 if 'mood' in pep_dict:
2045 mood = pep_dict['mood']
2046 mood_text = pep_dict.get('mood_text', None)
2047 connection.send_mood(mood, mood_text)
2048 else:
2049 connection.retract_mood()
2051 def delete_pep(self, jid, account):
2052 if jid == gajim.get_jid_from_account(account):
2053 gajim.connections[account].pep = {}
2054 self.draw_account(account)
2056 for contact in gajim.contacts.get_contacts(account, jid):
2057 contact.pep = {}
2059 self.draw_all_pep_types(jid, account)
2060 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
2061 if ctrl:
2062 ctrl.update_all_pep_types()
2064 def send_status_continue(self, account, status, txt, auto, to):
2065 if gajim.account_is_connected(account) and not to:
2066 if status == 'online' and gajim.interface.sleeper.getState() != \
2067 common.sleepy.STATE_UNKNOWN:
2068 gajim.sleeper_state[account] = 'online'
2069 elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \
2070 status == 'offline':
2071 gajim.sleeper_state[account] = 'off'
2073 if to:
2074 gajim.connections[account].send_custom_status(status, txt, to)
2075 else:
2076 if status in ('invisible', 'offline'):
2077 self.delete_pep(gajim.get_jid_from_account(account), account)
2078 was_invisible = gajim.connections[account].connected == \
2079 gajim.SHOW_LIST.index('invisible')
2080 gajim.connections[account].change_status(status, txt, auto)
2082 if account in gajim.interface.status_sent_to_users:
2083 gajim.interface.status_sent_to_users[account] = {}
2084 if account in gajim.interface.status_sent_to_groups:
2085 gajim.interface.status_sent_to_groups[account] = {}
2086 for gc_control in gajim.interface.msg_win_mgr.get_controls(
2087 message_control.TYPE_GC) + \
2088 gajim.interface.minimized_controls[account].values():
2089 if gc_control.account == account:
2090 if gajim.gc_connected[account][gc_control.room_jid]:
2091 gajim.connections[account].send_gc_status(
2092 gc_control.nick, gc_control.room_jid, status, txt)
2093 else:
2094 # for some reason, we are not connected to the room even
2095 # if tab is opened, send initial join_gc()
2096 gajim.connections[account].join_gc(gc_control.nick,
2097 gc_control.room_jid, None)
2098 if was_invisible and status != 'offline':
2099 # We come back from invisible, join bookmarks
2100 gajim.interface.auto_join_bookmarks(account)
2103 def chg_contact_status(self, contact, show, status, account):
2105 When a contact changes his or her status
2107 contact_instances = gajim.contacts.get_contacts(account, contact.jid)
2108 contact.show = show
2109 contact.status = status
2110 # name is to show in conversation window
2111 name = contact.get_shown_name()
2112 fjid = contact.get_full_jid()
2114 # The contact has several resources
2115 if len(contact_instances) > 1:
2116 if contact.resource != '':
2117 name += '/' + contact.resource
2119 # Remove resource when going offline
2120 if show in ('offline', 'error') and \
2121 not self.contact_has_pending_roster_events(contact, account):
2122 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2123 if ctrl:
2124 ctrl.update_ui()
2125 ctrl.parent_win.redraw_tab(ctrl)
2126 # keep the contact around, since it's
2127 # already attached to the control
2128 else:
2129 gajim.contacts.remove_contact(account, contact)
2131 elif contact.jid == gajim.get_jid_from_account(account) and \
2132 show in ('offline', 'error'):
2133 if gajim.config.get('show_self_contact') != 'never':
2134 # SelfContact went offline. Remove him when last pending
2135 # message was read
2136 self.remove_contact(contact.jid, account, backend=True)
2138 uf_show = helpers.get_uf_show(show)
2140 # print status in chat window and update status/GPG image
2141 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
2142 if ctrl and ctrl.type_id != message_control.TYPE_GC:
2143 ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
2144 account, contact.jid)
2145 ctrl.update_status_display(name, uf_show, status)
2147 if contact.resource:
2148 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2149 if ctrl:
2150 ctrl.update_status_display(name, uf_show, status)
2152 # Delete pep if needed
2153 keep_pep = any(c.show not in ('error', 'offline') for c in
2154 contact_instances)
2155 if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
2156 and not contact.is_groupchat():
2157 self.delete_pep(contact.jid, account)
2159 # Redraw everything and select the sender
2160 self.adjust_and_draw_contact_context(contact.jid, account)
2163 def on_status_changed(self, account, show):
2165 The core tells us that our status has changed
2167 if account not in gajim.contacts.get_accounts():
2168 return
2169 child_iterA = self._get_account_iter(account, self.model)
2170 if gajim.config.get('show_self_contact') == 'always':
2171 self_resource = gajim.connections[account].server_resource
2172 self_contact = gajim.contacts.get_contact(account,
2173 gajim.get_jid_from_account(account), resource=self_resource)
2174 if self_contact:
2175 status = gajim.connections[account].status
2176 self.chg_contact_status(self_contact, show, status, account)
2177 self.set_account_status_icon(account)
2178 if show == 'offline':
2179 if self.quit_on_next_offline > -1:
2180 # we want to quit, we are waiting for all accounts to be offline
2181 self.quit_on_next_offline -= 1
2182 if self.quit_on_next_offline < 1:
2183 # all accounts offline, quit
2184 self.quit_gtkgui_interface()
2185 else:
2186 # No need to redraw contacts if we're quitting
2187 if child_iterA:
2188 self.model[child_iterA][C_AVATAR_PIXBUF] = None
2189 if account in gajim.con_types:
2190 gajim.con_types[account] = None
2191 for jid in gajim.contacts.get_jid_list(account):
2192 lcontact = gajim.contacts.get_contacts(account, jid)
2193 ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid,
2194 account)
2195 for contact in [c for c in lcontact if (
2196 (c.show != 'offline' or c.is_transport()) and not ctrl)]:
2197 self.chg_contact_status(contact, 'offline', '', account)
2198 self.set_actions_menu_needs_rebuild()
2199 self.update_status_combobox()
2201 def get_status_message(self, show, on_response, show_pep=True,
2202 always_ask=False):
2204 Get the status message by:
2206 1/ looking in default status message
2207 2/ asking to user if needed depending on ask_on(ff)line_status and
2208 always_ask
2209 show_pep can be False to hide pep things from status message or True
2211 empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
2212 'mood': '', 'mood_text': ''}
2213 if show in gajim.config.get_per('defaultstatusmsg'):
2214 if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
2215 msg = gajim.config.get_per('defaultstatusmsg', show, 'message')
2216 msg = helpers.from_one_line(msg)
2217 on_response(msg, empty_pep)
2218 return
2219 if not always_ask and ((show == 'online' and not gajim.config.get(
2220 'ask_online_status')) or (show in ('offline', 'invisible') and not \
2221 gajim.config.get('ask_offline_status'))):
2222 on_response('', empty_pep)
2223 return
2225 dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
2226 dlg.dialog.present() # show it on current workspace
2228 def change_status(self, widget, account, status):
2229 def change(account, status):
2230 def on_response(message, pep_dict):
2231 if message is None:
2232 # user pressed Cancel to change status message dialog
2233 return
2234 self.send_status(account, status, message)
2235 self.send_pep(account, pep_dict)
2236 self.get_status_message(status, on_response)
2238 if status == 'invisible' and self.connected_rooms(account):
2239 dialogs.ConfirmationDialog(
2240 _('You are participating in one or more group chats'),
2241 _('Changing your status to invisible will result in '
2242 'disconnection from those group chats. Are you sure you want '
2243 'to go invisible?'), on_response_ok = (change, account, status))
2244 else:
2245 change(account, status)
2247 def update_status_combobox(self):
2248 # table to change index in connection.connected to index in combobox
2249 table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
2250 'xa':3, 'dnd':4, 'invisible':5}
2252 # we check if there are more options in the combobox that it should
2253 # if yes, we remove the first ones
2254 while len(self.status_combobox.get_model()) > len(table)+2:
2255 self.status_combobox.remove_text(0)
2257 show = helpers.get_global_show()
2258 # temporarily block signal in order not to send status that we show
2259 # in the combobox
2260 self.combobox_callback_active = False
2261 if helpers.statuses_unified():
2262 self.status_combobox.set_active(table[show])
2263 else:
2264 uf_show = helpers.get_uf_show(show)
2265 liststore = self.status_combobox.get_model()
2266 liststore.prepend(['SEPARATOR', None, '', True])
2267 status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
2268 liststore.prepend([status_combobox_text,
2269 gajim.interface.jabber_state_images['16'][show], show, False])
2270 self.status_combobox.set_active(0)
2271 gajim.interface.change_awn_icon_status(show)
2272 self.combobox_callback_active = True
2273 if gajim.interface.systray_enabled:
2274 gajim.interface.systray.change_status(show)
2276 def get_show(self, lcontact):
2277 prio = lcontact[0].priority
2278 show = lcontact[0].show
2279 for u in lcontact:
2280 if u.priority > prio:
2281 prio = u.priority
2282 show = u.show
2283 return show
2285 def on_message_window_delete(self, win_mgr, msg_win):
2286 if gajim.config.get('one_message_window') == 'always_with_roster':
2287 self.show_roster_vbox(True)
2288 gtkgui_helpers.resize_window(self.window,
2289 gajim.config.get('roster_width'),
2290 gajim.config.get('roster_height'))
2292 def close_all_from_dict(self, dic):
2294 Close all the windows in the given dictionary
2296 for w in dic.values():
2297 if isinstance(w, dict):
2298 self.close_all_from_dict(w)
2299 else:
2300 w.window.destroy()
2302 def close_all(self, account, force=False):
2304 Close all the windows from an account. If force is True, do not ask
2305 confirmation before closing chat/gc windows
2307 if account in gajim.interface.instances:
2308 self.close_all_from_dict(gajim.interface.instances[account])
2309 for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
2310 ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
2311 force = force)
2313 def on_roster_window_delete_event(self, widget, event):
2315 Main window X button was clicked
2317 if gajim.interface.systray_enabled and not gajim.config.get(
2318 'quit_on_roster_x_button') and gajim.config.get('trayicon') != \
2319 'on_event':
2320 self.tooltip.hide_tooltip()
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 def on_roster_treeview_button_release_event(self, widget, event):
3371 try:
3372 path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
3373 except TypeError:
3374 return False
3376 if event.button == 1: # Left click
3377 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3378 not event.state & gtk.gdk.CONTROL_MASK:
3379 # Check if button has been pressed on the same row
3380 if self.clicked_path == path:
3381 self.on_row_activated(widget, path)
3382 self.clicked_path = None
3384 def on_roster_treeview_button_press_event(self, widget, event):
3385 # hide tooltip, no matter the button is pressed
3386 self.tooltip.hide_tooltip()
3387 try:
3388 pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
3389 path, x = pos[0], pos[2]
3390 except TypeError:
3391 self.tree.get_selection().unselect_all()
3392 return False
3394 if event.button == 3: # Right click
3395 try:
3396 model, list_of_paths = self.tree.get_selection().\
3397 get_selected_rows()
3398 except TypeError:
3399 list_of_paths = []
3400 if path not in list_of_paths:
3401 self.tree.get_selection().unselect_all()
3402 self.tree.get_selection().select_path(path)
3403 return self.show_treeview_menu(event)
3405 elif event.button == 2: # Middle click
3406 try:
3407 model, list_of_paths = self.tree.get_selection().\
3408 get_selected_rows()
3409 except TypeError:
3410 list_of_paths = []
3411 if list_of_paths != [path]:
3412 self.tree.get_selection().unselect_all()
3413 self.tree.get_selection().select_path(path)
3414 type_ = model[path][C_TYPE]
3415 if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
3416 self.on_row_activated(widget, path)
3417 elif type_ == 'account':
3418 account = model[path][C_ACCOUNT].decode('utf-8')
3419 if account != 'all':
3420 show = gajim.connections[account].connected
3421 if show > 1: # We are connected
3422 self.on_change_status_message_activate(widget, account)
3423 return True
3424 show = helpers.get_global_show()
3425 if show == 'offline':
3426 return True
3427 def on_response(message, pep_dict):
3428 if message is None:
3429 return True
3430 for acct in gajim.connections:
3431 if not gajim.config.get_per('accounts', acct,
3432 'sync_with_global_status'):
3433 continue
3434 current_show = gajim.SHOW_LIST[gajim.connections[acct].\
3435 connected]
3436 self.send_status(acct, current_show, message)
3437 self.send_pep(acct, pep_dict)
3438 dialogs.ChangeStatusMessageDialog(on_response, show)
3439 return True
3441 elif event.button == 1: # Left click
3442 model = self.modelfilter
3443 type_ = model[path][C_TYPE]
3444 # x_min is the x start position of status icon column
3445 if gajim.config.get('avatar_position_in_roster') == 'left':
3446 x_min = gajim.config.get('roster_avatar_width')
3447 else:
3448 x_min = 0
3449 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3450 not event.state & gtk.gdk.CONTROL_MASK:
3451 # Don't handle double click if we press icon of a metacontact
3452 titer = model.get_iter(path)
3453 if x > x_min and x < x_min + 27 and type_ == 'contact' and \
3454 model.iter_has_child(titer):
3455 if (self.tree.row_expanded(path)):
3456 self.tree.collapse_row(path)
3457 else:
3458 self.tree.expand_row(path, False)
3459 return
3460 # We just save on which row we press button, and open chat
3461 # window on button release to be able to do DND without opening
3462 # chat window
3463 self.clicked_path = path
3464 return
3465 else:
3466 if type_ == 'group' and x < 27:
3467 # first cell in 1st column (the arrow SINGLE clicked)
3468 if (self.tree.row_expanded(path)):
3469 self.tree.collapse_row(path)
3470 else:
3471 self.expand_group_row(path)
3473 elif type_ == 'contact' and x > x_min and x < x_min + 27:
3474 if (self.tree.row_expanded(path)):
3475 self.tree.collapse_row(path)
3476 else:
3477 self.tree.expand_row(path, False)
3479 def expand_group_row(self, path):
3480 self.tree.expand_row(path, False)
3481 iter = self.modelfilter.get_iter(path)
3482 child_iter = self.modelfilter.iter_children(iter)
3483 while child_iter:
3484 type_ = self.modelfilter[child_iter][C_TYPE]
3485 account = self.modelfilter[child_iter][C_ACCOUNT]
3486 group = self.modelfilter[child_iter][C_JID]
3487 if type_ == 'group' and account + group not in self.collapsed_rows:
3488 self.expand_group_row(self.modelfilter.get_path(child_iter))
3489 child_iter = self.modelfilter.iter_next(child_iter)
3491 def on_req_usub(self, widget, list_):
3493 Remove a contact. list_ is a list of (contact, account) tuples
3495 def on_ok(is_checked, list_):
3496 remove_auth = True
3497 if len(list_) == 1:
3498 contact = list_[0][0]
3499 if contact.sub != 'to' and is_checked:
3500 remove_auth = False
3501 for (contact, account) in list_:
3502 if _('Not in Roster') not in contact.get_shown_groups():
3503 gajim.connections[account].unsubscribe(contact.jid,
3504 remove_auth)
3505 self.remove_contact(contact.jid, account, backend=True)
3506 if not remove_auth and contact.sub == 'both':
3507 contact.name = ''
3508 contact.groups = []
3509 contact.sub = 'from'
3510 # we can't see him, but have to set it manually in contact
3511 contact.show = 'offline'
3512 gajim.contacts.add_contact(account, contact)
3513 self.add_contact(contact.jid, account)
3514 def on_ok2(list_):
3515 on_ok(False, list_)
3517 if len(list_) == 1:
3518 contact = list_[0][0]
3519 pritext = _('Contact "%s" will be removed from your roster') % \
3520 contact.get_shown_name()
3521 sectext = _('You are about to remove "%(name)s" (%(jid)s) from '
3522 'your roster.\n') % {'name': contact.get_shown_name(),
3523 'jid': contact.jid}
3524 if contact.sub == 'to':
3525 dialogs.ConfirmationDialog(pritext, sectext + \
3526 _('By removing this contact you also remove authorization '
3527 'resulting in him or her always seeing you as offline.'),
3528 on_response_ok=(on_ok2, list_))
3529 elif _('Not in Roster') in contact.get_shown_groups():
3530 # Contact is not in roster
3531 dialogs.ConfirmationDialog(pritext, sectext + \
3532 _('Do you want to continue?'), on_response_ok=(on_ok2,
3533 list_))
3534 else:
3535 dialogs.ConfirmationDialogCheck(pritext, sectext + \
3536 _('By removing this contact you also by default remove '
3537 'authorization resulting in him or her always seeing you as'
3538 ' offline.'),
3539 _('I want this contact to know my status after removal'),
3540 on_response_ok=(on_ok, list_))
3541 else:
3542 # several contact to remove at the same time
3543 pritext = _('Contacts will be removed from your roster')
3544 jids = ''
3545 for (contact, account) in list_:
3546 jids += '\n ' + contact.get_shown_name() + ' (%s)' % \
3547 contact.jid + ','
3548 sectext = _('By removing these contacts:%s\nyou also remove '
3549 'authorization resulting in them always seeing you as '
3550 'offline.') % jids
3551 dialogs.ConfirmationDialog(pritext, sectext,
3552 on_response_ok=(on_ok2, list_))
3554 def on_send_custom_status(self, widget, contact_list, show, group=None):
3556 Send custom status
3558 # contact_list has only one element except if group != None
3559 def on_response(message, pep_dict):
3560 if message is None: # None if user pressed Cancel
3561 return
3562 account_list = []
3563 for (contact, account) in contact_list:
3564 if account not in account_list:
3565 account_list.append(account)
3566 # 1. update status_sent_to_[groups|users] list
3567 if group:
3568 for account in account_list:
3569 if account not in gajim.interface.status_sent_to_groups:
3570 gajim.interface.status_sent_to_groups[account] = {}
3571 gajim.interface.status_sent_to_groups[account][group] = show
3572 else:
3573 for (contact, account) in contact_list:
3574 if account not in gajim.interface.status_sent_to_users:
3575 gajim.interface.status_sent_to_users[account] = {}
3576 gajim.interface.status_sent_to_users[account][contact.jid] \
3577 = show
3579 # 2. update privacy lists if main status is invisible
3580 for account in account_list:
3581 if gajim.SHOW_LIST[gajim.connections[account].connected] == \
3582 'invisible':
3583 gajim.connections[account].set_invisible_rule()
3585 # 3. send directed presence
3586 for (contact, account) in contact_list:
3587 our_jid = gajim.get_jid_from_account(account)
3588 jid = contact.jid
3589 if jid == our_jid:
3590 jid += '/' + contact.resource
3591 self.send_status(account, show, message, to=jid)
3593 def send_it(is_checked=None):
3594 if is_checked is not None: # dialog has been shown
3595 if is_checked: # user does not want to be asked again
3596 gajim.config.set('confirm_custom_status', 'no')
3597 else:
3598 gajim.config.set('confirm_custom_status', 'yes')
3599 self.get_status_message(show, on_response, show_pep=False,
3600 always_ask=True)
3602 confirm_custom_status = gajim.config.get('confirm_custom_status')
3603 if confirm_custom_status == 'no':
3604 send_it()
3605 return
3606 pritext = _('You are about to send a custom status. Are you sure you '
3607 'want to continue?')
3608 sectext = _('This contact will temporarily see you as %(status)s, '
3609 'but only until you change your status. Then he or she will see '
3610 'your global status.') % {'status': show}
3611 dialogs.ConfirmationDialogCheck(pritext, sectext,
3612 _('_Do not ask me again'), on_response_ok=send_it)
3614 def on_status_combobox_changed(self, widget):
3616 When we change our status via the combobox
3618 model = self.status_combobox.get_model()
3619 active = self.status_combobox.get_active()
3620 if active == -1: # no active item
3621 return
3622 if not self.combobox_callback_active:
3623 self.previous_status_combobox_active = active
3624 return
3625 accounts = gajim.connections.keys()
3626 if len(accounts) == 0:
3627 dialogs.ErrorDialog(_('No account available'),
3628 _('You must create an account before you can chat with other '
3629 'contacts.'))
3630 self.update_status_combobox()
3631 return
3632 status = model[active][2].decode('utf-8')
3633 # status "desync'ed" or not
3634 statuses_unified = helpers.statuses_unified()
3635 if (active == 7 and statuses_unified) or (active == 9 and \
3636 not statuses_unified):
3637 # 'Change status message' selected:
3638 # do not change show, just show change status dialog
3639 status = model[self.previous_status_combobox_active][2].decode(
3640 'utf-8')
3641 def on_response(message, pep_dict):
3642 if message is not None: # None if user pressed Cancel
3643 for account in accounts:
3644 if not gajim.config.get_per('accounts', account,
3645 'sync_with_global_status'):
3646 continue
3647 current_show = gajim.SHOW_LIST[
3648 gajim.connections[account].connected]
3649 self.send_status(account, current_show, message)
3650 self.send_pep(account, pep_dict)
3651 self.combobox_callback_active = False
3652 self.status_combobox.set_active(
3653 self.previous_status_combobox_active)
3654 self.combobox_callback_active = True
3655 dialogs.ChangeStatusMessageDialog(on_response, status)
3656 return
3657 # we are about to change show, so save this new show so in case
3658 # after user chooses "Change status message" menuitem
3659 # we can return to this show
3660 self.previous_status_combobox_active = active
3661 connected_accounts = gajim.get_number_of_connected_accounts()
3663 def on_continue(message, pep_dict):
3664 if message is None:
3665 # user pressed Cancel to change status message dialog
3666 self.update_status_combobox()
3667 return
3668 global_sync_accounts = []
3669 for acct in accounts:
3670 if gajim.config.get_per('accounts', acct,
3671 'sync_with_global_status'):
3672 global_sync_accounts.append(acct)
3673 global_sync_connected_accounts = \
3674 gajim.get_number_of_connected_accounts(global_sync_accounts)
3675 for account in accounts:
3676 if not gajim.config.get_per('accounts', account,
3677 'sync_with_global_status'):
3678 continue
3679 # we are connected (so we wanna change show and status)
3680 # or no account is connected and we want to connect with new
3681 # show and status
3683 if not global_sync_connected_accounts > 0 or \
3684 gajim.connections[account].connected > 0:
3685 self.send_status(account, status, message)
3686 self.send_pep(account, pep_dict)
3687 self.update_status_combobox()
3689 if status == 'invisible':
3690 bug_user = False
3691 for account in accounts:
3692 if connected_accounts < 1 or gajim.account_is_connected(
3693 account):
3694 if not gajim.config.get_per('accounts', account,
3695 'sync_with_global_status'):
3696 continue
3697 # We're going to change our status to invisible
3698 if self.connected_rooms(account):
3699 bug_user = True
3700 break
3701 if bug_user:
3702 def on_ok():
3703 self.get_status_message(status, on_continue, show_pep=False)
3705 def on_cancel():
3706 self.update_status_combobox()
3708 dialogs.ConfirmationDialog(
3709 _('You are participating in one or more group chats'),
3710 _('Changing your status to invisible will result in '
3711 'disconnection from those group chats. Are you sure you '
3712 'want to go invisible?'), on_reponse_ok=on_ok,
3713 on_response_cancel=on_cancel)
3714 return
3716 self.get_status_message(status, on_continue)
3718 def on_preferences_menuitem_activate(self, widget):
3719 if 'preferences' in gajim.interface.instances:
3720 gajim.interface.instances['preferences'].window.present()
3721 else:
3722 gajim.interface.instances['preferences'] = config.PreferencesWindow(
3725 def on_plugins_menuitem_activate(self, widget):
3726 if gajim.interface.instances.has_key('plugins'):
3727 gajim.interface.instances['plugins'].window.present()
3728 else:
3729 gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow()
3731 def on_publish_tune_toggled(self, widget, account):
3732 active = widget.get_active()
3733 gajim.config.set_per('accounts', account, 'publish_tune', active)
3734 if active:
3735 gajim.interface.enable_music_listener()
3736 else:
3737 gajim.connections[account].retract_tune()
3738 # disable music listener only if no other account uses it
3739 for acc in gajim.connections:
3740 if gajim.config.get_per('accounts', acc, 'publish_tune'):
3741 break
3742 else:
3743 gajim.interface.disable_music_listener()
3745 helpers.update_optional_features(account)
3747 def on_publish_location_toggled(self, widget, account):
3748 active = widget.get_active()
3749 gajim.config.set_per('accounts', account, 'publish_location', active)
3750 if active:
3751 location_listener.enable()
3752 else:
3753 gajim.connections[account].retract_location()
3754 # disable music listener only if no other account uses it
3755 for acc in gajim.connections:
3756 if gajim.config.get_per('accounts', acc, 'publish_location'):
3757 break
3758 else:
3759 location_listener.disable()
3761 helpers.update_optional_features(account)
3763 def on_pep_services_menuitem_activate(self, widget, account):
3764 if 'pep_services' in gajim.interface.instances[account]:
3765 gajim.interface.instances[account]['pep_services'].window.present()
3766 else:
3767 gajim.interface.instances[account]['pep_services'] = \
3768 config.ManagePEPServicesWindow(account)
3770 def on_add_new_contact(self, widget, account):
3771 dialogs.AddNewContactWindow(account)
3773 def on_join_gc_activate(self, widget, account):
3775 When the join gc menuitem is clicked, show the join gc window
3777 invisible_show = gajim.SHOW_LIST.index('invisible')
3778 if gajim.connections[account].connected == invisible_show:
3779 dialogs.ErrorDialog(_('You cannot join a group chat while you are '
3780 'invisible'))
3781 return
3782 if 'join_gc' in gajim.interface.instances[account]:
3783 gajim.interface.instances[account]['join_gc'].window.present()
3784 else:
3785 try:
3786 gajim.interface.instances[account]['join_gc'] = \
3787 dialogs.JoinGroupchatWindow(account)
3788 except GajimGeneralException:
3789 pass
3791 def on_new_chat_menuitem_activate(self, widget, account):
3792 dialogs.NewChatDialog(account)
3794 def on_contents_menuitem_activate(self, widget):
3795 helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
3797 def on_faq_menuitem_activate(self, widget):
3798 helpers.launch_browser_mailer('url',
3799 'http://trac.gajim.org/wiki/GajimFaq')
3801 def on_features_menuitem_activate(self, widget):
3802 features_window.FeaturesWindow()
3804 def on_about_menuitem_activate(self, widget):
3805 dialogs.AboutDialog()
3807 def on_accounts_menuitem_activate(self, widget):
3808 if 'accounts' in gajim.interface.instances:
3809 gajim.interface.instances['accounts'].window.present()
3810 else:
3811 gajim.interface.instances['accounts'] = config.AccountsWindow()
3813 def on_file_transfers_menuitem_activate(self, widget):
3814 if gajim.interface.instances['file_transfers'].window.get_property(
3815 'visible'):
3816 gajim.interface.instances['file_transfers'].window.present()
3817 else:
3818 gajim.interface.instances['file_transfers'].window.show_all()
3820 def on_history_menuitem_activate(self, widget):
3821 if 'logs' in gajim.interface.instances:
3822 gajim.interface.instances['logs'].window.present()
3823 else:
3824 gajim.interface.instances['logs'] = history_window.\
3825 HistoryWindow()
3827 def on_show_transports_menuitem_activate(self, widget):
3828 gajim.config.set('show_transports_group', widget.get_active())
3829 self.refilter_shown_roster_items()
3831 def on_manage_bookmarks_menuitem_activate(self, widget):
3832 config.ManageBookmarksWindow()
3834 def on_profile_avatar_menuitem_activate(self, widget, account):
3835 gajim.interface.edit_own_details(account)
3837 def on_execute_command(self, widget, contact, account, resource=None):
3839 Execute command. Full JID needed; if it is other contact, resource is
3840 necessary. Widget is unnecessary, only to be able to make this a
3841 callback
3843 jid = contact.jid
3844 if resource is not None:
3845 jid = jid + u'/' + resource
3846 adhoc_commands.CommandWindow(account, jid)
3848 def on_roster_window_focus_in_event(self, widget, event):
3849 # roster received focus, so if we had urgency REMOVE IT
3850 # NOTE: we do not have to read the message to remove urgency
3851 # so this functions does that
3852 gtkgui_helpers.set_unset_urgency_hint(widget, False)
3854 # if a contact row is selected, update colors (eg. for status msg)
3855 # because gtk engines may differ in bg when window is selected
3856 # or not
3857 if len(self._last_selected_contact):
3858 for (jid, account) in self._last_selected_contact:
3859 self.draw_contact(jid, account, selected=True, focus=True)
3861 def on_roster_window_focus_out_event(self, widget, event):
3862 # if a contact row is selected, update colors (eg. for status msg)
3863 # because gtk engines may differ in bg when window is selected
3864 # or not
3865 if len(self._last_selected_contact):
3866 for (jid, account) in self._last_selected_contact:
3867 self.draw_contact(jid, account, selected=True, focus=False)
3869 def on_roster_window_key_press_event(self, widget, event):
3870 if event.keyval == gtk.keysyms.Escape:
3871 if gajim.interface.msg_win_mgr.mode == \
3872 MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
3873 gajim.interface.msg_win_mgr.one_window_opened():
3874 # let message window close the tab
3875 return
3876 list_of_paths = self.tree.get_selection().get_selected_rows()[1]
3877 if not len(list_of_paths) and gajim.interface.systray_enabled and \
3878 not gajim.config.get('quit_on_roster_x_button'):
3879 self.tooltip.hide_tooltip()
3880 self.window.hide()
3881 elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \
3882 gtk.keysyms.i:
3883 treeselection = self.tree.get_selection()
3884 model, list_of_paths = treeselection.get_selected_rows()
3885 for path in list_of_paths:
3886 type_ = model[path][C_TYPE]
3887 if type_ in ('contact', 'agent'):
3888 jid = model[path][C_JID].decode('utf-8')
3889 account = model[path][C_ACCOUNT].decode('utf-8')
3890 contact = gajim.contacts.get_first_contact_from_jid(account,
3891 jid)
3892 self.on_info(widget, contact, account)
3893 elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \
3894 gtk.keysyms.h:
3895 treeselection = self.tree.get_selection()
3896 model, list_of_paths = treeselection.get_selected_rows()
3897 if len(list_of_paths) != 1:
3898 return
3899 path = list_of_paths[0]
3900 type_ = model[path][C_TYPE]
3901 if type_ in ('contact', 'agent'):
3902 jid = model[path][C_JID].decode('utf-8')
3903 account = model[path][C_ACCOUNT].decode('utf-8')
3904 contact = gajim.contacts.get_first_contact_from_jid(account,
3905 jid)
3906 self.on_history(widget, contact, account)
3908 def on_roster_window_popup_menu(self, widget):
3909 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
3910 self.show_treeview_menu(event)
3912 def on_row_activated(self, widget, path):
3914 When an iter is activated (double-click or single click if gnome is set
3915 this way)
3917 model = self.modelfilter
3918 account = model[path][C_ACCOUNT].decode('utf-8')
3919 type_ = model[path][C_TYPE]
3920 if type_ in ('group', 'account'):
3921 if self.tree.row_expanded(path):
3922 self.tree.collapse_row(path)
3923 else:
3924 self.tree.expand_row(path, False)
3925 return
3926 jid = model[path][C_JID].decode('utf-8')
3927 resource = None
3928 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
3929 titer = model.get_iter(path)
3930 if contact.is_groupchat():
3931 first_ev = gajim.events.get_first_event(account, jid)
3932 if first_ev and self.open_event(account, jid, first_ev):
3933 # We are invited to a GC
3934 # open event cares about connecting to it
3935 self.remove_groupchat(jid, account)
3936 else:
3937 self.on_groupchat_maximized(None, jid, account)
3938 return
3940 # else
3941 first_ev = gajim.events.get_first_event(account, jid)
3942 if not first_ev:
3943 # look in other resources
3944 for c in gajim.contacts.get_contacts(account, jid):
3945 fjid = c.get_full_jid()
3946 first_ev = gajim.events.get_first_event(account, fjid)
3947 if first_ev:
3948 resource = c.resource
3949 break
3950 if not first_ev and model.iter_has_child(titer):
3951 child_iter = model.iter_children(titer)
3952 while not first_ev and child_iter:
3953 child_jid = model[child_iter][C_JID].decode('utf-8')
3954 first_ev = gajim.events.get_first_event(account, child_jid)
3955 if first_ev:
3956 jid = child_jid
3957 else:
3958 child_iter = model.iter_next(child_iter)
3959 session = None
3960 if first_ev:
3961 if first_ev.type_ in ('chat', 'normal'):
3962 session = first_ev.parameters[8]
3963 fjid = jid
3964 if resource:
3965 fjid += '/' + resource
3966 if self.open_event(account, fjid, first_ev):
3967 return
3968 # else
3969 contact = gajim.contacts.get_contact(account, jid, resource)
3970 if not contact or isinstance(contact, list):
3971 contact = gajim.contacts.get_contact_with_highest_priority(account,
3972 jid)
3973 if jid == gajim.get_jid_from_account(account):
3974 resource = contact.resource
3976 gajim.interface.on_open_chat_window(None, contact, account, \
3977 resource=resource, session=session)
3979 def on_roster_treeview_row_activated(self, widget, path, col=0):
3981 When an iter is double clicked: open the first event window
3983 if not gajim.single_click:
3984 self.on_row_activated(widget, path)
3986 def on_roster_treeview_row_expanded(self, widget, titer, path):
3988 When a row is expanded change the icon of the arrow
3990 self._toggeling_row = True
3991 model = widget.get_model()
3992 child_model = model.get_model()
3993 child_iter = model.convert_iter_to_child_iter(titer)
3995 if self.regroup: # merged accounts
3996 accounts = gajim.connections.keys()
3997 else:
3998 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
4000 type_ = model[titer][C_TYPE]
4001 if type_ == 'group':
4002 group = model[titer][C_JID].decode('utf-8')
4003 child_model[child_iter][C_IMG] = \
4004 gajim.interface.jabber_state_images['16']['opened']
4005 for account in accounts:
4006 if group in gajim.groups[account]: # This account has this group
4007 gajim.groups[account][group]['expand'] = True
4008 if account + group in self.collapsed_rows:
4009 self.collapsed_rows.remove(account + group)
4010 for contact in gajim.contacts.iter_contacts(account):
4011 jid = contact.jid
4012 if group in contact.groups and \
4013 gajim.contacts.is_big_brother(account, jid, accounts) and \
4014 account + group + jid not in self.collapsed_rows:
4015 titers = self._get_contact_iter(jid, account)
4016 for titer in titers:
4017 path = model.get_path(titer)
4018 self.tree.expand_row(path, False)
4019 elif type_ == 'account':
4020 account = accounts[0] # There is only one cause we don't use merge
4021 if account in self.collapsed_rows:
4022 self.collapsed_rows.remove(account)
4023 self.draw_account(account)
4024 # When we expand, groups are collapsed. Restore expand state
4025 for group in gajim.groups[account]:
4026 if gajim.groups[account][group]['expand']:
4027 titer = self._get_group_iter(group, account)
4028 if titer:
4029 path = model.get_path(titer)
4030 self.tree.expand_row(path, False)
4031 elif type_ == 'contact':
4032 # Metacontact got toggled, update icon
4033 jid = model[titer][C_JID].decode('utf-8')
4034 account = model[titer][C_ACCOUNT].decode('utf-8')
4035 contact = gajim.contacts.get_contact(account, jid)
4036 for group in contact.groups:
4037 if account + group + jid in self.collapsed_rows:
4038 self.collapsed_rows.remove(account + group + jid)
4039 family = gajim.contacts.get_metacontacts_family(account, jid)
4040 nearby_family = \
4041 self._get_nearby_family_and_big_brother(family, account)[0]
4042 # Redraw all brothers to show pending events
4043 for data in nearby_family:
4044 self.draw_contact(data['jid'], data['account'])
4046 self._toggeling_row = False
4048 def on_roster_treeview_row_collapsed(self, widget, titer, path):
4050 When a row is collapsed change the icon of the arrow
4052 self._toggeling_row = True
4053 model = widget.get_model()
4054 child_model = model.get_model()
4055 child_iter = model.convert_iter_to_child_iter(titer)
4057 if self.regroup: # merged accounts
4058 accounts = gajim.connections.keys()
4059 else:
4060 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
4062 type_ = model[titer][C_TYPE]
4063 if type_ == 'group':
4064 child_model[child_iter][C_IMG] = gajim.interface.\
4065 jabber_state_images['16']['closed']
4066 group = model[titer][C_JID].decode('utf-8')
4067 for account in accounts:
4068 if group in gajim.groups[account]: # This account has this group
4069 gajim.groups[account][group]['expand'] = False
4070 if account + group not in self.collapsed_rows:
4071 self.collapsed_rows.append(account + group)
4072 elif type_ == 'account':
4073 account = accounts[0] # There is only one cause we don't use merge
4074 if account not in self.collapsed_rows:
4075 self.collapsed_rows.append(account)
4076 self.draw_account(account)
4077 elif type_ == 'contact':
4078 # Metacontact got toggled, update icon
4079 jid = model[titer][C_JID].decode('utf-8')
4080 account = model[titer][C_ACCOUNT].decode('utf-8')
4081 contact = gajim.contacts.get_contact(account, jid)
4082 for group in contact.groups:
4083 if account + group + jid not in self.collapsed_rows:
4084 self.collapsed_rows.append(account + group + jid)
4085 family = gajim.contacts.get_metacontacts_family(account, jid)
4086 nearby_family = \
4087 self._get_nearby_family_and_big_brother(family, account)[0]
4088 # Redraw all brothers to show pending events
4089 for data in nearby_family:
4090 self.draw_contact(data['jid'], data['account'])
4092 self._toggeling_row = False
4094 def on_modelfilter_row_has_child_toggled(self, model, path, titer):
4096 Called when a row has gotten the first or lost its last child row
4098 Expand Parent if necessary.
4100 if self._toggeling_row:
4101 # Signal is emitted when we write to our model
4102 return
4104 type_ = model[titer][C_TYPE]
4105 account = model[titer][C_ACCOUNT]
4106 if not account:
4107 return
4109 account = account.decode('utf-8')
4111 if type_ == 'contact':
4112 child_iter = model.convert_iter_to_child_iter(titer)
4113 if self.model.iter_has_child(child_iter):
4114 # we are a bigbrother metacontact
4115 # redraw us to show/hide expand icon
4116 if self.filtering:
4117 # Prevent endless loops
4118 jid = model[titer][C_JID].decode('utf-8')
4119 gobject.idle_add(self.draw_contact, jid, account)
4120 elif type_ == 'group':
4121 group = model[titer][C_JID].decode('utf-8')
4122 self._adjust_group_expand_collapse_state(group, account)
4123 elif type_ == 'account':
4124 self._adjust_account_expand_collapse_state(account)
4126 # Selection can change when the model is filtered
4127 # Only write to the model when filtering is finished!
4129 # FIXME: When we are filtering our custom colors are somehow lost
4131 # def on_treeview_selection_changed(self, selection):
4132 # '''Called when selection in TreeView has changed.
4134 # Redraw unselected rows to make status message readable
4135 # on all possible backgrounds.
4136 # '''
4137 # model, list_of_paths = selection.get_selected_rows()
4138 # if len(self._last_selected_contact):
4139 # # update unselected rows
4140 # for (jid, account) in self._last_selected_contact:
4141 # gobject.idle_add(self.draw_contact, jid,
4142 # account)
4143 # self._last_selected_contact = []
4144 # if len(list_of_paths) == 0:
4145 # return
4146 # for path in list_of_paths:
4147 # row = model[path]
4148 # if row[C_TYPE] != 'contact':
4149 # self._last_selected_contact = []
4150 # return
4151 # jid = row[C_JID].decode('utf-8')
4152 # account = row[C_ACCOUNT].decode('utf-8')
4153 # self._last_selected_contact.append((jid, account))
4154 # gobject.idle_add(self.draw_contact, jid, account, True)
4156 def on_service_disco_menuitem_activate(self, widget, account):
4157 server_jid = gajim.config.get_per('accounts', account, 'hostname')
4158 if server_jid in gajim.interface.instances[account]['disco']:
4159 gajim.interface.instances[account]['disco'][server_jid].\
4160 window.present()
4161 else:
4162 try:
4163 # Object will add itself to the window dict
4164 disco.ServiceDiscoveryWindow(account, address_entry=True)
4165 except GajimGeneralException:
4166 pass
4168 def on_show_offline_contacts_menuitem_activate(self, widget):
4170 When show offline option is changed: redraw the treeview
4172 gajim.config.set('showoffline', not gajim.config.get('showoffline'))
4173 self.refilter_shown_roster_items()
4174 w = self.xml.get_object('show_only_active_contacts_menuitem')
4175 if gajim.config.get('showoffline'):
4176 # We need to filter twice to show groups with no contacts inside
4177 # in the correct expand state
4178 self.refilter_shown_roster_items()
4179 w.set_sensitive(False)
4180 else:
4181 w.set_sensitive(True)
4183 def on_show_only_active_contacts_menuitem_activate(self, widget):
4185 When show only active contact option is changed: redraw the treeview
4187 gajim.config.set('show_only_chat_and_online', not gajim.config.get(
4188 'show_only_chat_and_online'))
4189 self.refilter_shown_roster_items()
4190 w = self.xml.get_object('show_offline_contacts_menuitem')
4191 if gajim.config.get('show_only_chat_and_online'):
4192 # We need to filter twice to show groups with no contacts inside
4193 # in the correct expand state
4194 self.refilter_shown_roster_items()
4195 w.set_sensitive(False)
4196 else:
4197 w.set_sensitive(True)
4199 def on_view_menu_activate(self, widget):
4200 # Hide the show roster menu if we are not in the right windowing mode.
4201 if self.hpaned.get_child2() is not None:
4202 self.xml.get_object('show_roster_menuitem').show()
4203 else:
4204 self.xml.get_object('show_roster_menuitem').hide()
4206 def on_show_roster_menuitem_toggled(self, widget):
4207 # when num controls is 0 this menuitem is hidden, but still need to
4208 # disable keybinding
4209 if self.hpaned.get_child2() is not None:
4210 self.show_roster_vbox(widget.get_active())
4212 def on_rfilter_entry_changed(self, widget):
4213 """ When we update the content of the filter """
4214 self.rfilter_string = widget.get_text().lower()
4215 if self.rfilter_string == '':
4216 self.rfilter_enabled = False
4217 else:
4218 self.rfilter_enabled = True
4219 self.refilter_shown_roster_items()
4221 def on_rfilter_entry_icon_press(self, widget, icon, event):
4222 """ Disable the roster filtering by clicking the icon in the textEntry """
4223 self.xml.get_object('show_rfilter_menuitem').set_active(False)
4224 self.rfilter_enabled = False
4225 self.refilter_shown_roster_items()
4227 def on_show_rfilter_menuitem_toggled(self, widget):
4228 """ Show the roster filter entry """
4229 self.rfilter_enabled = widget.get_active()
4230 self.rfilter_entry.set_visible(self.rfilter_enabled)
4231 self.rfilter_entry.set_editable(self.rfilter_enabled)
4232 if self.rfilter_enabled:
4233 self.rfilter_entry.set_text('')
4234 self.rfilter_entry.grab_focus()
4236 def on_roster_hpaned_notify(self, pane, gparamspec):
4238 Keep changing the width of the roster
4239 (when a gtk.Paned widget handle is dragged)
4241 if gparamspec.name == 'position':
4242 roster_width = pane.get_child1().allocation.width
4243 gajim.config.set('roster_width', roster_width)
4245 ################################################################################
4246 ### Drag and Drop handling
4247 ################################################################################
4249 def drag_data_get_data(self, treeview, context, selection, target_id,
4250 etime):
4251 model, list_of_paths = self.tree.get_selection().get_selected_rows()
4252 if len(list_of_paths) != 1:
4253 return
4254 path = list_of_paths[0]
4255 data = ''
4256 if len(path) >= 2:
4257 data = model[path][C_JID]
4258 selection.set(selection.target, 8, data)
4260 def drag_begin(self, treeview, context):
4261 self.dragging = True
4263 def drag_end(self, treeview, context):
4264 self.dragging = False
4266 def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
4267 c_dest, was_big_brother, context, etime):
4268 gajim.connections[account_dest].send_contacts([c_source], c_dest.jid)
4270 def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
4271 c_dest, was_big_brother, context, etime):
4273 if not gajim.connections[account_source].private_storage_supported or \
4274 not gajim.connections[account_dest].private_storage_supported:
4275 dialogs.WarningDialog(_('Metacontacts storage not supported by '
4276 'your server'),
4277 _('Your server does not support storing metacontacts '
4278 'information. So those information will not be saved on next '
4279 'reconnection.'))
4281 def merge_contacts(is_checked=None):
4282 contacts = 0
4283 if is_checked is not None: # dialog has been shown
4284 if is_checked: # user does not want to be asked again
4285 gajim.config.set('confirm_metacontacts', 'no')
4286 else:
4287 gajim.config.set('confirm_metacontacts', 'yes')
4289 # We might have dropped on a metacontact.
4290 # Remove it and readd later with updated family info
4291 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
4292 c_dest.jid)
4293 if dest_family:
4294 self._remove_metacontact_family(dest_family, account_dest)
4295 source_family = gajim.contacts.get_metacontacts_family(
4296 account_source, c_source.jid)
4297 if dest_family == source_family:
4298 n = contacts = len(dest_family)
4299 for tag in source_family:
4300 if tag['jid'] == c_source.jid:
4301 tag['order'] = contacts
4302 continue
4303 if 'order' in tag:
4304 n -= 1
4305 tag['order'] = n
4306 else:
4307 self._remove_entity(c_dest, account_dest)
4309 old_family = gajim.contacts.get_metacontacts_family(account_source,
4310 c_source.jid)
4311 old_groups = c_source.groups
4313 # Remove old source contact(s)
4314 if was_big_brother:
4315 # We have got little brothers. Readd them all
4316 self._remove_metacontact_family(old_family, account_source)
4317 else:
4318 # We are only a litle brother. Simply remove us from our big
4319 # brother
4320 if self._get_contact_iter(c_source.jid, account_source):
4321 # When we have been in the group before.
4322 # Do not try to remove us again
4323 self._remove_entity(c_source, account_source)
4325 own_data = {}
4326 own_data['jid'] = c_source.jid
4327 own_data['account'] = account_source
4328 # Don't touch the rest of the family
4329 old_family = [own_data]
4331 # Apply new tag and update contact
4332 for data in old_family:
4333 if account_source != data['account'] and not self.regroup:
4334 continue
4336 _account = data['account']
4337 _jid = data['jid']
4338 _contact = gajim.contacts.get_first_contact_from_jid(_account,
4339 _jid)
4340 if not _contact:
4341 # One of the metacontacts may be not connected.
4342 continue
4344 _contact.groups = c_dest.groups[:]
4345 gajim.contacts.add_metacontact(account_dest, c_dest.jid,
4346 _account, _contact.jid, contacts)
4347 gajim.connections[account_source].update_contact(_contact.jid,
4348 _contact.name, _contact.groups)
4350 # Re-add all and update GUI
4351 new_family = gajim.contacts.get_metacontacts_family(account_source,
4352 c_source.jid)
4353 brothers = self._add_metacontact_family(new_family, account_source)
4355 for c, acc in brothers:
4356 self.draw_completely(c.jid, acc)
4358 old_groups.extend(c_dest.groups)
4359 for g in old_groups:
4360 self.draw_group(g, account_source)
4362 self.draw_account(account_source)
4363 context.finish(True, True, etime)
4365 confirm_metacontacts = gajim.config.get('confirm_metacontacts')
4366 if confirm_metacontacts == 'no':
4367 merge_contacts()
4368 return
4369 pritext = _('You are about to create a metacontact. Are you sure you '
4370 'want to continue?')
4371 sectext = _('Metacontacts are a way to regroup several contacts in one '
4372 'line. Generally it is used when the same person has several '
4373 'Jabber accounts or transport accounts.')
4374 dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
4375 _('_Do not ask me again'), on_response_ok=merge_contacts)
4376 if not confirm_metacontacts: # First time we see this window
4377 dlg.checkbutton.set_active(True)
4379 def on_drop_in_group(self, widget, account, c_source, grp_dest,
4380 is_big_brother, context, etime, grp_source = None):
4381 if is_big_brother:
4382 # add whole metacontact to new group
4383 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4384 # remove afterwards so the contact is not moved to General in the
4385 # meantime
4386 if grp_dest != grp_source:
4387 self.remove_contact_from_groups(c_source.jid, account,
4388 [grp_source])
4389 else:
4390 # Normal contact or little brother
4391 family = gajim.contacts.get_metacontacts_family(account,
4392 c_source.jid)
4393 if family:
4394 # Little brother
4395 # Remove whole family. Remove us from the family.
4396 # Then re-add other family members.
4397 self._remove_metacontact_family(family, account)
4398 gajim.contacts.remove_metacontact(account, c_source.jid)
4399 for data in family:
4400 if account != data['account'] and not self.regroup:
4401 continue
4402 if data['jid'] == c_source.jid and\
4403 data['account'] == account:
4404 continue
4405 self.add_contact(data['jid'], data['account'])
4406 break
4408 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4410 else:
4411 # Normal contact
4412 self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
4413 # remove afterwards so the contact is not moved to General in
4414 # the meantime
4415 if grp_dest != grp_source:
4416 self.remove_contact_from_groups(c_source.jid, account,
4417 [grp_source])
4419 if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY):
4420 context.finish(True, True, etime)
4422 def drag_drop(self, treeview, context, x, y, timestamp):
4423 target_list = treeview.drag_dest_get_target_list()
4424 target = treeview.drag_dest_find_target(context, target_list)
4425 treeview.drag_get_data(context, target)
4426 context.finish(False, True)
4427 return True
4429 def move_group(self, old_name, new_name, account):
4430 for group in gajim.groups[account].keys():
4431 if group.startswith(old_name):
4432 self.rename_group(group, group.replace(old_name, new_name),
4433 account)
4435 def drag_data_received_data(self, treeview, context, x, y, selection, info,
4436 etime):
4437 treeview.stop_emission('drag_data_received')
4438 drop_info = treeview.get_dest_row_at_pos(x, y)
4439 if not drop_info:
4440 return
4441 if not selection.data:
4442 return # prevents tb when several entrys are dragged
4443 model = treeview.get_model()
4444 data = selection.data
4445 path_dest, position = drop_info
4447 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \
4448 and path_dest[1] == 0: # dropped before the first group
4449 return
4450 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
4451 # dropped before a group: we drop it in the previous group every
4452 # time
4453 path_dest = (path_dest[0], path_dest[1]-1)
4454 # destination: the row something got dropped on
4455 iter_dest = model.get_iter(path_dest)
4456 type_dest = model[iter_dest][C_TYPE].decode('utf-8')
4457 jid_dest = model[iter_dest][C_JID].decode('utf-8')
4458 account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
4460 # drop on account row in merged mode, we cannot know the desired account
4461 if account_dest == 'all':
4462 return
4463 # nothing can be done, if destination account is offline
4464 if gajim.connections[account_dest].connected < 2:
4465 return
4467 # A file got dropped on the roster
4468 if info == self.TARGET_TYPE_URI_LIST:
4469 if len(path_dest) < 3:
4470 return
4471 if type_dest != 'contact':
4472 return
4473 c_dest = gajim.contacts.get_contact_with_highest_priority(
4474 account_dest, jid_dest)
4475 if not c_dest.supports(NS_FILE):
4476 return
4477 uri = data.strip()
4478 uri_splitted = uri.split() # we may have more than one file dropped
4479 try:
4480 # This is always the last element in windows
4481 uri_splitted.remove('\0')
4482 except ValueError:
4483 pass
4484 nb_uri = len(uri_splitted)
4485 # Check the URIs
4486 bad_uris = []
4487 for a_uri in uri_splitted:
4488 path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
4489 if not os.path.isfile(path):
4490 bad_uris.append(a_uri)
4491 if len(bad_uris):
4492 dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
4493 return
4494 def _on_send_files(account, jid, uris):
4495 c = gajim.contacts.get_contact_with_highest_priority(account,
4496 jid)
4497 for uri in uris:
4498 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
4499 if os.path.isfile(path): # is it file?
4500 gajim.interface.instances['file_transfers'].send_file(
4501 account, c, path)
4502 # Popup dialog to confirm sending
4503 prim_text = 'Send file?'
4504 sec_text = i18n.ngettext('Do you want to send this file to %s:',
4505 'Do you want to send these files to %s:', nb_uri) %\
4506 c_dest.get_shown_name()
4507 for uri in uri_splitted:
4508 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
4509 sec_text += '\n' + os.path.basename(path)
4510 dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
4511 on_response_ok=(_on_send_files, account_dest, jid_dest,
4512 uri_splitted))
4513 dialog.popup()
4514 return
4516 # a roster entry was dragged and dropped somewhere in the roster
4518 # source: the row that was dragged
4519 path_source = treeview.get_selection().get_selected_rows()[1][0]
4520 iter_source = model.get_iter(path_source)
4521 type_source = model[iter_source][C_TYPE]
4522 account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
4524 if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
4525 return
4527 if type_dest == 'self_contact':
4528 # drop on self contact row
4529 return
4531 if type_dest == 'groupchat':
4532 # drop on a minimized groupchat
4533 # TODO: Invite to groupchat if type_dest = contact
4534 return
4536 if type_source == 'group':
4537 if account_source != account_dest:
4538 # drop on another account
4539 return
4540 grp_source = model[iter_source][C_JID].decode('utf-8')
4541 delimiter = gajim.connections[account_source].nested_group_delimiter
4542 grp_source_list = grp_source.split(delimiter)
4543 new_grp = None
4544 if type_dest == 'account':
4545 new_grp = grp_source_list[-1]
4546 elif type_dest == 'group':
4547 new_grp = model[iter_dest][C_JID].decode('utf-8') + delimiter +\
4548 grp_source_list[-1]
4549 if new_grp:
4550 self.move_group(grp_source, new_grp, account_source)
4552 # Only normal contacts and group can be dragged
4553 if type_source != 'contact':
4554 return
4556 # A contact was dropped
4557 if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
4558 # drop on zeroconf account, adding not possible
4559 return
4561 if type_dest == 'account' and account_source == account_dest:
4562 # drop on the account it was dragged from
4563 return
4565 # Get valid source group, jid and contact
4566 it = iter_source
4567 while model[it][C_TYPE] == 'contact':
4568 it = model.iter_parent(it)
4569 grp_source = model[it][C_JID].decode('utf-8')
4570 if grp_source in helpers.special_groups and \
4571 grp_source not in ('Not in Roster', 'Observers'):
4572 # a transport or a minimized groupchat was dragged
4573 # we can add it to other accounts but not move it to another group,
4574 # see below
4575 return
4576 jid_source = data.decode('utf-8')
4577 c_source = gajim.contacts.get_contact_with_highest_priority(
4578 account_source, jid_source)
4580 # Get destination group
4581 grp_dest = None
4582 if type_dest == 'group':
4583 grp_dest = model[iter_dest][C_JID].decode('utf-8')
4584 elif type_dest in ('contact', 'agent'):
4585 it = iter_dest
4586 while model[it][C_TYPE] != 'group':
4587 it = model.iter_parent(it)
4588 grp_dest = model[it][C_JID].decode('utf-8')
4589 if grp_dest in helpers.special_groups:
4590 return
4592 if jid_source == jid_dest:
4593 if grp_source == grp_dest and account_source == account_dest:
4594 # Drop on self
4595 return
4597 # contact drop somewhere in or on a foreign account
4598 if (type_dest == 'account' or not self.regroup) and \
4599 account_source != account_dest:
4600 # add to account in specified group
4601 dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
4602 user_nick=c_source.name, group=grp_dest)
4603 return
4605 # we may not add contacts from special_groups
4606 if grp_source in helpers.special_groups :
4607 return
4609 # Is the contact we drag a meta contact?
4610 accounts = (self.regroup and gajim.contacts.get_accounts()) or \
4611 account_source
4612 is_big_brother = gajim.contacts.is_big_brother(account_source,
4613 jid_source, accounts)
4615 drop_in_middle_of_meta = False
4616 if type_dest == 'contact':
4617 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4:
4618 drop_in_middle_of_meta = True
4619 if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or\
4620 self.modelfilter.iter_has_child(iter_dest)):
4621 drop_in_middle_of_meta = True
4622 # Contact drop on group row or between two contacts that are
4623 # not metacontacts
4624 if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE,
4625 gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta:
4626 self.on_drop_in_group(None, account_source, c_source, grp_dest,
4627 is_big_brother, context, etime, grp_source)
4628 return
4630 # Contact drop on another contact, make meta contacts
4631 if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \
4632 position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta:
4633 c_dest = gajim.contacts.get_contact_with_highest_priority(
4634 account_dest, jid_dest)
4635 if not c_dest:
4636 # c_dest is None if jid_dest doesn't belong to account
4637 return
4638 menu = gtk.Menu()
4639 item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(),
4640 c_dest.get_shown_name()))
4641 item.connect('activate', self.on_drop_rosterx, account_source,
4642 c_source, account_dest, c_dest, is_big_brother, context, etime)
4643 menu.append(item)
4645 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
4646 c_dest.jid)
4647 source_family = gajim.contacts.get_metacontacts_family(
4648 account_source, c_source.jid)
4649 if dest_family == source_family:
4650 item = gtk.MenuItem(_('Make %s first contact') % (
4651 c_source.get_shown_name()))
4652 else:
4653 item = gtk.MenuItem(_('Make %s and %s metacontacts') % (
4654 c_source.get_shown_name(), c_dest.get_shown_name()))
4656 item.connect('activate', self.on_drop_in_contact, account_source,
4657 c_source, account_dest, c_dest, is_big_brother, context, etime)
4659 menu.append(item)
4661 menu.attach_to_widget(self.tree, None)
4662 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
4663 menu.show_all()
4664 menu.popup(None, None, None, 1, etime)
4666 ################################################################################
4667 ### Everything about images and icons....
4668 ### Cleanup assigned to Jim++ :-)
4669 ################################################################################
4671 def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
4673 Check jid and return the appropriate state images dict for the demanded
4674 size. icon_name is taken into account when jid is from transport:
4675 transport iconset doesn't contain all icons, so we fall back to jabber
4678 transport = gajim.get_transport_name_from_jid(jid)
4679 if transport and size in self.transports_state_images:
4680 if transport not in self.transports_state_images[size]:
4681 # we don't have iconset for this transport loaded yet. Let's do
4682 # it
4683 self.make_transport_state_images(transport)
4684 if transport in self.transports_state_images[size] and \
4685 icon_name in self.transports_state_images[size][transport]:
4686 return self.transports_state_images[size][transport]
4687 return gajim.interface.jabber_state_images[size]
4689 def make_transport_state_images(self, transport):
4691 Initialize opened and closed 'transport' iconset dict
4693 if gajim.config.get('use_transports_iconsets'):
4694 folder = os.path.join(helpers.get_transport_path(transport),
4695 '16x16')
4696 pixo, pixc = gtkgui_helpers.load_icons_meta()
4697 self.transports_state_images['opened'][transport] = \
4698 gtkgui_helpers.load_iconset(folder, pixo, transport=True)
4699 self.transports_state_images['closed'][transport] = \
4700 gtkgui_helpers.load_iconset(folder, pixc, transport=True)
4701 folder = os.path.join(helpers.get_transport_path(transport),
4702 '32x32')
4703 self.transports_state_images['32'][transport] = \
4704 gtkgui_helpers.load_iconset(folder, transport=True)
4705 folder = os.path.join(helpers.get_transport_path(transport),
4706 '16x16')
4707 self.transports_state_images['16'][transport] = \
4708 gtkgui_helpers.load_iconset(folder, transport=True)
4710 def update_jabber_state_images(self):
4711 # Update the roster
4712 self.setup_and_draw_roster()
4713 # Update the status combobox
4714 model = self.status_combobox.get_model()
4715 titer = model.get_iter_root()
4716 while titer:
4717 if model[titer][2] != '':
4718 # If it's not change status message iter
4719 # eg. if it has show parameter not ''
4720 model[titer][1] = gajim.interface.jabber_state_images['16'][
4721 model[titer][2]]
4722 titer = model.iter_next(titer)
4723 # Update the systray
4724 if gajim.interface.systray_enabled:
4725 gajim.interface.systray.set_img()
4727 for win in gajim.interface.msg_win_mgr.windows():
4728 for ctrl in win.controls():
4729 ctrl.update_ui()
4730 win.redraw_tab(ctrl)
4732 self.update_status_combobox()
4734 def set_account_status_icon(self, account):
4735 status = gajim.connections[account].connected
4736 child_iterA = self._get_account_iter(account, self.model)
4737 if not child_iterA:
4738 return
4739 if not self.regroup:
4740 show = gajim.SHOW_LIST[status]
4741 else: # accounts merged
4742 show = helpers.get_global_show()
4743 self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[
4744 '16'][show]
4746 ################################################################################
4747 ### Style and theme related methods
4748 ################################################################################
4750 def show_title(self):
4751 change_title_allowed = gajim.config.get('change_roster_title')
4752 if not change_title_allowed:
4753 return
4755 if gajim.config.get('one_message_window') == 'always_with_roster':
4756 # always_with_roster mode defers to the MessageWindow
4757 if not gajim.interface.msg_win_mgr.one_window_opened():
4758 # No MessageWindow to defer to
4759 self.window.set_title('Gajim')
4760 return
4762 nb_unread = 0
4763 start = ''
4764 for account in gajim.connections:
4765 # Count events in roster title only if we don't auto open them
4766 if not helpers.allow_popup_window(account):
4767 nb_unread += gajim.events.get_nb_events(['chat', 'normal',
4768 'file-request', 'file-error', 'file-completed',
4769 'file-request-error', 'file-send-error', 'file-stopped',
4770 'printed_chat'], account)
4771 if nb_unread > 1:
4772 start = '[' + str(nb_unread) + '] '
4773 elif nb_unread == 1:
4774 start = '* '
4776 self.window.set_title(start + 'Gajim')
4778 gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
4780 def _change_style(self, model, path, titer, option):
4781 if option is None or model[titer][C_TYPE] == option:
4782 # We changed style for this type of row
4783 model[titer][C_NAME] = model[titer][C_NAME]
4785 def change_roster_style(self, option):
4786 self.model.foreach(self._change_style, option)
4787 for win in gajim.interface.msg_win_mgr.windows():
4788 win.repaint_themed_widgets()
4790 def repaint_themed_widgets(self):
4792 Notify windows that contain themed widgets to repaint them
4794 for win in gajim.interface.msg_win_mgr.windows():
4795 win.repaint_themed_widgets()
4796 for account in gajim.connections:
4797 for addr in gajim.interface.instances[account]['disco']:
4798 gajim.interface.instances[account]['disco'][addr].paint_banner()
4799 for ctrl in gajim.interface.minimized_controls[account].values():
4800 ctrl.repaint_themed_widgets()
4802 def update_avatar_in_gui(self, jid, account):
4803 # Update roster
4804 self.draw_avatar(jid, account)
4805 # Update chat window
4807 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
4808 if ctrl:
4809 ctrl.show_avatar()
4811 def set_renderer_color(self, renderer, style, set_background=True):
4813 Set style for treeview cell, using PRELIGHT system color
4815 if set_background:
4816 bgcolor = self.tree.style.bg[style]
4817 renderer.set_property('cell-background-gdk', bgcolor)
4818 else:
4819 fgcolor = self.tree.style.fg[style]
4820 renderer.set_property('foreground-gdk', fgcolor)
4822 def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
4824 When a row is added, set properties for icon renderer
4826 type_ = model[titer][C_TYPE]
4827 if type_ == 'account':
4828 self._set_account_row_background_color(renderer)
4829 renderer.set_property('xalign', 0)
4830 elif type_ == 'group':
4831 self._set_group_row_background_color(renderer)
4832 parent_iter = model.iter_parent(titer)
4833 if model[parent_iter][C_TYPE] == 'group':
4834 renderer.set_property('xalign', 0.4)
4835 else:
4836 renderer.set_property('xalign', 0.2)
4837 elif type_:
4838 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4839 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4840 # This can append when at the moment we add the row
4841 return
4842 jid = model[titer][C_JID].decode('utf-8')
4843 account = model[titer][C_ACCOUNT].decode('utf-8')
4844 self._set_contact_row_background_color(renderer, jid, account)
4845 parent_iter = model.iter_parent(titer)
4846 if model[parent_iter][C_TYPE] == 'contact':
4847 renderer.set_property('xalign', 1)
4848 else:
4849 renderer.set_property('xalign', 0.6)
4850 renderer.set_property('width', 26)
4852 def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
4854 When a row is added, set properties for name renderer
4856 theme = gajim.config.get('roster_theme')
4857 type_ = model[titer][C_TYPE]
4858 if type_ == 'account':
4859 color = gajim.config.get_per('themes', theme, 'accounttextcolor')
4860 if color:
4861 renderer.set_property('foreground', color)
4862 else:
4863 self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False)
4864 renderer.set_property('font',
4865 gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
4866 renderer.set_property('xpad', 0)
4867 renderer.set_property('width', 3)
4868 self._set_account_row_background_color(renderer)
4869 elif type_ == 'group':
4870 color = gajim.config.get_per('themes', theme, 'grouptextcolor')
4871 if color:
4872 renderer.set_property('foreground', color)
4873 else:
4874 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
4875 renderer.set_property('font',
4876 gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
4877 parent_iter = model.iter_parent(titer)
4878 if model[parent_iter][C_TYPE] == 'group':
4879 renderer.set_property('xpad', 8)
4880 else:
4881 renderer.set_property('xpad', 4)
4882 self._set_group_row_background_color(renderer)
4883 elif type_:
4884 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4885 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4886 # This can append when at the moment we add the row
4887 return
4888 jid = model[titer][C_JID].decode('utf-8')
4889 account = model[titer][C_ACCOUNT].decode('utf-8')
4890 color = None
4891 if type_ == 'groupchat':
4892 ctrl = gajim.interface.minimized_controls[account].get(jid,
4893 None)
4894 if ctrl and ctrl.attention_flag:
4895 color = gajim.config.get_per('themes', theme,
4896 'state_muc_directed_msg_color')
4897 renderer.set_property('foreground', 'red')
4898 if not color:
4899 color = gajim.config.get_per('themes', theme,
4900 'contacttextcolor')
4901 if color:
4902 renderer.set_property('foreground', color)
4903 else:
4904 renderer.set_property('foreground', None)
4905 self._set_contact_row_background_color(renderer, jid, account)
4906 renderer.set_property('font',
4907 gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
4908 parent_iter = model.iter_parent(titer)
4909 if model[parent_iter][C_TYPE] == 'contact':
4910 renderer.set_property('xpad', 16)
4911 else:
4912 renderer.set_property('xpad', 12)
4914 def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
4915 data=None):
4917 When a row is added, draw the respective pep icon
4919 type_ = model[titer][C_TYPE]
4921 # allocate space for the icon only if needed
4922 if not model[titer][data]:
4923 renderer.set_property('visible', False)
4924 else:
4925 renderer.set_property('visible', True)
4927 if type_ == 'account':
4928 self._set_account_row_background_color(renderer)
4929 renderer.set_property('xalign', 1)
4930 elif type_:
4931 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4932 # This can append at the moment we add the row
4933 return
4934 jid = model[titer][C_JID].decode('utf-8')
4935 account = model[titer][C_ACCOUNT].decode('utf-8')
4936 self._set_contact_row_background_color(renderer, jid, account)
4938 def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer,
4939 data=None):
4941 When a row is added, set properties for avatar renderer
4943 type_ = model[titer][C_TYPE]
4944 if type_ in ('group', 'account'):
4945 renderer.set_property('visible', False)
4946 return
4948 # allocate space for the icon only if needed
4949 if model[titer][C_AVATAR_PIXBUF] or \
4950 gajim.config.get('avatar_position_in_roster') == 'left':
4951 renderer.set_property('visible', True)
4952 if type_:
4953 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4954 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4955 # This can append at the moment we add the row
4956 return
4957 jid = model[titer][C_JID].decode('utf-8')
4958 account = model[titer][C_ACCOUNT].decode('utf-8')
4959 self._set_contact_row_background_color(renderer, jid, account)
4960 else:
4961 renderer.set_property('visible', False)
4963 if gajim.config.get('avatar_position_in_roster') == 'left':
4964 renderer.set_property('width', gajim.config.get(
4965 'roster_avatar_width'))
4966 renderer.set_property('xalign', 0.5)
4967 else:
4968 renderer.set_property('xalign', 1) # align pixbuf to the right
4970 def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer,
4971 data=None):
4973 When a row is added, set properties for padlock renderer
4975 type_ = model[titer][C_TYPE]
4976 # allocate space for the icon only if needed
4977 if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]:
4978 renderer.set_property('visible', True)
4979 self._set_account_row_background_color(renderer)
4980 renderer.set_property('xalign', 1) # align pixbuf to the right
4981 else:
4982 renderer.set_property('visible', False)
4984 def _set_account_row_background_color(self, renderer):
4985 theme = gajim.config.get('roster_theme')
4986 color = gajim.config.get_per('themes', theme, 'accountbgcolor')
4987 if color:
4988 renderer.set_property('cell-background', color)
4989 else:
4990 self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
4992 def _set_contact_row_background_color(self, renderer, jid, account):
4993 theme = gajim.config.get('roster_theme')
4994 if jid in gajim.newly_added[account]:
4995 renderer.set_property('cell-background', gajim.config.get(
4996 'just_connected_bg_color'))
4997 elif jid in gajim.to_be_removed[account]:
4998 renderer.set_property('cell-background', gajim.config.get(
4999 'just_disconnected_bg_color'))
5000 else:
5001 color = gajim.config.get_per('themes', theme, 'contactbgcolor')
5002 renderer.set_property('cell-background', color if color else None)
5004 def _set_group_row_background_color(self, renderer):
5005 theme = gajim.config.get('roster_theme')
5006 color = gajim.config.get_per('themes', theme, 'groupbgcolor')
5007 if color:
5008 renderer.set_property('cell-background', color)
5009 else:
5010 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
5012 ################################################################################
5013 ### Everything about building menus
5014 ### FIXME: We really need to make it simpler! 1465 lines are a few to much....
5015 ################################################################################
5017 def make_menu(self, force=False):
5019 Create the main window's menus
5021 if not force and not self.actions_menu_needs_rebuild:
5022 return
5023 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
5024 single_message_menuitem = self.xml.get_object(
5025 'send_single_message_menuitem')
5026 join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
5027 muc_icon = gtkgui_helpers.load_icon('muc_active')
5028 if muc_icon:
5029 join_gc_menuitem.set_image(muc_icon)
5030 add_new_contact_menuitem = self.xml.get_object(
5031 'add_new_contact_menuitem')
5032 service_disco_menuitem = self.xml.get_object('service_disco_menuitem')
5033 advanced_menuitem = self.xml.get_object('advanced_menuitem')
5034 profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem')
5036 # destroy old advanced menus
5037 for m in self.advanced_menus:
5038 m.destroy()
5040 # make it sensitive. it is insensitive only if no accounts are
5041 # *available*
5042 advanced_menuitem.set_sensitive(True)
5044 if self.add_new_contact_handler_id:
5045 add_new_contact_menuitem.handler_disconnect(
5046 self.add_new_contact_handler_id)
5047 self.add_new_contact_handler_id = None
5049 if self.service_disco_handler_id:
5050 service_disco_menuitem.handler_disconnect(
5051 self.service_disco_handler_id)
5052 self.service_disco_handler_id = None
5054 if self.single_message_menuitem_handler_id:
5055 single_message_menuitem.handler_disconnect(
5056 self.single_message_menuitem_handler_id)
5057 self.single_message_menuitem_handler_id = None
5059 if self.profile_avatar_menuitem_handler_id:
5060 profile_avatar_menuitem.handler_disconnect(
5061 self.profile_avatar_menuitem_handler_id)
5062 self.profile_avatar_menuitem_handler_id = None
5064 # remove the existing submenus
5065 add_new_contact_menuitem.remove_submenu()
5066 service_disco_menuitem.remove_submenu()
5067 join_gc_menuitem.remove_submenu()
5068 single_message_menuitem.remove_submenu()
5069 advanced_menuitem.remove_submenu()
5070 profile_avatar_menuitem.remove_submenu()
5072 gc_sub_menu = gtk.Menu() # gc is always a submenu
5073 join_gc_menuitem.set_submenu(gc_sub_menu)
5075 connected_accounts = gajim.get_number_of_connected_accounts()
5077 connected_accounts_with_private_storage = 0
5079 # items that get shown whether an account is zeroconf or not
5080 accounts_list = sorted(gajim.contacts.get_accounts())
5081 if connected_accounts > 2 or \
5082 (connected_accounts > 1 and not gajim.zeroconf_is_connected()):
5083 # 2 or more "real" (no zeroconf) accounts? make submenus
5084 new_chat_sub_menu = gtk.Menu()
5086 for account in accounts_list:
5087 if gajim.connections[account].connected <= 1 or \
5088 gajim.config.get_per('accounts', account, 'is_zeroconf'):
5089 # if offline or connecting or zeroconf
5090 continue
5092 # new chat
5093 new_chat_item = gtk.MenuItem(_('using account %s') % account,
5094 False)
5095 new_chat_sub_menu.append(new_chat_item)
5096 new_chat_item.connect('activate',
5097 self.on_new_chat_menuitem_activate, account)
5099 new_chat_menuitem.set_submenu(new_chat_sub_menu)
5100 new_chat_sub_menu.show_all()
5102 # menu items that don't apply to zeroconf connections
5103 if connected_accounts == 1 or (connected_accounts == 2 and \
5104 gajim.zeroconf_is_connected()):
5105 # only one 'real' (non-zeroconf) account is connected, don't need
5106 # submenus
5108 for account in accounts_list:
5109 if gajim.account_is_connected(account) and \
5110 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
5111 # gc
5112 if gajim.connections[account].private_storage_supported:
5113 connected_accounts_with_private_storage += 1
5114 self.add_bookmarks_list(gc_sub_menu, account)
5115 gc_sub_menu.show_all()
5116 # add
5117 if not self.add_new_contact_handler_id:
5118 self.add_new_contact_handler_id = \
5119 add_new_contact_menuitem.connect(
5120 'activate', self.on_add_new_contact, account)
5121 # disco
5122 if not self.service_disco_handler_id:
5123 self.service_disco_handler_id = service_disco_menuitem.\
5124 connect('activate',
5125 self.on_service_disco_menuitem_activate, account)
5127 # single message
5128 if not self.single_message_menuitem_handler_id:
5129 self.single_message_menuitem_handler_id = \
5130 single_message_menuitem.connect('activate', \
5131 self.on_send_single_message_menuitem_activate, account)
5133 break # No other account connected
5134 else:
5135 # 2 or more 'real' accounts are connected, make submenus
5136 single_message_sub_menu = gtk.Menu()
5137 add_sub_menu = gtk.Menu()
5138 disco_sub_menu = gtk.Menu()
5140 for account in accounts_list:
5141 if gajim.connections[account].connected <= 1 or \
5142 gajim.config.get_per('accounts', account, 'is_zeroconf'):
5143 # skip account if it's offline or connecting or is zeroconf
5144 continue
5146 # single message
5147 single_message_item = gtk.MenuItem(_('using account %s') % \
5148 account, False)
5149 single_message_sub_menu.append(single_message_item)
5150 single_message_item.connect('activate',
5151 self.on_send_single_message_menuitem_activate, account)
5153 # join gc
5154 if gajim.connections[account].private_storage_supported:
5155 connected_accounts_with_private_storage += 1
5156 gc_item = gtk.MenuItem(_('using account %s') % account, False)
5157 gc_sub_menu.append(gc_item)
5158 gc_menuitem_menu = gtk.Menu()
5159 self.add_bookmarks_list(gc_menuitem_menu, account)
5160 gc_item.set_submenu(gc_menuitem_menu)
5162 # add
5163 add_item = gtk.MenuItem(_('to %s account') % account, False)
5164 add_sub_menu.append(add_item)
5165 add_item.connect('activate', self.on_add_new_contact, account)
5167 # disco
5168 disco_item = gtk.MenuItem(_('using %s account') % account,
5169 False)
5170 disco_sub_menu.append(disco_item)
5171 disco_item.connect('activate',
5172 self.on_service_disco_menuitem_activate, account)
5174 single_message_menuitem.set_submenu(single_message_sub_menu)
5175 single_message_sub_menu.show_all()
5176 gc_sub_menu.show_all()
5177 add_new_contact_menuitem.set_submenu(add_sub_menu)
5178 add_sub_menu.show_all()
5179 service_disco_menuitem.set_submenu(disco_sub_menu)
5180 disco_sub_menu.show_all()
5182 if connected_accounts == 0:
5183 # no connected accounts, make the menuitems insensitive
5184 for item in (new_chat_menuitem, join_gc_menuitem,
5185 add_new_contact_menuitem, service_disco_menuitem,
5186 single_message_menuitem):
5187 item.set_sensitive(False)
5188 else: # we have one or more connected accounts
5189 for item in (new_chat_menuitem, join_gc_menuitem,
5190 add_new_contact_menuitem, service_disco_menuitem,
5191 single_message_menuitem):
5192 item.set_sensitive(True)
5193 # disable some fields if only local account is there
5194 if connected_accounts == 1:
5195 for account in gajim.connections:
5196 if gajim.account_is_connected(account) and \
5197 gajim.connections[account].is_zeroconf:
5198 for item in (new_chat_menuitem, join_gc_menuitem,
5199 add_new_contact_menuitem, service_disco_menuitem,
5200 single_message_menuitem):
5201 item.set_sensitive(False)
5203 # Manage GC bookmarks
5204 newitem = gtk.SeparatorMenuItem() # separator
5205 gc_sub_menu.append(newitem)
5207 newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
5208 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5209 gtk.ICON_SIZE_MENU)
5210 newitem.set_image(img)
5211 newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
5212 gc_sub_menu.append(newitem)
5213 gc_sub_menu.show_all()
5214 if connected_accounts_with_private_storage == 0:
5215 newitem.set_sensitive(False)
5217 connected_accounts_with_vcard = []
5218 for account in gajim.connections:
5219 if gajim.account_is_connected(account) and \
5220 gajim.connections[account].vcard_supported:
5221 connected_accounts_with_vcard.append(account)
5222 if len(connected_accounts_with_vcard) > 1:
5223 # 2 or more accounts? make submenus
5224 profile_avatar_sub_menu = gtk.Menu()
5225 for account in connected_accounts_with_vcard:
5226 # profile, avatar
5227 profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
5228 False)
5229 profile_avatar_sub_menu.append(profile_avatar_item)
5230 profile_avatar_item.connect('activate',
5231 self.on_profile_avatar_menuitem_activate, account)
5232 profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
5233 profile_avatar_sub_menu.show_all()
5234 elif len(connected_accounts_with_vcard) == 1:
5235 # user has only one account
5236 account = connected_accounts_with_vcard[0]
5237 # profile, avatar
5238 if not self.profile_avatar_menuitem_handler_id:
5239 self.profile_avatar_menuitem_handler_id = \
5240 profile_avatar_menuitem.connect('activate',
5241 self.on_profile_avatar_menuitem_activate, account)
5243 if len(connected_accounts_with_vcard) == 0:
5244 profile_avatar_menuitem.set_sensitive(False)
5245 else:
5246 profile_avatar_menuitem.set_sensitive(True)
5248 # Advanced Actions
5249 if len(gajim.connections) == 0: # user has no accounts
5250 advanced_menuitem.set_sensitive(False)
5251 elif len(gajim.connections) == 1: # we have one acccount
5252 account = gajim.connections.keys()[0]
5253 advanced_menuitem_menu = \
5254 self.get_and_connect_advanced_menuitem_menu(account)
5255 self.advanced_menus.append(advanced_menuitem_menu)
5257 self.add_history_manager_menuitem(advanced_menuitem_menu)
5259 advanced_menuitem.set_submenu(advanced_menuitem_menu)
5260 advanced_menuitem_menu.show_all()
5261 else: # user has *more* than one account : build advanced submenus
5262 advanced_sub_menu = gtk.Menu()
5263 accounts = [] # Put accounts in a list to sort them
5264 for account in gajim.connections:
5265 accounts.append(account)
5266 accounts.sort()
5267 for account in accounts:
5268 advanced_item = gtk.MenuItem(_('for account %s') % account,
5269 False)
5270 advanced_sub_menu.append(advanced_item)
5271 advanced_menuitem_menu = \
5272 self.get_and_connect_advanced_menuitem_menu(account)
5273 self.advanced_menus.append(advanced_menuitem_menu)
5274 advanced_item.set_submenu(advanced_menuitem_menu)
5276 self.add_history_manager_menuitem(advanced_sub_menu)
5278 advanced_menuitem.set_submenu(advanced_sub_menu)
5279 advanced_sub_menu.show_all()
5281 self.actions_menu_needs_rebuild = False
5283 def build_account_menu(self, account):
5284 # we have to create our own set of icons for the menu
5285 # using self.jabber_status_images is poopoo
5286 iconset = gajim.config.get('iconset')
5287 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5288 state_images = gtkgui_helpers.load_iconset(path)
5290 if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
5291 xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui')
5292 account_context_menu = xml.get_object('account_context_menu')
5294 status_menuitem = xml.get_object('status_menuitem')
5295 start_chat_menuitem = xml.get_object('start_chat_menuitem')
5296 join_group_chat_menuitem = xml.get_object(
5297 'join_group_chat_menuitem')
5298 muc_icon = gtkgui_helpers.load_icon('muc_active')
5299 if muc_icon:
5300 join_group_chat_menuitem.set_image(muc_icon)
5301 open_gmail_inbox_menuitem = xml.get_object(
5302 'open_gmail_inbox_menuitem')
5303 add_contact_menuitem = xml.get_object('add_contact_menuitem')
5304 service_discovery_menuitem = xml.get_object(
5305 'service_discovery_menuitem')
5306 execute_command_menuitem = xml.get_object(
5307 'execute_command_menuitem')
5308 edit_account_menuitem = xml.get_object('edit_account_menuitem')
5309 sub_menu = gtk.Menu()
5310 status_menuitem.set_submenu(sub_menu)
5312 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
5313 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5314 item = gtk.ImageMenuItem(uf_show)
5315 icon = state_images[show]
5316 item.set_image(icon)
5317 sub_menu.append(item)
5318 con = gajim.connections[account]
5319 if show == 'invisible' and con.connected > 1 and \
5320 not con.privacy_rules_supported:
5321 item.set_sensitive(False)
5322 else:
5323 item.connect('activate', self.change_status, account, show)
5325 item = gtk.SeparatorMenuItem()
5326 sub_menu.append(item)
5328 item = gtk.ImageMenuItem(_('_Change Status Message'))
5329 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5330 sub_menu.append(item)
5331 item.connect('activate', self.on_change_status_message_activate,
5332 account)
5333 if gajim.connections[account].connected < 2:
5334 item.set_sensitive(False)
5336 item = gtk.SeparatorMenuItem()
5337 sub_menu.append(item)
5339 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5340 item = gtk.ImageMenuItem(uf_show)
5341 icon = state_images['offline']
5342 item.set_image(icon)
5343 sub_menu.append(item)
5344 item.connect('activate', self.change_status, account, 'offline')
5346 pep_menuitem = xml.get_object('pep_menuitem')
5347 if gajim.connections[account].pep_supported:
5348 pep_submenu = gtk.Menu()
5349 pep_menuitem.set_submenu(pep_submenu)
5350 def add_item(label, opt_name, func):
5351 item = gtk.CheckMenuItem(label)
5352 pep_submenu.append(item)
5353 if not dbus_support.supported:
5354 item.set_sensitive(False)
5355 else:
5356 activ = gajim.config.get_per('accounts', account,
5357 opt_name)
5358 item.set_active(activ)
5359 item.connect('toggled', func, account)
5361 add_item(_('Publish Tune'), 'publish_tune',
5362 self.on_publish_tune_toggled)
5363 add_item(_('Publish Location'), 'publish_location',
5364 self.on_publish_location_toggled)
5366 pep_config = gtk.ImageMenuItem(_('Configure Services...'))
5367 item = gtk.SeparatorMenuItem()
5368 pep_submenu.append(item)
5369 pep_config.set_sensitive(True)
5370 pep_submenu.append(pep_config)
5371 pep_config.connect('activate',
5372 self.on_pep_services_menuitem_activate, account)
5373 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5374 gtk.ICON_SIZE_MENU)
5375 pep_config.set_image(img)
5377 else:
5378 pep_menuitem.set_sensitive(False)
5380 if not gajim.connections[account].gmail_url:
5381 open_gmail_inbox_menuitem.set_no_show_all(True)
5382 open_gmail_inbox_menuitem.hide()
5383 else:
5384 open_gmail_inbox_menuitem.connect('activate',
5385 self.on_open_gmail_inbox, account)
5387 edit_account_menuitem.connect('activate', self.on_edit_account,
5388 account)
5389 add_contact_menuitem.connect('activate', self.on_add_new_contact,
5390 account)
5391 service_discovery_menuitem.connect('activate',
5392 self.on_service_disco_menuitem_activate, account)
5393 hostname = gajim.config.get_per('accounts', account, 'hostname')
5394 contact = gajim.contacts.create_contact(jid=hostname,
5395 account=account) # Fake contact
5396 execute_command_menuitem.connect('activate',
5397 self.on_execute_command, contact, account)
5399 start_chat_menuitem.connect('activate',
5400 self.on_new_chat_menuitem_activate, account)
5402 gc_sub_menu = gtk.Menu() # gc is always a submenu
5403 join_group_chat_menuitem.set_submenu(gc_sub_menu)
5404 self.add_bookmarks_list(gc_sub_menu, account)
5406 # make some items insensitive if account is offline
5407 if gajim.connections[account].connected < 2:
5408 for widget in (add_contact_menuitem, service_discovery_menuitem,
5409 join_group_chat_menuitem, execute_command_menuitem,
5410 pep_menuitem, start_chat_menuitem):
5411 widget.set_sensitive(False)
5412 else:
5413 xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui')
5414 account_context_menu = xml.get_object('zeroconf_context_menu')
5416 status_menuitem = xml.get_object('status_menuitem')
5417 zeroconf_properties_menuitem = xml.get_object(
5418 'zeroconf_properties_menuitem')
5419 sub_menu = gtk.Menu()
5420 status_menuitem.set_submenu(sub_menu)
5422 for show in ('online', 'away', 'dnd', 'invisible'):
5423 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5424 item = gtk.ImageMenuItem(uf_show)
5425 icon = state_images[show]
5426 item.set_image(icon)
5427 sub_menu.append(item)
5428 item.connect('activate', self.change_status, account, show)
5430 item = gtk.SeparatorMenuItem()
5431 sub_menu.append(item)
5433 item = gtk.ImageMenuItem(_('_Change Status Message'))
5434 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5435 sub_menu.append(item)
5436 item.connect('activate', self.on_change_status_message_activate,
5437 account)
5438 if gajim.connections[account].connected < 2:
5439 item.set_sensitive(False)
5441 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5442 item = gtk.ImageMenuItem(uf_show)
5443 icon = state_images['offline']
5444 item.set_image(icon)
5445 sub_menu.append(item)
5446 item.connect('activate', self.change_status, account, 'offline')
5448 zeroconf_properties_menuitem.connect('activate',
5449 self.on_zeroconf_properties, account)
5451 return account_context_menu
5453 def make_account_menu(self, event, titer):
5455 Make account's popup menu
5457 model = self.modelfilter
5458 account = model[titer][C_ACCOUNT].decode('utf-8')
5460 if account != 'all': # not in merged mode
5461 menu = self.build_account_menu(account)
5462 else:
5463 menu = gtk.Menu()
5464 iconset = gajim.config.get('iconset')
5465 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5466 accounts = [] # Put accounts in a list to sort them
5467 for account in gajim.connections:
5468 accounts.append(account)
5469 accounts.sort()
5470 for account in accounts:
5471 state_images = gtkgui_helpers.load_iconset(path)
5472 item = gtk.ImageMenuItem(account)
5473 show = gajim.SHOW_LIST[gajim.connections[account].connected]
5474 icon = state_images[show]
5475 item.set_image(icon)
5476 account_menu = self.build_account_menu(account)
5477 item.set_submenu(account_menu)
5478 menu.append(item)
5480 event_button = gtkgui_helpers.get_possible_button_event(event)
5482 menu.attach_to_widget(self.tree, None)
5483 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5484 menu.show_all()
5485 menu.popup(None, None, None, event_button, event.time)
5487 def make_group_menu(self, event, titer):
5489 Make group's popup menu
5491 model = self.modelfilter
5492 path = model.get_path(titer)
5493 group = model[titer][C_JID].decode('utf-8')
5494 account = model[titer][C_ACCOUNT].decode('utf-8')
5496 list_ = [] # list of (jid, account) tuples
5497 list_online = [] # list of (jid, account) tuples
5499 group = model[titer][C_JID]
5500 for jid in gajim.contacts.get_jid_list(account):
5501 contact = gajim.contacts.get_contact_with_highest_priority(account,
5502 jid)
5503 if group in contact.get_shown_groups():
5504 if contact.show not in ('offline', 'error'):
5505 list_online.append((contact, account))
5506 list_.append((contact, account))
5507 menu = gtk.Menu()
5509 # Make special context menu if group is Groupchats
5510 if group == _('Groupchats'):
5511 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All'))
5512 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
5513 gtk.ICON_SIZE_MENU)
5514 maximize_menuitem.set_image(icon)
5515 maximize_menuitem.connect('activate',
5516 self.on_all_groupchat_maximized, list_)
5517 menu.append(maximize_menuitem)
5518 else:
5519 # Send Group Message
5520 send_group_message_item = gtk.ImageMenuItem(
5521 _('Send Group M_essage'))
5522 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5523 send_group_message_item.set_image(icon)
5525 send_group_message_submenu = gtk.Menu()
5526 send_group_message_item.set_submenu(send_group_message_submenu)
5527 menu.append(send_group_message_item)
5529 group_message_to_all_item = gtk.MenuItem(_('To all users'))
5530 send_group_message_submenu.append(group_message_to_all_item)
5532 group_message_to_all_online_item = gtk.MenuItem(
5533 _('To all online users'))
5534 send_group_message_submenu.append(group_message_to_all_online_item)
5536 group_message_to_all_online_item.connect('activate',
5537 self.on_send_single_message_menuitem_activate, account,
5538 list_online)
5539 group_message_to_all_item.connect('activate',
5540 self.on_send_single_message_menuitem_activate, account, list_)
5542 # Invite to
5543 invite_menuitem = gtk.ImageMenuItem(_('In_vite to'))
5544 muc_icon = gtkgui_helpers.load_icon('muc_active')
5545 if muc_icon:
5546 invite_menuitem.set_image(muc_icon)
5548 gui_menu_builder.build_invite_submenu(invite_menuitem, list_online)
5549 menu.append(invite_menuitem)
5551 # Send Custom Status
5552 send_custom_status_menuitem = gtk.ImageMenuItem(
5553 _('Send Cus_tom Status'))
5554 # add a special img for this menuitem
5555 if helpers.group_is_blocked(account, group):
5556 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5557 'offline'))
5558 send_custom_status_menuitem.set_sensitive(False)
5559 else:
5560 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5561 gtk.ICON_SIZE_MENU)
5562 send_custom_status_menuitem.set_image(icon)
5563 status_menuitems = gtk.Menu()
5564 send_custom_status_menuitem.set_submenu(status_menuitems)
5565 iconset = gajim.config.get('iconset')
5566 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5567 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5568 # icon MUST be different instance for every item
5569 state_images = gtkgui_helpers.load_iconset(path)
5570 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5571 status_menuitem.connect('activate', self.on_send_custom_status,
5572 list_, s, group)
5573 icon = state_images[s]
5574 status_menuitem.set_image(icon)
5575 status_menuitems.append(status_menuitem)
5576 menu.append(send_custom_status_menuitem)
5578 # there is no singlemessage and custom status for zeroconf
5579 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
5580 send_custom_status_menuitem.set_sensitive(False)
5581 send_group_message_item.set_sensitive(False)
5583 if not group in helpers.special_groups:
5584 item = gtk.SeparatorMenuItem() # separator
5585 menu.append(item)
5587 # Rename
5588 rename_item = gtk.ImageMenuItem(_('Re_name'))
5589 # add a special img for rename menuitem
5590 gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input')
5591 menu.append(rename_item)
5592 rename_item.connect('activate', self.on_rename, 'group', group,
5593 account)
5595 # Block group
5596 is_blocked = False
5597 if self.regroup:
5598 for g_account in gajim.connections:
5599 if helpers.group_is_blocked(g_account, group):
5600 is_blocked = True
5601 else:
5602 if helpers.group_is_blocked(account, group):
5603 is_blocked = True
5605 if is_blocked and gajim.connections[account].\
5606 privacy_rules_supported:
5607 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5608 icon = gtk.image_new_from_stock(gtk.STOCK_STOP,
5609 gtk.ICON_SIZE_MENU)
5610 unblock_menuitem.set_image(icon)
5611 unblock_menuitem.connect('activate', self.on_unblock, list_,
5612 group)
5613 menu.append(unblock_menuitem)
5614 else:
5615 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5616 icon = gtk.image_new_from_stock(gtk.STOCK_STOP,
5617 gtk.ICON_SIZE_MENU)
5618 block_menuitem.set_image(icon)
5619 block_menuitem.connect('activate', self.on_block, list_, group)
5620 menu.append(block_menuitem)
5621 if not gajim.connections[account].privacy_rules_supported:
5622 block_menuitem.set_sensitive(False)
5624 # Remove group
5625 remove_item = gtk.ImageMenuItem(_('_Remove'))
5626 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE,
5627 gtk.ICON_SIZE_MENU)
5628 remove_item.set_image(icon)
5629 menu.append(remove_item)
5630 remove_item.connect('activate', self.on_remove_group_item_activated,
5631 group, account)
5633 # unsensitive if account is not connected
5634 if gajim.connections[account].connected < 2:
5635 rename_item.set_sensitive(False)
5637 # General group cannot be changed
5638 if group == _('General'):
5639 rename_item.set_sensitive(False)
5640 remove_item.set_sensitive(False)
5642 event_button = gtkgui_helpers.get_possible_button_event(event)
5644 menu.attach_to_widget(self.tree, None)
5645 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5646 menu.show_all()
5647 menu.popup(None, None, None, event_button, event.time)
5649 def make_contact_menu(self, event, titer):
5651 Make contact's popup menu
5653 model = self.modelfilter
5654 jid = model[titer][C_JID].decode('utf-8')
5655 account = model[titer][C_ACCOUNT].decode('utf-8')
5656 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5657 menu = gui_menu_builder.get_contact_menu(contact, account)
5658 event_button = gtkgui_helpers.get_possible_button_event(event)
5659 menu.attach_to_widget(self.tree, None)
5660 menu.popup(None, None, None, event_button, event.time)
5662 def make_multiple_contact_menu(self, event, iters):
5664 Make group's popup menu
5666 model = self.modelfilter
5667 list_ = [] # list of (jid, account) tuples
5668 one_account_offline = False
5669 is_blocked = True
5670 privacy_rules_supported = True
5671 for titer in iters:
5672 jid = model[titer][C_JID].decode('utf-8')
5673 account = model[titer][C_ACCOUNT].decode('utf-8')
5674 if gajim.connections[account].connected < 2:
5675 one_account_offline = True
5676 if not gajim.connections[account].privacy_rules_supported:
5677 privacy_rules_supported = False
5678 contact = gajim.contacts.get_contact_with_highest_priority(account,
5679 jid)
5680 if helpers.jid_is_blocked(account, jid):
5681 is_blocked = False
5682 list_.append((contact, account))
5684 menu = gtk.Menu()
5685 account = None
5686 for (contact, current_account) in list_:
5687 # check that we use the same account for every sender
5688 if account is not None and account != current_account:
5689 account = None
5690 break
5691 account = current_account
5692 if account is not None:
5693 send_group_message_item = gtk.ImageMenuItem(
5694 _('Send Group M_essage'))
5695 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5696 send_group_message_item.set_image(icon)
5697 menu.append(send_group_message_item)
5698 send_group_message_item.connect('activate',
5699 self.on_send_single_message_menuitem_activate, account, list_)
5701 # Invite to Groupchat
5702 invite_item = gtk.ImageMenuItem(_('In_vite to'))
5703 muc_icon = gtkgui_helpers.load_icon('muc_active')
5704 if muc_icon:
5705 invite_item.set_image(muc_icon)
5707 gui_menu_builder.build_invite_submenu(invite_item, list_)
5708 menu.append(invite_item)
5710 item = gtk.SeparatorMenuItem() # separator
5711 menu.append(item)
5713 # Manage Transport submenu
5714 item = gtk.ImageMenuItem(_('_Manage Contacts'))
5715 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES,
5716 gtk.ICON_SIZE_MENU)
5717 item.set_image(icon)
5718 manage_contacts_submenu = gtk.Menu()
5719 item.set_submenu(manage_contacts_submenu)
5720 menu.append(item)
5722 # Edit Groups
5723 edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups'))
5724 icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
5725 edit_groups_item.set_image(icon)
5726 manage_contacts_submenu.append(edit_groups_item)
5727 edit_groups_item.connect('activate', self.on_edit_groups, list_)
5729 item = gtk.SeparatorMenuItem() # separator
5730 manage_contacts_submenu.append(item)
5732 # Block
5733 if is_blocked and privacy_rules_supported:
5734 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5735 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5736 unblock_menuitem.set_image(icon)
5737 unblock_menuitem.connect('activate', self.on_unblock, list_)
5738 manage_contacts_submenu.append(unblock_menuitem)
5739 else:
5740 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5741 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5742 block_menuitem.set_image(icon)
5743 block_menuitem.connect('activate', self.on_block, list_)
5744 manage_contacts_submenu.append(block_menuitem)
5746 if not privacy_rules_supported:
5747 block_menuitem.set_sensitive(False)
5749 # Remove
5750 remove_item = gtk.ImageMenuItem(_('_Remove'))
5751 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5752 remove_item.set_image(icon)
5753 manage_contacts_submenu.append(remove_item)
5754 remove_item.connect('activate', self.on_req_usub, list_)
5755 # unsensitive remove if one account is not connected
5756 if one_account_offline:
5757 remove_item.set_sensitive(False)
5759 event_button = gtkgui_helpers.get_possible_button_event(event)
5761 menu.attach_to_widget(self.tree, None)
5762 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5763 menu.show_all()
5764 menu.popup(None, None, None, event_button, event.time)
5766 def make_transport_menu(self, event, titer):
5768 Make transport's popup menu
5770 model = self.modelfilter
5771 jid = model[titer][C_JID].decode('utf-8')
5772 path = model.get_path(titer)
5773 account = model[titer][C_ACCOUNT].decode('utf-8')
5774 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5775 menu = gtk.Menu()
5777 # Send single message
5778 item = gtk.ImageMenuItem(_('Send Single Message'))
5779 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5780 item.set_image(icon)
5781 item.connect('activate',
5782 self.on_send_single_message_menuitem_activate, account, contact)
5783 menu.append(item)
5785 blocked = False
5786 if helpers.jid_is_blocked(account, jid):
5787 blocked = True
5789 # Send Custom Status
5790 send_custom_status_menuitem = gtk.ImageMenuItem(
5791 _('Send Cus_tom Status'))
5792 # add a special img for this menuitem
5793 if blocked:
5794 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5795 'offline'))
5796 send_custom_status_menuitem.set_sensitive(False)
5797 else:
5798 if account in gajim.interface.status_sent_to_users and \
5799 jid in gajim.interface.status_sent_to_users[account]:
5800 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5801 gajim.interface.status_sent_to_users[account][jid]))
5802 else:
5803 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5804 gtk.ICON_SIZE_MENU)
5805 send_custom_status_menuitem.set_image(icon)
5806 status_menuitems = gtk.Menu()
5807 send_custom_status_menuitem.set_submenu(status_menuitems)
5808 iconset = gajim.config.get('iconset')
5809 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5810 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5811 # icon MUST be different instance for every item
5812 state_images = gtkgui_helpers.load_iconset(path)
5813 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5814 status_menuitem.connect('activate', self.on_send_custom_status,
5815 [(contact, account)], s)
5816 icon = state_images[s]
5817 status_menuitem.set_image(icon)
5818 status_menuitems.append(status_menuitem)
5819 menu.append(send_custom_status_menuitem)
5821 item = gtk.SeparatorMenuItem() # separator
5822 menu.append(item)
5824 # Execute Command
5825 item = gtk.ImageMenuItem(_('Execute Command...'))
5826 icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
5827 item.set_image(icon)
5828 menu.append(item)
5829 item.connect('activate', self.on_execute_command, contact, account,
5830 contact.resource)
5831 if gajim.account_is_disconnected(account):
5832 item.set_sensitive(False)
5834 # Manage Transport submenu
5835 item = gtk.ImageMenuItem(_('_Manage Transport'))
5836 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES,
5837 gtk.ICON_SIZE_MENU)
5838 item.set_image(icon)
5839 manage_transport_submenu = gtk.Menu()
5840 item.set_submenu(manage_transport_submenu)
5841 menu.append(item)
5843 # Modify Transport
5844 item = gtk.ImageMenuItem(_('_Modify Transport'))
5845 icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
5846 gtk.ICON_SIZE_MENU)
5847 item.set_image(icon)
5848 manage_transport_submenu.append(item)
5849 item.connect('activate', self.on_edit_agent, contact, account)
5850 if gajim.account_is_disconnected(account):
5851 item.set_sensitive(False)
5853 # Rename
5854 item = gtk.ImageMenuItem(_('_Rename'))
5855 # add a special img for rename menuitem
5856 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
5857 manage_transport_submenu.append(item)
5858 item.connect('activate', self.on_rename, 'agent', jid, account)
5859 if gajim.account_is_disconnected(account):
5860 item.set_sensitive(False)
5862 item = gtk.SeparatorMenuItem() # separator
5863 manage_transport_submenu.append(item)
5865 # Block
5866 if blocked:
5867 item = gtk.ImageMenuItem(_('_Unblock'))
5868 item.connect('activate', self.on_unblock, [(contact, account)])
5869 else:
5870 item = gtk.ImageMenuItem(_('_Block'))
5871 item.connect('activate', self.on_block, [(contact, account)])
5873 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5874 item.set_image(icon)
5875 manage_transport_submenu.append(item)
5876 if gajim.account_is_disconnected(account):
5877 item.set_sensitive(False)
5879 # Remove
5880 item = gtk.ImageMenuItem(_('_Remove'))
5881 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5882 item.set_image(icon)
5883 manage_transport_submenu.append(item)
5884 item.connect('activate', self.on_remove_agent, [(contact, account)])
5885 if gajim.account_is_disconnected(account):
5886 item.set_sensitive(False)
5888 item = gtk.SeparatorMenuItem() # separator
5889 menu.append(item)
5891 # Information
5892 information_menuitem = gtk.ImageMenuItem(_('_Information'))
5893 icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
5894 information_menuitem.set_image(icon)
5895 menu.append(information_menuitem)
5896 information_menuitem.connect('activate', self.on_info, contact, account)
5898 event_button = gtkgui_helpers.get_possible_button_event(event)
5900 menu.attach_to_widget(self.tree, None)
5901 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5902 menu.show_all()
5903 menu.popup(None, None, None, event_button, event.time)
5905 def make_groupchat_menu(self, event, titer):
5906 model = self.modelfilter
5908 jid = model[titer][C_JID].decode('utf-8')
5909 account = model[titer][C_ACCOUNT].decode('utf-8')
5910 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5911 menu = gtk.Menu()
5913 if jid in gajim.interface.minimized_controls[account]:
5914 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize'))
5915 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
5916 gtk.ICON_SIZE_MENU)
5917 maximize_menuitem.set_image(icon)
5918 maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
5919 jid, account)
5920 menu.append(maximize_menuitem)
5922 if not gajim.gc_connected[account].get(jid, False):
5923 connect_menuitem = gtk.ImageMenuItem(_('_Reconnect'))
5924 connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \
5925 gtk.ICON_SIZE_MENU)
5926 connect_menuitem.set_image(connect_icon)
5927 connect_menuitem.connect('activate', self.on_reconnect, jid,
5928 account)
5929 menu.append(connect_menuitem)
5930 disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect'))
5931 disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \
5932 gtk.ICON_SIZE_MENU)
5933 disconnect_menuitem.set_image(disconnect_icon)
5934 disconnect_menuitem.connect('activate', self.on_disconnect, jid,
5935 account)
5936 menu.append(disconnect_menuitem)
5938 item = gtk.SeparatorMenuItem() # separator
5939 menu.append(item)
5941 history_menuitem = gtk.ImageMenuItem(_('_History'))
5942 history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \
5943 gtk.ICON_SIZE_MENU)
5944 history_menuitem.set_image(history_icon)
5945 history_menuitem .connect('activate', self.on_history, contact, account)
5946 menu.append(history_menuitem)
5948 event_button = gtkgui_helpers.get_possible_button_event(event)
5950 menu.attach_to_widget(self.tree, None)
5951 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5952 menu.show_all()
5953 menu.popup(None, None, None, event_button, event.time)
5955 def get_and_connect_advanced_menuitem_menu(self, account):
5957 Add FOR ACCOUNT options
5959 xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui')
5960 advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
5962 xml_console_menuitem = xml.get_object('xml_console_menuitem')
5963 archiving_preferences_menuitem = xml.get_object(
5964 'archiving_preferences_menuitem')
5965 privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
5966 administrator_menuitem = xml.get_object('administrator_menuitem')
5967 send_server_message_menuitem = xml.get_object(
5968 'send_server_message_menuitem')
5969 set_motd_menuitem = xml.get_object('set_motd_menuitem')
5970 update_motd_menuitem = xml.get_object('update_motd_menuitem')
5971 delete_motd_menuitem = xml.get_object('delete_motd_menuitem')
5973 xml_console_menuitem.connect('activate',
5974 self.on_xml_console_menuitem_activate, account)
5976 if gajim.connections[account]:
5977 if gajim.connections[account].privacy_rules_supported:
5978 privacy_lists_menuitem.connect('activate',
5979 self.on_privacy_lists_menuitem_activate, account)
5980 else:
5981 privacy_lists_menuitem.set_sensitive(False)
5982 if gajim.connections[account].archive_pref_supported:
5983 archiving_preferences_menuitem.connect('activate',
5984 self.on_archiving_preferences_menuitem_activate, account)
5985 else:
5986 archiving_preferences_menuitem.set_sensitive(False)
5988 if gajim.connections[account].is_zeroconf:
5989 administrator_menuitem.set_sensitive(False)
5990 send_server_message_menuitem.set_sensitive(False)
5991 set_motd_menuitem.set_sensitive(False)
5992 update_motd_menuitem.set_sensitive(False)
5993 delete_motd_menuitem.set_sensitive(False)
5994 else:
5995 send_server_message_menuitem.connect('activate',
5996 self.on_send_server_message_menuitem_activate, account)
5998 set_motd_menuitem.connect('activate',
5999 self.on_set_motd_menuitem_activate, account)
6001 update_motd_menuitem.connect('activate',
6002 self.on_update_motd_menuitem_activate, account)
6004 delete_motd_menuitem.connect('activate',
6005 self.on_delete_motd_menuitem_activate, account)
6007 advanced_menuitem_menu.show_all()
6009 return advanced_menuitem_menu
6011 def add_history_manager_menuitem(self, menu):
6013 Add a seperator and History Manager menuitem BELOW for account menuitems
6015 item = gtk.SeparatorMenuItem() # separator
6016 menu.append(item)
6018 # History manager
6019 item = gtk.ImageMenuItem(_('History Manager'))
6020 icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
6021 gtk.ICON_SIZE_MENU)
6022 item.set_image(icon)
6023 menu.append(item)
6024 item.connect('activate', self.on_history_manager_menuitem_activate)
6026 def add_bookmarks_list(self, gc_sub_menu, account):
6028 Show join new group chat item and bookmarks list for an account
6030 item = gtk.ImageMenuItem(_('_Join New Group Chat'))
6031 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
6032 item.set_image(icon)
6033 item.connect('activate', self.on_join_gc_activate, account)
6035 gc_sub_menu.append(item)
6037 # User has at least one bookmark.
6038 if gajim.connections[account].bookmarks:
6039 item = gtk.SeparatorMenuItem()
6040 gc_sub_menu.append(item)
6042 for bookmark in gajim.connections[account].bookmarks:
6043 # Do not use underline.
6044 item = gtk.MenuItem(bookmark['name'], False)
6045 item.connect('activate', self.on_bookmark_menuitem_activate,
6046 account, bookmark)
6047 gc_sub_menu.append(item)
6049 def set_actions_menu_needs_rebuild(self):
6050 self.actions_menu_needs_rebuild = True
6051 # Just handle new_chat_menuitem to have ctrl+N working even if we don't
6052 # open the menu
6053 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
6054 ag = gtk.accel_groups_from_object(self.window)[0]
6056 if self.new_chat_menuitem_handler_id:
6057 new_chat_menuitem.handler_disconnect(
6058 self.new_chat_menuitem_handler_id)
6059 self.new_chat_menuitem_handler_id = None
6061 new_chat_menuitem.remove_submenu()
6063 connected_accounts = gajim.get_number_of_connected_accounts()
6064 if connected_accounts == 1 or (connected_accounts == 2 and \
6065 gajim.zeroconf_is_connected()):
6066 # only one 'real' (non-zeroconf) account is connected, don't need
6067 # submenus
6068 accounts_list = sorted(gajim.contacts.get_accounts())
6069 for account in accounts_list:
6070 if gajim.account_is_connected(account) and \
6071 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
6072 if not self.new_chat_menuitem_handler_id:
6073 self.new_chat_menuitem_handler_id = new_chat_menuitem.\
6074 connect('activate',
6075 self.on_new_chat_menuitem_activate, account)
6077 def show_appropriate_context_menu(self, event, iters):
6078 # iters must be all of the same type
6079 model = self.modelfilter
6080 type_ = model[iters[0]][C_TYPE]
6081 for titer in iters[1:]:
6082 if model[titer][C_TYPE] != type_:
6083 return
6084 if type_ == 'group' and len(iters) == 1:
6085 self.make_group_menu(event, iters[0])
6086 if type_ == 'groupchat' and len(iters) == 1:
6087 self.make_groupchat_menu(event, iters[0])
6088 elif type_ == 'agent' and len(iters) == 1:
6089 self.make_transport_menu(event, iters[0])
6090 elif type_ in ('contact', 'self_contact') and len(iters) == 1:
6091 self.make_contact_menu(event, iters[0])
6092 elif type_ == 'contact':
6093 self.make_multiple_contact_menu(event, iters)
6094 elif type_ == 'account' and len(iters) == 1:
6095 self.make_account_menu(event, iters[0])
6097 def show_treeview_menu(self, event):
6098 try:
6099 model, list_of_paths = self.tree.get_selection().get_selected_rows()
6100 except TypeError:
6101 self.tree.get_selection().unselect_all()
6102 return
6103 if not len(list_of_paths):
6104 # no row is selected
6105 return
6106 if len(list_of_paths) > 1:
6107 iters = []
6108 for path in list_of_paths:
6109 iters.append(model.get_iter(path))
6110 else:
6111 path = list_of_paths[0]
6112 iters = [model.get_iter(path)]
6113 self.show_appropriate_context_menu(event, iters)
6115 return True
6117 def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier):
6119 Bring up the conference join dialog, when CTRL+J accelerator is being
6120 activated
6122 # find a connected account:
6123 for account in gajim.connections:
6124 if gajim.account_is_connected(account):
6125 break
6126 self.on_join_gc_activate(None, account)
6127 return True
6129 def fill_column(self, col):
6130 for rend in self.renderers_list:
6131 col.pack_start(rend[1], expand=rend[2])
6132 col.add_attribute(rend[1], rend[3], rend[4])
6133 col.set_cell_data_func(rend[1], rend[5], rend[6])
6134 # set renderers propertys
6135 for renderer in self.renderers_propertys.keys():
6136 renderer.set_property(self.renderers_propertys[renderer][0],
6137 self.renderers_propertys[renderer][1])
6139 ################################################################################
6141 ################################################################################
6143 def __init__(self):
6144 self.filtering = False
6145 # Number of renderers plugins added
6146 self.nb_ext_renderers = 0
6147 # [icon, name, type, jid, account, editable, mood_pixbuf,
6148 # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
6149 # padlock_pixbuf]
6150 self.columns = [gtk.Image, str, str, str, str,
6151 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
6152 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf]
6153 self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
6154 self.window = self.xml.get_object('roster_window')
6155 self.hpaned = self.xml.get_object('roster_hpaned')
6156 gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
6157 gajim.interface.msg_win_mgr.connect('window-delete',
6158 self.on_message_window_delete)
6159 self.advanced_menus = [] # We keep them to destroy them
6160 if gajim.config.get('roster_window_skip_taskbar'):
6161 self.window.set_property('skip-taskbar-hint', True)
6162 self.tree = self.xml.get_object('roster_treeview')
6163 sel = self.tree.get_selection()
6164 sel.set_mode(gtk.SELECTION_MULTIPLE)
6165 # sel.connect('changed',
6166 # self.on_treeview_selection_changed)
6168 self._iters = {}
6169 # for merged mode
6170 self._iters['MERGED'] = {'account': None, 'groups': {}}
6171 # holds a list of (jid, account) tupples
6172 self._last_selected_contact = []
6173 self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
6174 'closed': {}}
6176 self.last_save_dir = None
6177 self.editing_path = None # path of row with cell in edit mode
6178 self.add_new_contact_handler_id = False
6179 self.service_disco_handler_id = False
6180 self.new_chat_menuitem_handler_id = False
6181 self.single_message_menuitem_handler_id = False
6182 self.profile_avatar_menuitem_handler_id = False
6183 #FIXME: When list_accel_closures will be wrapped in pygtk
6184 # no need of this variable
6185 self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
6186 self.set_actions_menu_needs_rebuild()
6187 self.regroup = gajim.config.get('mergeaccounts')
6188 self.clicked_path = None # Used remember on wich row we clicked
6189 if len(gajim.connections) < 2:
6190 # Do not merge accounts if only one exists
6191 self.regroup = False
6192 gtkgui_helpers.resize_window(self.window,
6193 gajim.config.get('roster_width'),
6194 gajim.config.get('roster_height'))
6195 if gajim.config.get('save-roster-position'):
6196 gtkgui_helpers.move_window(self.window,
6197 gajim.config.get('roster_x-position'),
6198 gajim.config.get('roster_y-position'))
6200 self.popups_notification_height = 0
6201 self.popup_notification_windows = []
6203 # Remove contact from roster when last event opened
6204 # { (contact, account): { backend: boolean }
6205 self.contacts_to_be_removed = {}
6206 gajim.events.event_removed_subscribe(self.on_event_removed)
6208 # when this value become 0 we quit main application. If it's more than 0
6209 # it means we are waiting for this number of accounts to disconnect
6210 # before quitting
6211 self.quit_on_next_offline = -1
6213 # uf_show, img, show, sensitive
6214 liststore = gtk.ListStore(str, gtk.Image, str, bool)
6215 self.status_combobox = self.xml.get_object('status_combobox')
6217 cell = cell_renderer_image.CellRendererImage(0, 1)
6218 self.status_combobox.pack_start(cell, False)
6220 # img to show is in in 2nd column of liststore
6221 self.status_combobox.add_attribute(cell, 'image', 1)
6222 # if it will be sensitive or not it is in the fourth column
6223 # all items in the 'row' must have sensitive to False
6224 # if we want False (so we add it for img_cell too)
6225 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6227 cell = gtk.CellRendererText()
6228 cell.set_property('xpad', 5) # padding for status text
6229 self.status_combobox.pack_start(cell, True)
6230 # text to show is in in first column of liststore
6231 self.status_combobox.add_attribute(cell, 'text', 0)
6232 # if it will be sensitive or not it is in the fourth column
6233 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6235 self.status_combobox.set_row_separator_func(self._iter_is_separator)
6237 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
6238 uf_show = helpers.get_uf_show(show)
6239 liststore.append([uf_show,
6240 gajim.interface.jabber_state_images['16'][show], show, True])
6241 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6242 liststore.append(['SEPARATOR', None, '', True])
6244 path = gtkgui_helpers.get_icon_path('gajim-kbd_input')
6245 img = gtk.Image()
6246 img.set_from_file(path)
6247 # sensitivity to False because by default we're offline
6248 self.status_message_menuitem_iter = liststore.append(
6249 [_('Change Status Message...'), img, '', False])
6250 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6251 liststore.append(['SEPARATOR', None, '', True])
6253 uf_show = helpers.get_uf_show('offline')
6254 liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
6255 'offline'], 'offline', True])
6257 status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
6258 'invisible', 'separator1', 'change_status_msg', 'separator2',
6259 'offline']
6260 self.status_combobox.set_model(liststore)
6262 # default to offline
6263 number_of_menuitem = status_combobox_items.index('offline')
6264 self.status_combobox.set_active(number_of_menuitem)
6266 # holds index to previously selected item so if
6267 # "change status message..." is selected we can fallback to previously
6268 # selected item and not stay with that item selected
6269 self.previous_status_combobox_active = number_of_menuitem
6271 showOffline = gajim.config.get('showoffline')
6272 showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online')
6274 w = self.xml.get_object('show_offline_contacts_menuitem')
6275 w.set_active(showOffline)
6276 if showOnlyChatAndOnline:
6277 w.set_sensitive(False)
6279 w = self.xml.get_object('show_only_active_contacts_menuitem')
6280 w.set_active(showOnlyChatAndOnline)
6281 if showOffline:
6282 w.set_sensitive(False)
6284 show_transports_group = gajim.config.get('show_transports_group')
6285 self.xml.get_object('show_transports_menuitem').set_active(
6286 show_transports_group)
6288 self.xml.get_object('show_roster_menuitem').set_active(True)
6290 # columns
6291 col = gtk.TreeViewColumn()
6292 # list of renderers with attributes / properties in the form:
6293 # (name, renderer_object, expand?, attribute_name, attribute_value,
6294 # cell_data_func, func_arg)
6295 self.renderers_list = []
6296 self.renderers_propertys ={}
6297 self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF,
6298 'activity': C_ACTIVITY_PIXBUF, 'tune': C_TUNE_PIXBUF,
6299 'location': C_LOCATION_PIXBUF}
6301 renderer_text = gtk.CellRendererText()
6302 self.renderers_propertys[renderer_text] = ('ellipsize',
6303 pango.ELLIPSIZE_END)
6305 def add_avatar_renderer():
6306 self.renderers_list.append(('avatar', gtk.CellRendererPixbuf(),
6307 False, 'pixbuf', C_AVATAR_PIXBUF,
6308 self._fill_avatar_pixbuf_renderer, None))
6310 if gajim.config.get('avatar_position_in_roster') == 'left':
6311 add_avatar_renderer()
6313 self.renderers_list += (
6314 ('icon', cell_renderer_image.CellRendererImage(0, 0), False,
6315 'image', C_IMG, self._iconCellDataFunc, None),
6317 ('name', renderer_text, True,
6318 'markup', C_NAME, self._nameCellDataFunc, None),
6320 ('mood', gtk.CellRendererPixbuf(), False,
6321 'pixbuf', C_MOOD_PIXBUF,
6322 self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF),
6324 ('activity', gtk.CellRendererPixbuf(), False,
6325 'pixbuf', C_ACTIVITY_PIXBUF,
6326 self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF),
6328 ('tune', gtk.CellRendererPixbuf(), False,
6329 'pixbuf', C_TUNE_PIXBUF,
6330 self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF),
6332 ('location', gtk.CellRendererPixbuf(), False,
6333 'pixbuf', C_LOCATION_PIXBUF,
6334 self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF))
6336 if gajim.config.get('avatar_position_in_roster') == 'right':
6337 add_avatar_renderer()
6339 self.renderers_list.append(('padlock', gtk.CellRendererPixbuf(), False,
6340 'pixbuf', C_PADLOCK_PIXBUF,
6341 self._fill_padlock_pixbuf_renderer, None))
6343 # fill and append column
6344 self.fill_column(col)
6345 self.tree.append_column(col)
6347 # do not show gtk arrows workaround
6348 col = gtk.TreeViewColumn()
6349 render_pixbuf = gtk.CellRendererPixbuf()
6350 col.pack_start(render_pixbuf, expand=False)
6351 self.tree.append_column(col)
6352 col.set_visible(False)
6353 self.tree.set_expander_column(col)
6355 # set search function
6356 self.tree.set_search_equal_func(self._search_roster_func)
6358 # signals
6359 self.TARGET_TYPE_URI_LIST = 80
6360 TARGETS = [('MY_TREE_MODEL_ROW',
6361 gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)]
6362 TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
6363 ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)]
6364 self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS,
6365 gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY)
6366 self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT)
6367 self.tree.connect('drag_begin', self.drag_begin)
6368 self.tree.connect('drag_end', self.drag_end)
6369 self.tree.connect('drag_drop', self.drag_drop)
6370 self.tree.connect('drag_data_get', self.drag_data_get_data)
6371 self.tree.connect('drag_data_received', self.drag_data_received_data)
6372 self.dragging = False
6373 self.xml.connect_signals(self)
6374 self.combobox_callback_active = True
6376 self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
6377 self.tooltip = tooltips.RosterTooltip()
6378 # Workaroung: For strange reasons signal is behaving like row-changed
6379 self._toggeling_row = False
6380 self.setup_and_draw_roster()
6382 if gajim.config.get('show_roster_on_startup') == 'always':
6383 self.window.show_all()
6384 elif gajim.config.get('show_roster_on_startup') == 'never':
6385 if gajim.config.get('trayicon') != 'always':
6386 # Without trayicon, user should see the roster!
6387 self.window.show_all()
6388 gajim.config.set('last_roster_visible', True)
6389 else:
6390 if gajim.config.get('last_roster_visible') or \
6391 gajim.config.get('trayicon') != 'always':
6392 self.window.show_all()
6394 if len(gajim.connections) == 0: # if we have no account
6395 def _open_wizard():
6396 gajim.interface.instances['account_creation_wizard'] = \
6397 config.AccountCreationWizardWindow()
6398 # Open wizard only after roster is created, so we can make it
6399 # transient for the roster window
6400 gobject.idle_add(_open_wizard)
6401 if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
6402 # Create zeroconf in config file
6403 from common.zeroconf import connection_zeroconf
6404 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
6406 # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
6407 # conference.
6408 accel_group = gtk.accel_groups_from_object(self.window)[0]
6409 accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK,
6410 gtk.ACCEL_MASK, self.on_ctrl_j)
6412 # Setting CTRL+N to be the shortcut for show Start chat dialog
6413 new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
6414 new_chat_menuitem.add_accelerator('activate', accel_group,
6415 gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
6417 # Setting the search stuff
6418 self.rfilter_entry = self.xml.get_object('rfilter_entry')
6419 self.rfilter_string = ''
6420 self.rfilter_enabled = False
6422 gajim.ged.register_event_handler('presence-received', ged.GUI1,
6423 self._nec_presence_received)
6424 # presence has to be fully handled so that contact is added to occupant
6425 # list before roster can be correctly updated
6426 gajim.ged.register_event_handler('gc-presence-received', ged.GUI2,
6427 self._nec_gc_presence_received)
6428 gajim.ged.register_event_handler('roster-received', ged.GUI1,
6429 self._nec_roster_received)
6430 gajim.ged.register_event_handler('anonymous-auth', ged.GUI1,
6431 self._nec_anonymous_auth)
6432 gajim.ged.register_event_handler('our-show', ged.GUI1,
6433 self._nec_our_show)
6434 gajim.ged.register_event_handler('connection-type', ged.GUI1,
6435 self._nec_connection_type)
6436 gajim.ged.register_event_handler('agent-removed', ged.GUI1,
6437 self._nec_agent_removed)
6438 gajim.ged.register_event_handler('pep-received', ged.GUI1,
6439 self._nec_pep_received)
6440 gajim.ged.register_event_handler('vcard-received', ged.GUI1,
6441 self._nec_vcard_received)
6442 gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
6443 self._nec_gc_subject_received)
6444 gajim.ged.register_event_handler('metacontacts-received', ged.GUI2,
6445 self._nec_metacontacts_received)
6446 gajim.ged.register_event_handler('signed-in', ged.GUI1,
6447 self._nec_signed_in)
6448 gajim.ged.register_event_handler('decrypted-message-received', ged.GUI2,
6449 self._nec_decrypted_message_received)