gajim

view src/roster_window.py @ 10773:1076fc9700f5

merge elghinn's branch (roster versioning) to trunk. Fixes #4661, #3190
author Yann Leboulanger <asterix@lagaule.org>
date Fri, 10 Jul 2009 15:05:01 +0200
parents 5f154c48e9db 06e57851eb8e
children b24f17156faa 5abc3754d886
line source
1 # -*- coding: utf-8 -*-
2 ## src/roster_window.py
3 ##
4 ## Copyright (C) 2003-2008 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>
19 ##
20 ## This file is part of Gajim.
21 ##
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.
25 ##
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.
30 ##
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/>.
33 ##
35 import gtk
36 import pango
37 import gobject
38 import os
39 import sys
40 import time
42 import common.sleepy
43 import history_window
44 import dialogs
45 import vcard
46 import config
47 import disco
48 import gtkgui_helpers
49 import cell_renderer_image
50 import tooltips
51 import message_control
52 import adhoc_commands
53 import features_window
55 from common import gajim
56 from common import helpers
57 from common.exceptions import GajimGeneralException
58 from common import i18n
59 from common import pep
61 from message_window import MessageWindowMgr
63 from common import dbus_support
64 if dbus_support.supported:
65 from music_track_listener import MusicTrackListener
66 import dbus
68 from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC
69 from common.pep import MOODS, ACTIVITIES
71 #(icon, name, type, jid, account, editable, second pixbuf)
72 (
73 C_IMG, # image to show state (online, new message etc)
74 C_NAME, # cellrenderer text that holds contact nickame
75 C_TYPE, # account, group or contact?
76 C_JID, # the jid of the row
77 C_ACCOUNT, # cellrenderer text that holds account name
78 C_MOOD_PIXBUF,
79 C_ACTIVITY_PIXBUF,
80 C_TUNE_PIXBUF,
81 C_AVATAR_PIXBUF, # avatar_pixbuf
82 C_PADLOCK_PIXBUF, # use for account row only
83 ) = range(10)
85 class RosterWindow:
86 '''Class for main window of the GTK+ interface'''
88 def _get_account_iter(self, name, model=None):
89 '''
90 Return the gtk.TreeIter of the given account or None
91 if not found.
93 Keyword arguments:
94 name -- the account name
95 model -- the data model (default TreeFilterModel)
96 '''
97 if not model:
98 model = self.modelfilter
99 if model is None:
100 return
101 account_iter = model.get_iter_root()
102 if self.regroup:
103 return account_iter
104 while account_iter:
105 account_name = model[account_iter][C_ACCOUNT]
106 if account_name and name == account_name.decode('utf-8'):
107 break
108 account_iter = model.iter_next(account_iter)
109 return account_iter
112 def _get_group_iter(self, name, account, account_iter=None, model=None):
113 '''
114 Return the gtk.TreeIter of the given group or None if not found.
116 Keyword arguments:
117 name -- the group name
118 account -- the account name
119 account_iter -- the iter of the account the model (default None)
120 model -- the data model (default TreeFilterModel)
122 '''
123 if not model:
124 model = self.modelfilter
125 if not account_iter:
126 account_iter = self._get_account_iter(account, model)
127 group_iter = model.iter_children(account_iter)
128 # C_NAME column contacts the pango escaped group name
129 while group_iter:
130 group_name = model[group_iter][C_JID].decode('utf-8')
131 if name == group_name:
132 break
133 group_iter = model.iter_next(group_iter)
134 return group_iter
137 def _get_self_contact_iter(self, jid, account, model=None):
138 ''' Return the gtk.TreeIter of SelfContact or None if not found.
140 Keyword arguments:
141 jid -- the jid of SelfContact
142 account -- the account of SelfContact
143 model -- the data model (default TreeFilterModel)
145 '''
147 if not model:
148 model = self.modelfilter
149 iterAcct = self._get_account_iter(account, model)
150 iterC = model.iter_children(iterAcct)
152 # There might be several SelfContacts in merged account view
153 while iterC:
154 if model[iterC][C_TYPE] != 'self_contact':
155 break
156 iter_jid = model[iterC][C_JID]
157 if iter_jid and jid == iter_jid.decode('utf-8'):
158 return iterC
159 iterC = model.iter_next(iterC)
160 return None
163 def _get_contact_iter(self, jid, account, contact=None, model=None):
164 ''' Return a list of gtk.TreeIter of the given contact.
166 Keyword arguments:
167 jid -- the jid without resource
168 account -- the account
169 contact -- the contact (default None)
170 model -- the data model (default TreeFilterModel)
172 '''
173 if not model:
174 model = self.modelfilter
175 # when closing Gajim model can be none (async pbs?)
176 if model is None:
177 return []
179 if jid == gajim.get_jid_from_account(account):
180 contact_iter = self._get_self_contact_iter(jid, account, model)
181 if contact_iter:
182 return [contact_iter]
183 else:
184 return []
186 if not contact:
187 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
188 if not contact:
189 # We don't know this contact
190 return []
192 acct = self._get_account_iter(account, model)
193 found = [] # the contact iters. One per group
194 for group in contact.get_shown_groups():
195 group_iter = self._get_group_iter(group, account, acct, model)
196 contact_iter = model.iter_children(group_iter)
198 while contact_iter:
199 # Loop over all contacts in this group
200 iter_jid = model[contact_iter][C_JID]
201 if iter_jid and jid == iter_jid.decode('utf-8') and \
202 account == model[contact_iter][C_ACCOUNT].decode('utf-8'):
203 # only one iter per group
204 found.append(contact_iter)
205 contact_iter = None
206 elif model.iter_has_child(contact_iter):
207 # it's a big brother and has children
208 contact_iter = model.iter_children(contact_iter)
209 else:
210 # try to find next contact:
211 # other contact in this group or
212 # brother contact
213 next_contact_iter = model.iter_next(contact_iter)
214 if next_contact_iter:
215 contact_iter = next_contact_iter
216 else:
217 # It's the last one.
218 # Go up if we are big brother
219 parent_iter = model.iter_parent(contact_iter)
220 if parent_iter and model[parent_iter][C_TYPE] == 'contact':
221 contact_iter = model.iter_next(parent_iter)
222 else:
223 # we tested all
224 # contacts in this group
225 contact_iter = None
226 return found
229 def _iter_is_separator(self, model, titer):
230 ''' Return True if the given iter is a separator.
232 Keyword arguments:
233 model -- the data model
234 iter -- the gtk.TreeIter to test
235 '''
236 if model[titer][0] == 'SEPARATOR':
237 return True
238 return False
241 def _iter_contact_rows(self, model=None):
242 '''Iterate over all contact rows in given model.
244 Keyword argument
245 model -- the data model (default TreeFilterModel)
246 '''
247 if not model:
248 model = self.modelfilter
249 account_iter = model.get_iter_root()
250 while account_iter:
251 group_iter = model.iter_children(account_iter)
252 while group_iter:
253 contact_iter = model.iter_children(group_iter)
254 while contact_iter:
255 yield model[contact_iter]
256 contact_iter = model.iter_next(
257 contact_iter)
258 group_iter = model.iter_next(group_iter)
259 account_iter = model.iter_next(account_iter)
262 #############################################################################
263 ### Methods for adding and removing roster window items
264 #############################################################################
266 def add_account(self, account):
267 '''
268 Add account to roster and draw it. Do nothing if it is
269 already in.
270 '''
271 if self._get_account_iter(account):
272 # Will happen on reconnect or for merged accounts
273 return
275 if self.regroup:
276 # Merged accounts view
277 show = helpers.get_global_show()
278 self.model.append(None, [
279 gajim.interface.jabber_state_images['16'][show],
280 _('Merged accounts'), 'account', '', 'all',
281 None, None, None, None, None])
282 else:
283 show = gajim.SHOW_LIST[gajim.connections[account].connected]
284 our_jid = gajim.get_jid_from_account(account)
286 tls_pixbuf = None
287 if gajim.account_is_securely_connected(account):
288 # the only way to create a pixbuf from stock
289 tls_pixbuf = self.window.render_icon(
290 gtk.STOCK_DIALOG_AUTHENTICATION,
291 gtk.ICON_SIZE_MENU)
293 self.model.append(None, [
294 gajim.interface.jabber_state_images['16'][show],
295 gobject.markup_escape_text(account), 'account',
296 our_jid, account, None, None, None, None,
297 tls_pixbuf])
299 self.draw_account(account)
302 def add_account_contacts(self, account):
303 '''Add all contacts and groups of the given account to roster,
304 draw them and account.
305 '''
306 self.starting = True
307 jids = gajim.contacts.get_jid_list(account)
309 self.tree.freeze_child_notify()
310 for jid in jids:
311 self.add_contact(jid, account)
312 self.tree.thaw_child_notify()
314 # Do not freeze the GUI when drawing the contacts
315 if jids:
316 # Overhead is big, only invoke when needed
317 self._idle_draw_jids_of_account(jids, account)
319 # Draw all known groups
320 for group in gajim.groups[account]:
321 self.draw_group(group, account)
322 self.draw_account(account)
323 self.starting = False
326 def _add_entity(self, contact, account, groups=None,
327 big_brother_contact=None, big_brother_account=None):
328 '''Add the given contact to roster data model.
330 Contact is added regardless if he is already in roster or not.
331 Return list of newly added iters.
333 Keyword arguments:
334 contact -- the contact to add
335 account -- the contacts account
336 groups -- list of groups to add the contact to.
337 (default groups in contact.get_shown_groups()).
338 Parameter ignored when big_brother_contact is specified.
339 big_brother_contact -- if specified contact is added as child
340 big_brother_contact. (default None)
341 '''
342 added_iters = []
343 if big_brother_contact:
344 # Add contact under big brother
346 parent_iters = self._get_contact_iter(
347 big_brother_contact.jid, big_brother_account,
348 big_brother_contact, self.model)
349 assert len(parent_iters) > 0, 'Big brother is not yet in roster!'
351 # Do not confuse get_contact_iter: Sync groups of family members
352 contact.groups = big_brother_contact.get_shown_groups()[:]
354 for child_iter in parent_iters:
355 it = self.model.append(child_iter, (None, contact.get_shown_name(),
356 'contact', contact.jid, account, None, None, None, None, None))
357 added_iters.append(it)
358 else:
359 # We are a normal contact. Add us to our groups.
360 if not groups:
361 groups = contact.get_shown_groups()
362 for group in groups:
363 child_iterG = self._get_group_iter(group, account,
364 model = self.model)
365 if not child_iterG:
366 # Group is not yet in roster, add it!
367 child_iterA = self._get_account_iter(account, self.model)
368 child_iterG = self.model.append(child_iterA,
369 [gajim.interface.jabber_state_images['16']['closed'],
370 gobject.markup_escape_text(group),
371 'group', group, account, None, None, None, None, None])
372 self.draw_group(group, account)
374 if contact.is_transport():
375 typestr = 'agent'
376 elif contact.is_groupchat():
377 typestr = 'groupchat'
378 else:
379 typestr = 'contact'
381 # we add some values here. see draw_contact
382 # for more
383 i_ = self.model.append(child_iterG, (None,
384 contact.get_shown_name(), typestr,
385 contact.jid, account, None, None, None,
386 None, None))
387 added_iters.append(i_)
389 # Restore the group expand state
390 if account + group in self.collapsed_rows:
391 is_expanded = False
392 else:
393 is_expanded = True
394 if group not in gajim.groups[account]:
395 gajim.groups[account][group] = {'expand': is_expanded}
397 assert len(added_iters), '%s has not been added to roster!' % contact.jid
398 return added_iters
400 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.
412 '''
413 iters = self._get_contact_iter(contact.jid, account, contact, self.model)
414 assert iters, '%s shall be removed but is not in roster' % contact.jid
416 parent_iter = self.model.iter_parent(iters[0])
417 parent_type = self.model[parent_iter][C_TYPE]
419 if groups:
420 # Only remove from specified groups
421 all_iters = iters[:]
422 group_iters = [self._get_group_iter(group, account)
423 for group in groups]
424 iters = [titer for titer in all_iters
425 if self.model.iter_parent(titer) in group_iters]
427 iter_children = self.model.iter_children(iters[0])
429 if iter_children:
430 # We have children. We cannot be removed!
431 return False
432 else:
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)
441 if parent_type == 'group' and \
442 self.model.iter_n_children(parent_i) == 1:
443 group = self.model[parent_i][C_JID].decode('utf-8')
444 if group in gajim.groups[account]:
445 del gajim.groups[account][group]
446 self.model.remove(parent_i)
447 else:
448 self.model.remove(i)
449 return True
451 def _add_metacontact_family(self, family, account):
452 '''
453 Add the give Metacontact family to roster data model.
455 Add Big Brother to his groups and all others under him.
456 Return list of all added (contact, account) tuples with
457 Big Brother as first element.
459 Keyword arguments:
460 family -- the family, see Contacts.get_metacontacts_family()
461 '''
463 nearby_family, big_brother_jid, big_brother_account = \
464 self._get_nearby_family_and_big_brother(family, account)
465 big_brother_contact = gajim.contacts.get_first_contact_from_jid(
466 big_brother_account, big_brother_jid)
468 assert len(self._get_contact_iter(big_brother_jid,
469 big_brother_account, big_brother_contact, self.model)) == 0, \
470 'Big brother %s already in roster\n Family: %s' \
471 % (big_brother_jid, family)
472 self._add_entity(big_brother_contact, big_brother_account)
474 brothers = []
475 # Filter family members
476 for data in nearby_family:
477 _account = data['account']
478 _jid = data['jid']
479 _contact = gajim.contacts.get_first_contact_from_jid(
480 _account, _jid)
482 if not _contact or _contact == big_brother_contact:
483 # Corresponding account is not connected
484 # or brother already added
485 continue
487 assert len(self._get_contact_iter(_jid, _account,
488 _contact, self.model)) == 0, \
489 "%s already in roster.\n Family: %s" % (_jid, nearby_family)
490 self._add_entity(_contact, _account,
491 big_brother_contact = big_brother_contact,
492 big_brother_account = big_brother_account)
493 brothers.append((_contact, _account))
495 brothers.insert(0, (big_brother_contact, big_brother_account))
496 return brothers
498 def _remove_metacontact_family(self, family, account):
499 '''
500 Remove the given Metacontact family from roster data model.
502 See Contacts.get_metacontacts_family() and
503 RosterWindow._remove_entity()
504 '''
505 nearby_family = self._get_nearby_family_and_big_brother(
506 family, account)[0]
508 # Family might has changed (actual big brother not on top).
509 # Remove childs first then big brother
510 family_in_roster = False
511 for data in nearby_family:
512 _account = data['account']
513 _jid = data['jid']
514 _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
516 iters = self._get_contact_iter(_jid, _account, _contact, self.model)
517 if not iters or not _contact:
518 # Family might not be up to date.
519 # Only try to remove what is actually in the roster
520 continue
521 assert iters, '%s shall be removed but is not in roster \
522 \n Family: %s' % (_jid, family)
524 family_in_roster = True
526 parent_iter = self.model.iter_parent(iters[0])
527 parent_type = self.model[parent_iter][C_TYPE]
529 if parent_type != 'contact':
530 # The contact on top
531 old_big_account = _account
532 old_big_contact = _contact
533 old_big_jid = _jid
534 continue
536 ok = self._remove_entity(_contact, _account)
537 assert ok, '%s was not removed' % _jid
538 assert len(self._get_contact_iter(_jid, _account, _contact,
539 self.model)) == 0, '%s is removed but still in roster' % _jid
541 if not family_in_roster:
542 return False
544 assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \
545 (nearby_family, family)
546 iters = self._get_contact_iter(old_big_jid, old_big_account,
547 old_big_contact, self.model)
548 assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
549 old_big_jid
550 assert not self.model.iter_children(iters[0]),\
551 'Old Big Brother %s still has children' % old_big_jid
553 ok = self._remove_entity(old_big_contact, old_big_account)
554 assert ok, "Old Big Brother %s not removed" % old_big_jid
555 assert len(self._get_contact_iter(old_big_jid, old_big_account,
556 old_big_contact, self.model)) == 0,\
557 'Old Big Brother %s is removed but still in roster' % old_big_jid
559 return True
562 def _recalibrate_metacontact_family(self, family, account):
563 '''Regroup metacontact family if necessary.'''
565 brothers = []
566 nearby_family, big_brother_jid, big_brother_account = \
567 self._get_nearby_family_and_big_brother(family, account)
568 child_iters = self._get_contact_iter(big_brother_jid, big_brother_account,
569 model = self.model)
570 parent_iter = self.model.iter_parent(child_iters[0])
571 parent_type = self.model[parent_iter][C_TYPE]
573 # Check if the current BigBrother has even been before.
574 if parent_type == 'contact':
575 for data in nearby_family:
576 # recalibrate after remove to keep highlight
577 if data['jid'] in gajim.to_be_removed[data['account']]:
578 return
580 self._remove_metacontact_family(family, account)
581 brothers = self._add_metacontact_family(family, account)
583 for c, acc in brothers:
584 self.draw_completely(c.jid, acc)
587 def _get_nearby_family_and_big_brother(self, family, account):
588 '''Return the nearby family and its Big Brother
590 Nearby family is the part of the family that is grouped with the metacontact.
591 A metacontact may be over different accounts. If regroup is s False the
592 given family is split account wise.
594 (nearby_family, big_brother_jid, big_brother_account)
595 '''
596 if self.regroup:
597 # group all together
598 nearby_family = family
599 else:
600 # we want one nearby_family per account
601 nearby_family = [data for data in family
602 if account == data['account']]
604 big_brother_data = gajim.contacts.get_metacontacts_big_brother(
605 nearby_family)
606 big_brother_jid = big_brother_data['jid']
607 big_brother_account = big_brother_data['account']
609 return (nearby_family, big_brother_jid, big_brother_account)
612 def _add_self_contact(self, account):
613 '''Add account's SelfContact to roster and draw it and the account.
615 Return the SelfContact contact instance
616 '''
617 jid = gajim.get_jid_from_account(account)
618 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
620 assert len(self._get_contact_iter(jid, account, contact, self.model)) == \
621 0, 'Self contact %s already in roster' % jid
623 child_iterA = self._get_account_iter(account, self.model)
624 self.model.append(child_iterA, (None, gajim.nicks[account],
625 'self_contact', jid, account, None, None, None, None,
626 None))
628 self.draw_completely(jid, account)
629 self.draw_account(account)
631 return contact
634 def add_contact(self, jid, account):
635 '''Add contact to roster and draw him.
637 Add contact to all its group and redraw the groups, the contact and the
638 account. If it's a Metacontact, add and draw the whole family.
639 Do nothing if the contact is already in roster.
641 Return the added contact instance. If it is a Metacontact return
642 Big Brother.
644 Keyword arguments:
645 jid -- the contact's jid or SelfJid to add SelfContact
646 account -- the corresponding account.
648 '''
649 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
650 if len(self._get_contact_iter(jid, account, contact, self.model)):
651 # If contact already in roster, do nothing
652 return
654 if jid == gajim.get_jid_from_account(account):
655 if contact.resource != gajim.connections[account].server_resource:
656 return self._add_self_contact(account)
657 return
659 is_observer = contact.is_observer()
660 if is_observer:
661 # if he has a tag, remove it
662 tag = gajim.contacts.get_metacontacts_tag(account, jid)
663 if tag:
664 gajim.contacts.remove_metacontact(account, jid)
666 # Add contact to roster
667 family = gajim.contacts.get_metacontacts_family(account, jid)
668 contacts = []
669 if family:
670 # We have a family. So we are a metacontact.
671 # Add all family members that we shall be grouped with
672 if self.regroup:
673 # remove existing family members to regroup them
674 self._remove_metacontact_family(family, account)
675 contacts = self._add_metacontact_family(family, account)
676 else:
677 # We are a normal contact
678 contacts = [(contact, account),]
679 self._add_entity(contact, account)
681 # Draw the contact and its groups contact
682 if not self.starting:
683 for c, acc in contacts:
684 self.draw_completely(c.jid, acc)
685 for group in contact.get_shown_groups():
686 self.draw_group(group, account)
687 self._adjust_group_expand_collapse_state(group, account)
688 self.draw_account(account)
690 return contacts[0][0] # it's contact/big brother with highest priority
692 def remove_contact(self, jid, account, force=False, backend=False):
693 '''Remove contact from roster.
695 Remove contact from all its group. Remove empty groups or redraw
696 otherwise.
697 Draw the account.
698 If it's a Metacontact, remove the whole family.
699 Do nothing if the contact is not in roster.
701 Keyword arguments:
702 jid -- the contact's jid or SelfJid to remove SelfContact
703 account -- the corresponding account.
704 force -- remove contact even it has pending evens (Default False)
705 backend -- also remove contact instance (Default False)
707 '''
708 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
709 if not contact:
710 return
712 if not force and (self.contact_has_pending_roster_events(contact,
713 account) or gajim.interface.msg_win_mgr.get_control(jid, account)):
714 # Contact has pending events or window
715 #TODO: or single message windows? Bur they are not listed for the
716 # moment
717 key = (jid, account)
718 if not key in self.contacts_to_be_removed:
719 self.contacts_to_be_removed[key] = {'backend': backend}
720 # if more pending event, don't remove from roster
721 if self.contact_has_pending_roster_events(contact, account):
722 return False
724 iters = self._get_contact_iter(jid, account, contact, self.model)
725 if iters:
726 # no more pending events
727 # Remove contact from roster directly
728 family = gajim.contacts.get_metacontacts_family(account, jid)
729 if family:
730 # We have a family. So we are a metacontact.
731 self._remove_metacontact_family(family, account)
732 else:
733 self._remove_entity(contact, account)
735 if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\
736 or force):
737 # If a window is still opened: don't remove contact instance
738 # Remove contact before redrawing, otherwise the old
739 # numbers will still be show
740 gajim.contacts.remove_jid(account, jid, remove_meta=True)
741 rest_of_family = [data for data in family
742 if account != data['account'] or jid != data['jid']]
743 if iters and rest_of_family:
744 # reshow the rest of the family
745 brothers = self._add_metacontact_family(rest_of_family, account)
746 for c, acc in brothers:
747 self.draw_completely(c.jid, acc)
749 if iters:
750 # Draw all groups of the contact
751 for group in contact.get_shown_groups():
752 self.draw_group(group, account)
753 self.draw_account(account)
755 return True
757 def add_groupchat(self, jid, account, status=''):
758 '''Add groupchat to roster and draw it.
759 Return the added contact instance.
760 '''
761 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
762 # Do not show gc if we are disconnected and minimize it
763 if gajim.account_is_connected(account):
764 show = 'online'
765 else:
766 show = 'offline'
767 status = ''
769 if contact is None:
770 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
771 if gc_control:
772 # there is a window that we can minimize
773 gajim.interface.minimized_controls[account][jid] = gc_control
774 name = gc_control.name
775 elif jid in gajim.interface.minimized_controls[account]:
776 name = gajim.interface.minimized_controls[account][jid].name
777 else:
778 name = jid.split('@')[0]
779 # New groupchat
780 contact = gajim.contacts.create_contact(jid=jid, name=name,
781 groups=[_('Groupchats')], show=show, status=status, sub='none')
782 gajim.contacts.add_contact(account, contact)
783 self.add_contact(jid, account)
784 else:
785 contact.show = show
786 contact.status = status
787 self.adjust_and_draw_contact_context(jid, account)
789 return contact
792 def remove_groupchat(self, jid, account):
793 '''Remove groupchat from roster and redraw account and group.'''
794 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
795 if contact.is_groupchat():
796 if jid in gajim.interface.minimized_controls[account]:
797 del gajim.interface.minimized_controls[account][jid]
798 self.remove_contact(jid, account, force=True, backend=True)
799 return True
800 else:
801 return False
804 # FIXME: This function is yet unused! Port to new API
805 def add_transport(self, jid, account):
806 '''Add transport to roster and draw it.
807 Return the added contact instance.'''
808 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
809 if contact is None:
810 contact = gajim.contacts.create_contact(jid=jid, name=jid,
811 groups=[_('Transports')], show='offline', status='offline',
812 sub='from')
813 gajim.contacts.add_contact(account, contact)
814 self.add_contact(jid, account)
815 return contact
817 def remove_transport(self, jid, account):
818 '''Remove transport from roster and redraw account and group.'''
819 self.remove_contact(jid, account, force=True, backend=True)
820 return True
822 def add_contact_to_groups(self, jid, account, groups, update=True):
823 '''Add contact to given groups and redraw them.
825 Contact on server is updated too. When the contact has a family,
826 the action will be performed for all members.
828 Keyword Arguments:
829 jid -- the jid
830 account -- the corresponding account
831 groups -- list of Groups to add the contact to.
832 update -- update contact on the server
834 '''
835 self.remove_contact(jid, account, force=True)
836 for contact in gajim.contacts.get_contacts(account, jid):
837 for group in groups:
838 if group not in contact.groups:
839 # we might be dropped from meta to group
840 contact.groups.append(group)
841 if update:
842 gajim.connections[account].update_contact(jid, contact.name,
843 contact.groups)
845 self.add_contact(jid, account)
847 for group in groups:
848 self._adjust_group_expand_collapse_state(group, account)
850 def remove_contact_from_groups(self, jid, account, groups, update=True):
851 '''Remove contact from given groups and redraw them.
853 Contact on server is updated too. When the contact has a family,
854 the action will be performed for all members.
856 Keyword Arguments:
857 jid -- the jid
858 account -- the corresponding account
859 groups -- list of Groups to remove the contact from
860 update -- update contact on the server
862 '''
863 self.remove_contact(jid, account, force=True)
864 for contact in gajim.contacts.get_contacts(account, jid):
865 for group in groups:
866 if group in contact.groups:
867 # Needed when we remove from "General" or "Observers"
868 contact.groups.remove(group)
869 if update:
870 gajim.connections[account].update_contact(jid, contact.name,
871 contact.groups)
872 self.add_contact(jid, account)
874 # Also redraw old groups
875 for group in groups:
876 self.draw_group(group, account)
878 # FIXME: maybe move to gajim.py
879 def remove_newly_added(self, jid, account):
880 if jid in gajim.newly_added[account]:
881 gajim.newly_added[account].remove(jid)
882 self.draw_contact(jid, account)
884 # FIXME: maybe move to gajim.py
885 def remove_to_be_removed(self, jid, account):
886 if account not in gajim.interface.instances:
887 # Account has been deleted during the timeout that called us
888 return
889 if jid in gajim.newly_added[account]:
890 return
891 if jid in gajim.to_be_removed[account]:
892 gajim.to_be_removed[account].remove(jid)
893 family = gajim.contacts.get_metacontacts_family(account, jid)
894 if family:
895 # Peform delayed recalibration
896 self._recalibrate_metacontact_family(family, account)
897 self.draw_contact(jid, account)
899 #FIXME: integrate into add_contact()
900 def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
901 keyID = ''
902 attached_keys = gajim.config.get_per('accounts', account,
903 'attached_gpg_keys').split()
904 if jid in attached_keys:
905 keyID = attached_keys[attached_keys.index(jid) + 1]
906 contact = gajim.contacts.create_contact(jid=jid, name=nick,
907 groups=[_('Not in Roster')], show='not in roster', status='',
908 sub='none', resource=resource, keyID=keyID)
909 gajim.contacts.add_contact(account, contact)
910 self.add_contact(contact.jid, account)
911 return contact
914 ################################################################################
915 ### Methods for adding and removing roster window items
916 ################################################################################
918 def draw_account(self, account):
919 child_iter = self._get_account_iter(account, self.model)
920 if not child_iter:
921 assert False, 'Account iter of %s could not be found.' % account
922 return
924 num_of_accounts = gajim.get_number_of_connected_accounts()
925 num_of_secured = gajim.get_number_of_securely_connected_accounts()
927 if gajim.account_is_securely_connected(account) and not self.regroup or \
928 self.regroup and num_of_secured and num_of_secured == num_of_accounts:
929 tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
930 gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
931 self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf
932 else:
933 self.model[child_iter][C_PADLOCK_PIXBUF] = None
935 if self.regroup:
936 account_name = _('Merged accounts')
937 accounts = []
938 else:
939 account_name = account
940 accounts = [account]
942 if account in self.collapsed_rows and \
943 self.model.iter_has_child(child_iter):
944 account_name = '[%s]' % account_name
946 if (gajim.account_is_connected(account) or (self.regroup and \
947 gajim.get_number_of_connected_accounts())) and gajim.config.get(
948 'show_contacts_number'):
949 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
950 accounts = accounts)
951 account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
953 self.model[child_iter][C_NAME] = account_name
955 if gajim.config.get('show_mood_in_roster') \
956 and 'mood' in gajim.connections[account].mood \
957 and gajim.connections[account].mood['mood'].strip() in MOODS:
959 self.model[child_iter][C_MOOD_PIXBUF] = gtkgui_helpers.load_mood_icon(
960 gajim.connections[account].mood['mood'].strip()).get_pixbuf()
962 elif gajim.config.get('show_mood_in_roster') \
963 and 'mood' in gajim.connections[account].mood:
964 self.model[child_iter][C_MOOD_PIXBUF] = \
965 gtkgui_helpers.load_mood_icon('unknown'). \
966 get_pixbuf()
967 else:
968 self.model[child_iter][C_MOOD_PIXBUF] = None
970 if gajim.config.get('show_activity_in_roster') \
971 and 'activity' in gajim.connections[account].activity \
972 and gajim.connections[account].activity['activity'].strip() \
973 in ACTIVITIES:
974 if 'subactivity' in gajim.connections[account].activity \
975 and gajim.connections[account].activity['subactivity'].strip() \
976 in ACTIVITIES[gajim.connections[account].activity['activity'].strip()]:
977 self.model[child_iter][C_ACTIVITY_PIXBUF] = \
978 gtkgui_helpers.load_activity_icon(
979 gajim.connections[account].activity['activity'].strip(),
980 gajim.connections[account].activity['subactivity'].strip()). \
981 get_pixbuf()
982 else:
983 self.model[child_iter][C_ACTIVITY_PIXBUF] = \
984 gtkgui_helpers.load_activity_icon(
985 gajim.connections[account].activity['activity'].strip()). \
986 get_pixbuf()
987 elif gajim.config.get('show_activity_in_roster') \
988 and 'activity' in gajim.connections[account].activity:
989 self.model[child_iter][C_ACTIVITY_PIXBUF] = \
990 gtkgui_helpers.load_activity_icon('unknown'). \
991 get_pixbuf()
992 else:
993 self.model[child_iter][C_ACTIVITY_PIXBUF] = None
995 if gajim.config.get('show_tunes_in_roster') \
996 and ('artist' in gajim.connections[account].tune \
997 or 'title' in gajim.connections[account].tune):
998 path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
999 self.model[child_iter][C_TUNE_PIXBUF] = \
1000 gtk.gdk.pixbuf_new_from_file(path)
1001 else:
1002 self.model[child_iter][C_TUNE_PIXBUF] = None
1004 return False
1006 def draw_group(self, group, account):
1007 child_iter = self._get_group_iter(group, account, model=self.model)
1008 if not child_iter:
1009 # Eg. We redraw groups after we removed a entitiy
1010 # and its empty groups
1011 return
1012 if self.regroup:
1013 accounts = []
1014 else:
1015 accounts = [account]
1016 text = gobject.markup_escape_text(group)
1017 if helpers.group_is_blocked(account, group):
1018 text = '<span strikethrough="true">%s</span>' % text
1019 if gajim.config.get('show_contacts_number'):
1020 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
1021 accounts = accounts, groups = [group])
1022 text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
1024 self.model[child_iter][C_NAME] = text
1025 return False
1027 def draw_parent_contact(self, jid, account):
1028 child_iters = self._get_contact_iter(jid, account, model=self.model)
1029 if not child_iters:
1030 return False
1031 parent_iter = self.model.iter_parent(child_iters[0])
1032 if self.model[parent_iter][C_TYPE] != 'contact':
1033 # parent is not a contact
1034 return
1035 parent_jid = self.model[parent_iter][C_JID].decode('utf-8')
1036 parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8')
1037 self.draw_contact(parent_jid, parent_account)
1038 return False
1040 def draw_contact(self, jid, account, selected=False, focus=False):
1041 '''draw the correct state image, name BUT not avatar'''
1042 # focus is about if the roster window has toplevel-focus or not
1043 # FIXME: We really need a custom cell_renderer
1045 contact_instances = gajim.contacts.get_contacts(account, jid)
1046 contact = gajim.contacts.get_highest_prio_contact_from_contacts(
1047 contact_instances)
1049 child_iters = self._get_contact_iter(jid, account, contact, self.model)
1050 if not child_iters:
1051 return False
1053 name = gobject.markup_escape_text(contact.get_shown_name())
1055 # gets number of unread gc marked messages
1056 if jid in gajim.interface.minimized_controls[account] and \
1057 gajim.interface.minimized_controls[account][jid]:
1058 nb_unread = len(gajim.events.get_events(account, jid,
1059 ['printed_marked_gc_msg']))
1060 nb_unread += gajim.interface.minimized_controls \
1061 [account][jid].get_nb_unread_pm()
1063 if nb_unread == 1:
1064 name = '%s *' % name
1065 elif nb_unread > 1:
1066 name = '%s [%s]' % (name, str(nb_unread))
1068 # Strike name if blocked
1069 strike = False
1070 if helpers.jid_is_blocked(account, jid):
1071 strike = True
1072 else:
1073 for group in contact.get_shown_groups():
1074 if helpers.group_is_blocked(account, group):
1075 strike = True
1076 break
1077 if strike:
1078 name = '<span strikethrough="true">%s</span>' % name
1080 # Show resource counter
1081 nb_connected_contact = 0
1082 for c in contact_instances:
1083 if c.show not in ('error', 'offline'):
1084 nb_connected_contact += 1
1085 if nb_connected_contact > 1:
1086 name += ' (' + unicode(nb_connected_contact) + ')'
1088 # show (account_name) if there are 2 contact with same jid
1089 # in merged mode
1090 if self.regroup:
1091 add_acct = False
1092 # look through all contacts of all accounts
1093 for account_ in gajim.connections:
1094 # useless to add account name
1095 if account_ == account:
1096 continue
1097 for jid_ in gajim.contacts.get_jid_list(account_):
1098 contact_ = gajim.contacts.get_first_contact_from_jid(
1099 account_, jid_)
1100 if contact_.get_shown_name() == contact.get_shown_name() and \
1101 (jid_, account_) != (jid, account):
1102 add_acct = True
1103 break
1104 if add_acct:
1105 # No need to continue in other account
1106 # if we already found one
1107 break
1108 if add_acct:
1109 name += ' (' + account + ')'
1111 # add status msg, if not empty, under contact name in
1112 # the treeview
1113 if contact.status and gajim.config.get('show_status_msgs_in_roster'):
1114 status = contact.status.strip()
1115 if status != '':
1116 status = helpers.reduce_chars_newlines(status,
1117 max_lines = 1)
1118 # escape markup entities and make them small
1119 # italic and fg color color is calcuted to be
1120 # always readable
1121 color = gtkgui_helpers._get_fade_color(self.tree, selected, focus)
1122 colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue)
1123 name += '\n<span size="small" style="italic" ' \
1124 'foreground="%s">%s</span>' % (
1125 colorstring,
1126 gobject.markup_escape_text(status))
1128 icon_name = helpers.get_icon_name_to_show(contact, account)
1129 # look if another resource has awaiting events
1130 for c in contact_instances:
1131 c_icon_name = helpers.get_icon_name_to_show(c, account)
1132 if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
1133 icon_name = c_icon_name
1134 break
1136 # Check for events of collapsed (hidden) brothers
1137 family = gajim.contacts.get_metacontacts_family(account, jid)
1138 is_big_brother = False
1139 have_visible_children = False
1140 if family:
1141 bb_jid, bb_account = \
1142 self._get_nearby_family_and_big_brother(family, account)[1:]
1143 is_big_brother = (jid, account) == (bb_jid, bb_account)
1144 iters = self._get_contact_iter(jid, account)
1145 have_visible_children = iters \
1146 and self.modelfilter.iter_has_child(iters[0])
1148 if have_visible_children:
1149 # We are the big brother and have a visible family
1150 for child_iter in child_iters:
1151 child_path = self.model.get_path(child_iter)
1152 path = self.modelfilter.convert_child_path_to_path(child_path)
1154 if not self.tree.row_expanded(path) and icon_name != 'event':
1155 iterC = self.model.iter_children(child_iter)
1156 while iterC:
1157 # a child has awaiting messages?
1158 jidC = self.model[iterC][C_JID].decode('utf-8')
1159 accountC = self.model[iterC][C_ACCOUNT].decode('utf-8')
1160 if len(gajim.events.get_events(accountC, jidC)):
1161 icon_name = 'event'
1162 break
1163 iterC = self.model.iter_next(iterC)
1165 if self.tree.row_expanded(path):
1166 state_images = self.get_appropriate_state_images(
1167 jid, size = 'opened',
1168 icon_name = icon_name)
1169 else:
1170 state_images = self.get_appropriate_state_images(
1171 jid, size = 'closed',
1172 icon_name = icon_name)
1174 # Expand/collapse icon might differ per iter
1175 # (group)
1176 img = state_images[icon_name]
1177 self.model[child_iter][C_IMG] = img
1178 self.model[child_iter][C_NAME] = name
1179 else:
1180 # A normal contact or little brother
1181 state_images = self.get_appropriate_state_images(jid,
1182 icon_name = icon_name)
1184 # All iters have the same icon (no expand/collapse)
1185 img = state_images[icon_name]
1186 for child_iter in child_iters:
1187 self.model[child_iter][C_IMG] = img
1188 self.model[child_iter][C_NAME] = name
1190 # We are a little brother
1191 if family and not is_big_brother and not self.starting:
1192 self.draw_parent_contact(jid, account)
1194 for group in contact.get_shown_groups():
1195 # We need to make sure that _visible_func is called for
1196 # our groups otherwise we might not be shown
1197 iterG = self._get_group_iter(group, account, model=self.model)
1198 if iterG:
1199 # it's not self contact
1200 self.model[iterG][C_JID] = self.model[iterG][C_JID]
1202 return False
1205 def draw_mood(self, jid, account):
1206 iters = self._get_contact_iter(jid, account, model=self.model)
1207 if not iters or not gajim.config.get('show_mood_in_roster'):
1208 return
1209 jid = self.model[iters[0]][C_JID]
1210 jid = jid.decode('utf-8')
1211 contact = gajim.contacts.get_contact(account, jid)
1212 if 'mood' in contact.mood and contact.mood['mood'].strip() in MOODS:
1213 pixbuf = gtkgui_helpers.load_mood_icon(
1214 contact.mood['mood'].strip()).get_pixbuf()
1215 elif 'mood' in contact.mood:
1216 pixbuf = gtkgui_helpers.load_mood_icon(
1217 'unknown').get_pixbuf()
1218 else:
1219 pixbuf = None
1220 for child_iter in iters:
1221 self.model[child_iter][C_MOOD_PIXBUF] = pixbuf
1222 return False
1225 def draw_activity(self, jid, account):
1226 iters = self._get_contact_iter(jid, account, model=self.model)
1227 if not iters or not gajim.config.get('show_activity_in_roster'):
1228 return
1229 jid = self.model[iters[0]][C_JID]
1230 jid = jid.decode('utf-8')
1231 contact = gajim.contacts.get_contact(account, jid)
1232 if 'activity' in contact.activity \
1233 and contact.activity['activity'].strip() in ACTIVITIES:
1234 if 'subactivity' in contact.activity \
1235 and contact.activity['subactivity'].strip() in \
1236 ACTIVITIES[contact.activity['activity'].strip()]:
1237 pixbuf = gtkgui_helpers.load_activity_icon(
1238 contact.activity['activity'].strip(),
1239 contact.activity['subactivity'].strip()).get_pixbuf()
1240 else:
1241 pixbuf = gtkgui_helpers.load_activity_icon(
1242 contact.activity['activity'].strip()).get_pixbuf()
1243 elif 'activity' in contact.activity:
1244 pixbuf = gtkgui_helpers.load_activity_icon(
1245 'unknown').get_pixbuf()
1246 else:
1247 pixbuf = None
1248 for child_iter in iters:
1249 self.model[child_iter][C_ACTIVITY_PIXBUF] = pixbuf
1250 return False
1253 def draw_tune(self, jid, account):
1254 iters = self._get_contact_iter(jid, account, model=self.model)
1255 if not iters or not gajim.config.get('show_tunes_in_roster'):
1256 return
1257 jid = self.model[iters[0]][C_JID]
1258 jid = jid.decode('utf-8')
1259 contact = gajim.contacts.get_contact(account, jid)
1260 if 'artist' in contact.tune or 'title' in contact.tune:
1261 path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
1262 pixbuf = gtk.gdk.pixbuf_new_from_file(path)
1263 else:
1264 pixbuf = None
1265 for child_iter in iters:
1266 self.model[child_iter][C_TUNE_PIXBUF] = pixbuf
1267 return False
1270 def draw_avatar(self, jid, account):
1271 iters = self._get_contact_iter(jid, account, model=self.model)
1272 if not iters or not gajim.config.get('show_avatars_in_roster'):
1273 return
1274 jid = self.model[iters[0]][C_JID]
1275 jid = jid.decode('utf-8')
1276 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
1277 if pixbuf is None or pixbuf == 'ask':
1278 scaled_pixbuf = None
1279 else:
1280 scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
1281 for child_iter in iters:
1282 self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf
1283 return False
1285 def draw_completely(self, jid, account):
1286 self.draw_contact(jid, account)
1287 self.draw_mood(jid, account)
1288 self.draw_activity(jid, account)
1289 self.draw_tune(jid, account)
1290 self.draw_avatar(jid, account)
1292 def adjust_and_draw_contact_context(self, jid, account):
1293 '''Draw contact, account and groups of given jid
1294 Show contact if it has pending events
1295 '''
1296 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
1297 if not contact:
1298 # idle draw or just removed SelfContact
1299 return
1301 family = gajim.contacts.get_metacontacts_family(account, jid)
1302 if family:
1303 # There might be a new big brother
1304 self._recalibrate_metacontact_family(family, account)
1305 self.draw_contact(jid, account)
1306 self.draw_account(account)
1308 for group in contact.get_shown_groups():
1309 self.draw_group(group, account)
1310 self._adjust_group_expand_collapse_state(group, account)
1312 def _idle_draw_jids_of_account(self, jids, account):
1313 '''Draw given contacts and their avatars in a lazy fashion.
1315 Keyword arguments:
1316 jids -- a list of jids to draw
1317 account -- the corresponding account
1318 '''
1319 def _draw_all_contacts(jids, account):
1320 for jid in jids:
1321 family = gajim.contacts.get_metacontacts_family(account, jid)
1322 if family:
1323 # For metacontacts over several accounts:
1324 # When we connect a new account existing brothers
1325 # must be redrawn (got removed and readded)
1326 for data in family:
1327 self.draw_completely(data['jid'], data['account'])
1328 else:
1329 self.draw_completely(jid, account)
1330 yield True
1331 yield False
1333 task = _draw_all_contacts(jids, account)
1334 gobject.idle_add(task.next)
1336 def setup_and_draw_roster(self):
1337 '''create new empty model and draw roster'''
1338 self.modelfilter = None
1339 # (icon, name, type, jid, account, editable, mood_pixbuf,
1340 # activity_pixbuf, tune_pixbuf avatar_pixbuf, padlock_pixbuf)
1341 self.model = gtk.TreeStore(gtk.Image, str, str, str, str,
1342 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
1343 gtk.gdk.Pixbuf, gtk.gdk.Pixbuf)
1345 self.model.set_sort_func(1, self._compareIters)
1346 self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
1347 self.modelfilter = self.model.filter_new()
1348 self.modelfilter.set_visible_func(self._visible_func)
1349 self.modelfilter.connect('row-has-child-toggled',
1350 self.on_modelfilter_row_has_child_toggled)
1351 self.tree.set_model(self.modelfilter)
1353 for acct in gajim.connections:
1354 self.add_account(acct)
1355 self.add_account_contacts(acct)
1356 # Recalculate column width for ellipsizing
1357 self.tree.columns_autosize()
1360 def select_contact(self, jid, account):
1361 '''Select contact in roster. If contact is hidden but has events,
1362 show him.'''
1363 # Refiltering SHOULD NOT be needed:
1364 # When a contact gets a new event he will be redrawn and his
1365 # icon changes, so _visible_func WILL be called on him anyway
1366 iters = self._get_contact_iter(jid, account)
1367 if not iters:
1368 # Not visible in roster
1369 return
1370 path = self.modelfilter.get_path(iters[0])
1371 if self.dragging or not gajim.config.get('scroll_roster_to_last_message'):
1372 # do not change selection while DND'ing
1373 return
1374 # Expand his parent, so this path is visible, don't expand it.
1375 self.tree.expand_to_path(path[:-1])
1376 self.tree.scroll_to_cell(path)
1377 self.tree.set_cursor(path)
1380 def _adjust_account_expand_collapse_state(self, account):
1381 '''Expand/collapse account row based on self.collapsed_rows'''
1382 iterA = self._get_account_iter(account)
1383 if not iterA:
1384 # thank you modelfilter
1385 return
1386 path = self.modelfilter.get_path(iterA)
1387 if account in self.collapsed_rows:
1388 self.tree.collapse_row(path)
1389 else:
1390 self.tree.expand_row(path, False)
1391 return False
1394 def _adjust_group_expand_collapse_state(self, group, account):
1395 '''Expand/collapse group row based on self.collapsed_rows'''
1396 iterG = self._get_group_iter(group, account)
1397 if not iterG:
1398 # Group not visible
1399 return
1400 path = self.modelfilter.get_path(iterG)
1401 if account + group in self.collapsed_rows:
1402 self.tree.collapse_row(path)
1403 else:
1404 self.tree.expand_row(path, False)
1405 return False
1407 ##############################################################################
1408 ### Roster and Modelfilter handling
1409 ##############################################################################
1411 def _search_roster_func(self, model, column, key, titer):
1412 key = key.decode('utf-8').lower()
1413 name = model[titer][C_NAME].decode('utf-8').lower()
1414 return not (key in name)
1416 def refilter_shown_roster_items(self):
1417 self.filtering = True
1418 self.modelfilter.refilter()
1419 self.filtering = False
1421 def contact_has_pending_roster_events(self, contact, account):
1422 '''Return True if the contact or one if it resources has pending events'''
1423 # jid has pending events
1424 if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
1425 return True
1426 # check events of all resources
1427 for contact_ in gajim.contacts.get_contacts(account, contact.jid):
1428 if contact_.resource and gajim.events.get_nb_roster_events(account,
1429 contact_.get_full_jid()) > 0:
1430 return True
1431 return False
1433 def contact_is_visible(self, contact, account):
1434 if self.contact_has_pending_roster_events(contact, account):
1435 return True
1437 if contact.show in ('offline', 'error'):
1438 if contact.jid in gajim.to_be_removed[account]:
1439 return True
1440 return False
1441 if gajim.config.get('show_only_chat_and_online') and contact.show in (
1442 'away', 'xa', 'busy'):
1443 return False
1444 return True
1446 def _visible_func(self, model, titer):
1447 '''Determine whether iter should be visible in the treeview'''
1448 type_ = model[titer][C_TYPE]
1449 if not type_:
1450 return False
1451 if type_ == 'account':
1452 # Always show account
1453 return True
1455 account = model[titer][C_ACCOUNT]
1456 if not account:
1457 return False
1459 account = account.decode('utf-8')
1460 jid = model[titer][C_JID]
1461 if not jid:
1462 return False
1463 jid = jid.decode('utf-8')
1464 if type_ == 'group':
1465 group = jid
1466 if group == _('Transports'):
1467 return gajim.config.get('show_transports_group') and \
1468 (gajim.account_is_connected(account) or \
1469 gajim.config.get('showoffline'))
1470 if gajim.config.get('showoffline'):
1471 return True
1474 if self.regroup:
1475 # C_ACCOUNT for groups depends on the order
1476 # accounts were connected
1477 # Check all accounts for online group contacts
1478 accounts = gajim.contacts.get_accounts()
1479 else:
1480 accounts = [account]
1481 for _acc in accounts:
1482 for contact in gajim.contacts.iter_contacts(_acc):
1483 # Is this contact in this group ? (last part of if check if it's
1484 # self contact)
1485 if group in contact.get_shown_groups():
1486 if self.contact_is_visible(contact, _acc):
1487 return True
1488 return False
1489 if type_ == 'contact':
1490 if gajim.config.get('showoffline'):
1491 return True
1492 bb_jid = None
1493 bb_account = None
1494 family = gajim.contacts.get_metacontacts_family(account, jid)
1495 if family:
1496 nearby_family, bb_jid, bb_account = \
1497 self._get_nearby_family_and_big_brother(family, account)
1498 if (bb_jid, bb_account) == (jid, account):
1499 # Show the big brother if a child has pending events
1500 for data in nearby_family:
1501 jid = data['jid']
1502 account = data['account']
1503 contact = gajim.contacts.get_contact_with_highest_priority(
1504 account, jid)
1505 if contact and self.contact_is_visible(contact, account):
1506 return True
1507 return False
1508 else:
1509 contact = gajim.contacts.get_contact_with_highest_priority(account,
1510 jid)
1511 return self.contact_is_visible(contact, account)
1512 if type_ == 'agent':
1513 return gajim.config.get('show_transports_group') and \
1514 (gajim.account_is_connected(account) or \
1515 gajim.config.get('showoffline'))
1516 return True
1518 def _compareIters(self, model, iter1, iter2, data=None):
1519 '''Compare two iters to sort them'''
1520 name1 = model[iter1][C_NAME]
1521 name2 = model[iter2][C_NAME]
1522 if not name1 or not name2:
1523 return 0
1524 name1 = name1.decode('utf-8')
1525 name2 = name2.decode('utf-8')
1526 type1 = model[iter1][C_TYPE]
1527 type2 = model[iter2][C_TYPE]
1528 if type1 == 'self_contact':
1529 return -1
1530 if type2 == 'self_contact':
1531 return 1
1532 if type1 == 'group':
1533 name1 = model[iter1][C_JID]
1534 name2 = model[iter2][C_JID]
1535 if name1 == _('Transports'):
1536 return 1
1537 if name2 == _('Transports'):
1538 return -1
1539 if name1 == _('Not in Roster'):
1540 return 1
1541 if name2 == _('Not in Roster'):
1542 return -1
1543 if name1 == _('Groupchats'):
1544 return 1
1545 if name2 == _('Groupchats'):
1546 return -1
1547 account1 = model[iter1][C_ACCOUNT]
1548 account2 = model[iter2][C_ACCOUNT]
1549 if not account1 or not account2:
1550 return 0
1551 account1 = account1.decode('utf-8')
1552 account2 = account2.decode('utf-8')
1553 if type1 == 'account':
1554 if account1 < account2:
1555 return -1
1556 return 1
1557 jid1 = model[iter1][C_JID].decode('utf-8')
1558 jid2 = model[iter2][C_JID].decode('utf-8')
1559 if type1 == 'contact':
1560 lcontact1 = gajim.contacts.get_contacts(account1, jid1)
1561 contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
1562 if not contact1:
1563 return 0
1564 name1 = contact1.get_shown_name()
1565 if type2 == 'contact':
1566 lcontact2 = gajim.contacts.get_contacts(account2, jid2)
1567 contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
1568 if not contact2:
1569 return 0
1570 name2 = contact2.get_shown_name()
1571 # We first compare by show if sort_by_show_in_roster is True or if it's a
1572 # child contact
1573 if type1 == 'contact' and type2 == 'contact' and \
1574 gajim.config.get('sort_by_show_in_roster'):
1575 cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
1576 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
1577 s = self.get_show(lcontact1)
1578 show1 = cshow.get(s, 9)
1579 s = self.get_show(lcontact2)
1580 show2 = cshow.get(s, 9)
1581 removing1 = False
1582 removing2 = False
1583 if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
1584 removing1 = True
1585 if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
1586 removing2 = True
1587 if removing1 and not removing2:
1588 return 1
1589 if removing2 and not removing1:
1590 return -1
1591 sub1 = contact1.sub
1592 sub2 = contact2.sub
1593 # none and from goes after
1594 if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
1595 return -1
1596 if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
1597 return 1
1598 if show1 < show2:
1599 return -1
1600 elif show1 > show2:
1601 return 1
1602 # We compare names
1603 if name1.lower() < name2.lower():
1604 return -1
1605 if name2.lower() < name1.lower():
1606 return 1
1607 if type1 == 'contact' and type2 == 'contact':
1608 # We compare account names
1609 if account1.lower() < account2.lower():
1610 return -1
1611 if account2.lower() < account1.lower():
1612 return 1
1613 # We compare jids
1614 if jid1.lower() < jid2.lower():
1615 return -1
1616 if jid2.lower() < jid1.lower():
1617 return 1
1618 return 0
1620 ################################################################################
1621 ### FIXME: Methods that don't belong to roster window...
1622 ### ... atleast not in there current form
1623 ################################################################################
1625 def fire_up_unread_messages_events(self, account):
1626 '''reads from db the unread messages, and fire them up, and
1627 if we find very old unread messages, delete them from unread table'''
1628 results = gajim.logger.get_unread_msgs()
1629 for result in results:
1630 jid = result[4]
1631 if gajim.contacts.get_first_contact_from_jid(account, jid):
1632 # We have this jid in our contacts list
1633 # XXX unread messages should probably have their session saved with
1634 # them
1635 session = gajim.connections[account].make_new_session(jid)
1637 tim = time.localtime(float(result[2]))
1638 session.roster_message(jid, result[1], tim, msg_type='chat',
1639 msg_id=result[0])
1641 elif (time.time() - result[2]) > 2592000:
1642 # ok, here we see that we have a message in unread messages table
1643 # that is older than a month. It is probably from someone not in our
1644 # roster for accounts we usually launch, so we will delete this id
1645 # from unread message tables.
1646 gajim.logger.set_read_messages([result[0]])
1648 def fill_contacts_and_groups_dicts(self, array, account):
1649 '''fill gajim.contacts and gajim.groups'''
1650 # FIXME: This function needs to be splitted
1651 # Most of the logic SHOULD NOT be done at GUI level
1652 if account not in gajim.contacts.get_accounts():
1653 gajim.contacts.add_account(account)
1654 if account not in gajim.groups:
1655 gajim.groups[account] = {}
1656 # .keys() is needed
1657 for jid in array.keys():
1658 # Remove the contact in roster. It might has changed
1659 self.remove_contact(jid, account, force=True)
1660 # Remove old Contact instances
1661 gajim.contacts.remove_jid(account, jid, remove_meta=False)
1662 jids = jid.split('/')
1663 # get jid
1664 ji = jids[0]
1665 # get resource
1666 resource = ''
1667 if len(jids) > 1:
1668 resource = '/'.join(jids[1:])
1669 # get name
1670 name = array[jid]['name'] or ''
1671 show = 'offline' # show is offline by default
1672 status = '' # no status message by default
1674 keyID = ''
1675 attached_keys = gajim.config.get_per('accounts', account,
1676 'attached_gpg_keys').split()
1677 if jid in attached_keys:
1678 keyID = attached_keys[attached_keys.index(jid) + 1]
1680 if gajim.jid_is_transport(jid):
1681 array[jid]['groups'] = [_('Transports')]
1682 contact1 = gajim.contacts.create_contact(jid=ji, name=name,
1683 groups=array[jid]['groups'], show=show, status=status,
1684 sub=array[jid]['subscription'], ask=array[jid]['ask'],
1685 resource=resource, keyID=keyID)
1686 gajim.contacts.add_contact(account, contact1)
1688 if gajim.config.get('ask_avatars_on_startup'):
1689 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
1690 if pixbuf == 'ask':
1691 transport = gajim.get_transport_name_from_jid(contact1.jid)
1692 if not transport or gajim.jid_is_transport(contact1.jid):
1693 jid_with_resource = contact1.jid
1694 if contact1.resource:
1695 jid_with_resource += '/' + contact1.resource
1696 gajim.connections[account].request_vcard(jid_with_resource)
1697 else:
1698 host = gajim.get_server_from_jid(contact1.jid)
1699 if host not in gajim.transport_avatar[account]:
1700 gajim.transport_avatar[account][host] = [contact1.jid]
1701 else:
1702 gajim.transport_avatar[account][host].append(contact1.jid)
1704 # If we already have chat windows opened, update them with new contact
1705 # instance
1706 chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
1707 if chat_control:
1708 chat_control.contact = contact1
1710 def _change_awn_icon_status(self, status):
1711 if not dbus_support.supported:
1712 # do nothing if user doesn't have D-Bus bindings
1713 return
1714 try:
1715 bus = dbus.SessionBus()
1716 if not 'com.google.code.Awn' in bus.list_names():
1717 # Awn is not installed
1718 return
1719 except Exception:
1720 return
1721 iconset = gajim.config.get('iconset')
1722 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
1723 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
1724 status = status + '.png'
1725 elif status == 'online':
1726 prefix = os.path.join(gajim.DATA_DIR, 'pixmaps')
1727 status = 'gajim.png'
1728 path = os.path.join(prefix, status)
1729 try:
1730 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
1731 awn = dbus.Interface(obj, 'com.google.code.Awn')
1732 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
1733 except Exception:
1734 pass
1736 def music_track_changed(self, unused_listener, music_track_info,
1737 account=''):
1738 if account == '':
1739 accounts = gajim.connections.keys()
1740 if music_track_info is None:
1741 artist = ''
1742 title = ''
1743 source = ''
1744 elif hasattr(music_track_info, 'paused') and music_track_info.paused == 0:
1745 artist = ''
1746 title = ''
1747 source = ''
1748 else:
1749 artist = music_track_info.artist
1750 title = music_track_info.title
1751 source = music_track_info.album
1752 if account == '':
1753 for account in accounts:
1754 if not gajim.account_is_connected(account):
1755 continue
1756 if not gajim.connections[account].pep_supported:
1757 continue
1758 if gajim.connections[account].music_track_info == music_track_info:
1759 continue
1760 pep.user_send_tune(account, artist, title, source)
1761 gajim.connections[account].music_track_info = music_track_info
1762 elif account in gajim.connections and \
1763 gajim.connections[account].pep_supported:
1764 if gajim.connections[account].music_track_info != music_track_info:
1765 pep.user_send_tune(account, artist, title, source)
1766 gajim.connections[account].music_track_info = music_track_info
1768 def connected_rooms(self, account):
1769 if account in gajim.gc_connected[account].values():
1770 return True
1771 return False
1773 def on_event_removed(self, event_list):
1774 '''Remove contacts on last events removed.
1776 Only performed if removal was requested before but the contact
1777 still had pending events
1778 '''
1779 contact_list = ((event.jid.split('/')[0], event.account) for event in \
1780 event_list)
1782 for jid, account in contact_list:
1783 self.draw_contact(jid, account)
1784 # Remove contacts in roster if removal was requested
1785 key = (jid, account)
1786 if key in self.contacts_to_be_removed.keys():
1787 backend = self.contacts_to_be_removed[key]['backend']
1788 del self.contacts_to_be_removed[key]
1789 # Remove contact will delay removal if there are more events pending
1790 self.remove_contact(jid, account, backend=backend)
1791 self.show_title()
1793 def open_event(self, account, jid, event):
1794 '''If an event was handled, return True, else return False'''
1795 data = event.parameters
1796 ft = gajim.interface.instances['file_transfers']
1797 event = gajim.events.get_first_event(account, jid, event.type_)
1798 if event.type_ == 'normal':
1799 dialogs.SingleMessageWindow(account, jid,
1800 action='receive', from_whom=jid, subject=data[1], message=data[0],
1801 resource=data[5], session=data[8], form_node=data[9])
1802 gajim.events.remove_events(account, jid, event)
1803 return True
1804 elif event.type_ == 'file-request':
1805 contact = gajim.contacts.get_contact_with_highest_priority(account,
1806 jid)
1807 ft.show_file_request(account, contact, data)
1808 gajim.events.remove_events(account, jid, event)
1809 return True
1810 elif event.type_ in ('file-request-error', 'file-send-error'):
1811 ft.show_send_error(data)
1812 gajim.events.remove_events(account, jid, event)
1813 return True
1814 elif event.type_ in ('file-error', 'file-stopped'):
1815 ft.show_stopped(jid, data)
1816 gajim.events.remove_events(account, jid, event)
1817 return True
1818 elif event.type_ == 'file-completed':
1819 ft.show_completed(jid, data)
1820 gajim.events.remove_events(account, jid, event)
1821 return True
1822 elif event.type_ == 'gc-invitation':
1823 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1824 data[1])
1825 gajim.events.remove_events(account, jid, event)
1826 return True
1827 elif event.type_ == 'subscription_request':
1828 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1829 gajim.events.remove_events(account, jid, event)
1830 return True
1831 elif event.type_ == 'unsubscribed':
1832 gajim.interface.show_unsubscribed_dialog(account, data)
1833 gajim.events.remove_events(account, jid, event)
1834 return True
1835 return False
1837 ################################################################################
1838 ### This and that... random.
1839 ################################################################################
1841 def show_roster_vbox(self, active):
1842 if active:
1843 self.xml.get_widget('roster_vbox2').show()
1844 else:
1845 self.xml.get_widget('roster_vbox2').hide()
1848 def show_tooltip(self, contact):
1849 pointer = self.tree.get_pointer()
1850 props = self.tree.get_path_at_pos(pointer[0], pointer[1])
1851 # check if the current pointer is at the same path
1852 # as it was before setting the timeout
1853 if props and self.tooltip.id == props[0]:
1854 # bounding rectangle of coordinates for the cell within the treeview
1855 rect = self.tree.get_cell_area(props[0], props[1])
1857 # position of the treeview on the screen
1858 position = self.tree.window.get_origin()
1859 self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y)
1860 else:
1861 self.tooltip.hide_tooltip()
1864 def authorize(self, widget, jid, account):
1865 '''Authorize a contact (by re-sending auth menuitem)'''
1866 gajim.connections[account].send_authorization(jid)
1867 dialogs.InformationDialog(_('Authorization has been sent'),
1868 _('Now "%s" will know your status.') %jid)
1870 def req_sub(self, widget, jid, txt, account, groups=[], nickname=None,
1871 auto_auth=False):
1872 '''Request subscription to a contact'''
1873 gajim.connections[account].request_subscription(jid, txt, nickname,
1874 groups, auto_auth, gajim.nicks[account])
1875 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
1876 if not contact:
1877 keyID = ''
1878 attached_keys = gajim.config.get_per('accounts', account,
1879 'attached_gpg_keys').split()
1880 if jid in attached_keys:
1881 keyID = attached_keys[attached_keys.index(jid) + 1]
1882 contact = gajim.contacts.create_contact(jid=jid, name=nickname,
1883 groups=groups, show='requested', status='', ask='none',
1884 sub='subscribe', keyID=keyID)
1885 gajim.contacts.add_contact(account, contact)
1886 else:
1887 if not _('Not in Roster') in contact.get_shown_groups():
1888 dialogs.InformationDialog(_('Subscription request has been sent'),
1889 _('If "%s" accepts this request you will know his or her status.'
1890 ) % jid)
1891 return
1892 self.remove_contact(contact.jid, account, force=True)
1893 contact.groups = groups
1894 if nickname:
1895 contact.name = nickname
1896 self.add_contact(jid, account)
1898 def revoke_auth(self, widget, jid, account):
1899 '''Revoke a contact's authorization'''
1900 gajim.connections[account].refuse_authorization(jid)
1901 dialogs.InformationDialog(_('Authorization has been removed'),
1902 _('Now "%s" will always see you as offline.') %jid)
1904 def set_state(self, account, state):
1905 child_iterA = self._get_account_iter(account, self.model)
1906 if child_iterA:
1907 self.model[child_iterA][0] = \
1908 gajim.interface.jabber_state_images['16'][state]
1909 if gajim.interface.systray_enabled:
1910 gajim.interface.systray.change_status(state)
1912 def set_connecting_state(self, account):
1913 self.set_state(account, 'connecting')
1915 def send_status(self, account, status, txt, auto=False, to=None):
1916 child_iterA = self._get_account_iter(account, self.model)
1917 if status != 'offline':
1918 if to is None:
1919 gajim.config.set_per('accounts', account, 'last_status', status)
1920 gajim.config.set_per('accounts', account, 'last_status_msg',
1921 helpers.to_one_line(txt))
1922 if gajim.connections[account].connected < 2:
1923 self.set_connecting_state(account)
1925 keyid = gajim.config.get_per('accounts', account, 'keyid')
1926 if keyid and not gajim.connections[account].gpg:
1927 dialogs.WarningDialog(_('GPG is not usable'),
1928 _('You will be connected to %s without OpenPGP.') % account)
1930 self.send_status_continue(account, status, txt, auto, to)
1932 def send_pep(self, account, pep_dict=None):
1933 '''Sends pep information (activity, mood)'''
1934 if not pep_dict:
1935 return
1936 # activity
1937 if 'activity' in pep_dict and pep_dict['activity'] in pep.ACTIVITIES:
1938 activity = pep_dict['activity']
1939 if 'subactivity' in pep_dict and \
1940 pep_dict['subactivity'] in pep.ACTIVITIES[activity]:
1941 subactivity = pep_dict['subactivity']
1942 else:
1943 subactivity = 'other'
1944 if 'activity_text' in pep_dict:
1945 activity_text = pep_dict['activity_text']
1946 else:
1947 activity_text = ''
1948 pep.user_send_activity(account, activity, subactivity, activity_text)
1949 else:
1950 pep.user_send_activity(account, '')
1952 # mood
1953 if 'mood' in pep_dict and pep_dict['mood'] in pep.MOODS:
1954 mood = pep_dict['mood']
1955 if 'mood_text' in pep_dict:
1956 mood_text = pep_dict['mood_text']
1957 else:
1958 mood_text = ''
1959 pep.user_send_mood(account, mood, mood_text)
1960 else:
1961 pep.user_send_mood(account, '')
1963 def send_status_continue(self, account, status, txt, auto, to):
1964 if gajim.account_is_connected(account) and not to:
1965 if status == 'online' and gajim.interface.sleeper.getState() != \
1966 common.sleepy.STATE_UNKNOWN:
1967 gajim.sleeper_state[account] = 'online'
1968 elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa'):
1969 gajim.sleeper_state[account] = 'off'
1971 if to:
1972 gajim.connections[account].send_custom_status(status, txt, to)
1973 else:
1974 if status in ('invisible', 'offline'):
1975 pep.delete_pep(gajim.get_jid_from_account(account), \
1976 account)
1977 was_invisible = gajim.connections[account].connected == \
1978 gajim.SHOW_LIST.index('invisible')
1979 gajim.connections[account].change_status(status, txt, auto)
1981 if account in gajim.interface.status_sent_to_users:
1982 gajim.interface.status_sent_to_users[account] = {}
1983 if account in gajim.interface.status_sent_to_groups:
1984 gajim.interface.status_sent_to_groups[account] = {}
1985 for gc_control in gajim.interface.msg_win_mgr.get_controls(
1986 message_control.TYPE_GC) + \
1987 gajim.interface.minimized_controls[account].values():
1988 if gc_control.account == account:
1989 if gajim.gc_connected[account][gc_control.room_jid]:
1990 gajim.connections[account].send_gc_status(gc_control.nick,
1991 gc_control.room_jid, status, txt)
1992 else:
1993 # for some reason, we are not connected to the room even if
1994 # tab is opened, send initial join_gc()
1995 gajim.connections[account].join_gc(gc_control.nick,
1996 gc_control.room_jid, None)
1997 if was_invisible and status != 'offline':
1998 # We come back from invisible, join bookmarks
1999 gajim.interface.auto_join_bookmarks(account)
2002 def chg_contact_status(self, contact, show, status, account):
2003 '''When a contact changes his or her status'''
2004 contact_instances = gajim.contacts.get_contacts(account, contact.jid)
2005 contact.show = show
2006 contact.status = status
2007 # name is to show in conversation window
2008 name = contact.get_shown_name()
2009 fjid = contact.get_full_jid()
2011 # The contact has several resources
2012 if len(contact_instances) > 1:
2013 if contact.resource != '':
2014 name += '/' + contact.resource
2016 # Remove resource when going offline
2017 if show in ('offline', 'error') and \
2018 not self.contact_has_pending_roster_events(contact, account):
2019 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2020 if ctrl:
2021 ctrl.update_ui()
2022 ctrl.parent_win.redraw_tab(ctrl)
2023 # keep the contact around, since it's
2024 # already attached to the control
2025 else:
2026 gajim.contacts.remove_contact(account, contact)
2028 elif contact.jid == gajim.get_jid_from_account(account) and \
2029 show in ('offline', 'error'):
2030 # SelfContact went offline. Remove him when last pending
2031 # message was read
2032 self.remove_contact(contact.jid, account, backend=True)
2034 uf_show = helpers.get_uf_show(show)
2036 # print status in chat window and update status/GPG image
2037 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
2038 if ctrl and ctrl.type_id != message_control.TYPE_GC:
2039 ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
2040 account, contact.jid)
2041 ctrl.update_status_display(name, uf_show, status)
2043 if contact.resource:
2044 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
2045 if ctrl:
2046 ctrl.update_status_display(name, uf_show, status)
2048 # Delete pep if needed
2049 keep_pep = any(c.show not in ('error', 'offline') for c in
2050 contact_instances)
2051 if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
2052 and not contact.is_groupchat():
2053 pep.delete_pep(contact.jid, account)
2055 # Redraw everything and select the sender
2056 self.adjust_and_draw_contact_context(contact.jid, account)
2059 def on_status_changed(self, account, status):
2060 '''the core tells us that our status has changed'''
2061 if account not in gajim.contacts.get_accounts():
2062 return
2063 child_iterA = self._get_account_iter(account, self.model)
2064 self.set_account_status_icon(account)
2065 if status == 'offline':
2066 if self.quit_on_next_offline > -1:
2067 # we want to quit, we are waiting for all accounts to be offline
2068 self.quit_on_next_offline -= 1
2069 if self.quit_on_next_offline < 1:
2070 # all accounts offline, quit
2071 self.quit_gtkgui_interface()
2072 else:
2073 # No need to redraw contacts if we're quitting
2074 if child_iterA:
2075 self.model[child_iterA][C_AVATAR_PIXBUF] = None
2076 if account in gajim.con_types:
2077 gajim.con_types[account] = None
2078 for jid in gajim.contacts.get_jid_list(account):
2079 lcontact = gajim.contacts.get_contacts(account, jid)
2080 ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
2081 for contact in [c for c in lcontact if ((c.show != 'offline' or \
2082 c.is_transport()) and not ctrl)]:
2083 self.chg_contact_status(contact, 'offline', '', account)
2084 self.actions_menu_needs_rebuild = True
2085 self.update_status_combobox()
2087 def get_status_message(self, show, on_response, show_pep=True,
2088 always_ask=False):
2089 ''' get the status message by:
2090 1/ looking in default status message
2091 2/ asking to user if needed depending on ask_on(ff)line_status and
2092 always_ask
2093 show_pep can be False to hide pep things from status message or True
2094 '''
2095 empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
2096 'mood': '', 'mood_text': ''}
2097 if show in gajim.config.get_per('defaultstatusmsg'):
2098 if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
2099 on_response(gajim.config.get_per('defaultstatusmsg', show,
2100 'message'), empty_pep)
2101 return
2102 if not always_ask and ((show == 'online' and not gajim.config.get(
2103 'ask_online_status')) or (show in ('offline', 'invisible') and not \
2104 gajim.config.get('ask_offline_status'))):
2105 on_response('', empty_pep)
2106 return
2108 dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
2109 dlg.window.present() # show it on current workspace
2111 def change_status(self, widget, account, status):
2112 def change(account, status):
2113 def on_response(message, pep_dict):
2114 if message is None:
2115 # user pressed Cancel to change status message dialog
2116 return
2117 self.send_status(account, status, message)
2118 self.send_pep(account, pep_dict)
2119 self.get_status_message(status, on_response)
2121 if status == 'invisible' and self.connected_rooms(account):
2122 dialogs.ConfirmationDialog(
2123 _('You are participating in one or more group chats'),
2124 _('Changing your status to invisible will result in disconnection '
2125 'from those group chats. Are you sure you want to go invisible?'),
2126 on_response_ok = (change, account, status))
2127 else:
2128 change(account, status)
2130 def update_status_combobox(self):
2131 # table to change index in connection.connected to index in combobox
2132 table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
2133 'xa':3, 'dnd':4, 'invisible':5}
2135 # we check if there are more options in the combobox that it should
2136 # if yes, we remove the first ones
2137 while len(self.status_combobox.get_model()) > len(table)+2:
2138 self.status_combobox.remove_text(0)
2140 show = helpers.get_global_show()
2141 # temporarily block signal in order not to send status that we show
2142 # in the combobox
2143 self.combobox_callback_active = False
2144 if helpers.statuses_unified():
2145 self.status_combobox.set_active(table[show])
2146 else:
2147 uf_show = helpers.get_uf_show(show)
2148 liststore = self.status_combobox.get_model()
2149 liststore.prepend(['SEPARATOR', None, '', True])
2150 status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
2151 liststore.prepend([status_combobox_text,
2152 gajim.interface.jabber_state_images['16'][show], show, False])
2153 self.status_combobox.set_active(0)
2154 self._change_awn_icon_status(show)
2155 self.combobox_callback_active = True
2156 if gajim.interface.systray_enabled:
2157 gajim.interface.systray.change_status(show)
2159 def get_show(self, lcontact):
2160 prio = lcontact[0].priority
2161 show = lcontact[0].show
2162 for u in lcontact:
2163 if u.priority > prio:
2164 prio = u.priority
2165 show = u.show
2166 return show
2168 def on_message_window_delete(self, win_mgr, msg_win):
2169 if gajim.config.get('one_message_window') == 'always_with_roster':
2170 self.show_roster_vbox(True)
2171 gtkgui_helpers.resize_window(self.window,
2172 gajim.config.get('roster_width'),
2173 gajim.config.get('roster_height'))
2175 def close_all_from_dict(self, dic):
2176 '''close all the windows in the given dictionary'''
2177 for w in dic.values():
2178 if isinstance(w, dict):
2179 self.close_all_from_dict(w)
2180 else:
2181 w.window.destroy()
2183 def close_all(self, account, force=False):
2184 '''close all the windows from an account
2185 if force is True, do not ask confirmation before closing chat/gc windows
2186 '''
2187 if account in gajim.interface.instances:
2188 self.close_all_from_dict(gajim.interface.instances[account])
2189 for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
2190 ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
2191 force = force)
2193 def on_roster_window_delete_event(self, widget, event):
2194 '''Main window X button was clicked'''
2195 if gajim.interface.systray_enabled and not gajim.config.get(
2196 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event':
2197 self.tooltip.hide_tooltip()
2198 self.window.hide()
2199 elif gajim.config.get('quit_on_roster_x_button'):
2200 self.on_quit_request()
2201 else:
2202 def on_ok(checked):
2203 if checked:
2204 gajim.config.set('quit_on_roster_x_button', True)
2205 self.on_quit_request()
2206 dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'),
2207 _('Are you sure you want to quit Gajim?'),
2208 _('Always close Gajim'), on_response_ok=on_ok)
2209 return True # do NOT destroy the window
2211 def prepare_quit(self):
2212 msgwin_width_adjust = 0
2214 # in case show_roster_on_start is False and roster is never shown
2215 # window.window is None
2216 if self.window.window is not None:
2217 x, y = self.window.window.get_root_origin()
2218 gajim.config.set('roster_x-position', x)
2219 gajim.config.set('roster_y-position', y)
2220 width, height = self.window.get_size()
2221 # For the width use the size of the vbox containing the tree and
2222 # status combo, this will cancel out any hpaned width
2223 width = self.xml.get_widget('roster_vbox2').allocation.width
2224 gajim.config.set('roster_width', width)
2225 gajim.config.set('roster_height', height)
2226 if not self.xml.get_widget('roster_vbox2').get_property('visible'):
2227 # The roster vbox is hidden, so the message window is larger
2228 # then we want to save (i.e. the window will grow every startup)
2229 # so adjust.
2230 msgwin_width_adjust = -1 * width
2231 gajim.config.set('show_roster_on_startup',
2232 self.window.get_property('visible'))
2233 gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust)
2235 gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows))
2236 gajim.interface.save_config()
2237 for account in gajim.connections:
2238 gajim.connections[account].quit(True)
2239 self.close_all(account)
2240 if gajim.interface.systray_enabled:
2241 gajim.interface.hide_systray()
2243 def quit_gtkgui_interface(self):
2244 '''When we quit the gtk interface : exit gtk'''
2245 self.prepare_quit()
2246 gtk.main_quit()
2248 def on_quit_request(self, widget=None):
2249 ''' user want to quit. Check if he should be warned about messages
2250 pending. Terminate all sessions and send offline to all connected
2251 account. We do NOT really quit gajim here '''
2252 accounts = gajim.connections.keys()
2253 get_msg = False
2254 for acct in accounts:
2255 if gajim.connections[acct].connected:
2256 get_msg = True
2257 break
2259 def on_continue2(message, pep_dict):
2260 self.quit_on_next_offline = 0
2261 accounts_to_disconnect = []
2262 for acct in accounts:
2263 if gajim.connections[acct].connected:
2264 self.quit_on_next_offline += 1
2265 accounts_to_disconnect.append(acct)
2267 for acct in accounts_to_disconnect:
2268 self.send_status(acct, 'offline', message)
2269 self.send_pep(acct, pep_dict)
2271 if not self.quit_on_next_offline:
2272 self.quit_gtkgui_interface()
2274 def on_continue(message, pep_dict):
2275 if message is None:
2276 # user pressed Cancel to change status message dialog
2277 return
2278 # check if we have unread messages
2279 unread = gajim.events.get_nb_events()
2280 if not gajim.config.get('notify_on_all_muc_messages'):
2281 unread_not_to_notify = gajim.events.get_nb_events(
2282 ['printed_gc_msg'])
2283 unread -= unread_not_to_notify
2285 # check if we have recent messages
2286 recent = False
2287 for win in gajim.interface.msg_win_mgr.windows():
2288 for ctrl in win.controls():
2289 fjid = ctrl.get_full_jid()
2290 if fjid in gajim.last_message_time[ctrl.account]:
2291 if time.time() - gajim.last_message_time[ctrl.account][fjid] \
2292 < 2:
2293 recent = True
2294 break
2295 if recent:
2296 break
2298 if unread or recent:
2299 dialogs.ConfirmationDialog(_('You have unread messages'),
2300 _('Messages will only be available for reading them later if you'
2301 ' have history enabled and contact is in your roster.'),
2302 on_response_ok=(on_continue2, message, pep_dict))
2303 return
2304 on_continue2(message, pep_dict)
2306 if get_msg:
2307 self.get_status_message('offline', on_continue, show_pep=False)
2308 else:
2309 on_continue('', None)
2311 ################################################################################
2312 ### Menu and GUI callbacks
2313 ### FIXME: order callbacks in itself...
2314 ################################################################################
2316 def on_actions_menuitem_activate(self, widget):
2317 self.make_menu()
2319 def on_edit_menuitem_activate(self, widget):
2320 '''need to call make_menu to build profile, avatar item'''
2321 self.make_menu()
2323 def on_bookmark_menuitem_activate(self, widget, account, bookmark):
2324 gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'],
2325 bookmark['password'])
2327 def on_send_server_message_menuitem_activate(self, widget, account):
2328 server = gajim.config.get_per('accounts', account, 'hostname')
2329 server += '/announce/online'
2330 dialogs.SingleMessageWindow(account, server, 'send')
2332 def on_xml_console_menuitem_activate(self, widget, account):
2333 if 'xml_console' in gajim.interface.instances[account]:
2334 gajim.interface.instances[account]['xml_console'].window.present()
2335 else:
2336 gajim.interface.instances[account]['xml_console'] = \
2337 dialogs.XMLConsoleWindow(account)
2339 def on_privacy_lists_menuitem_activate(self, widget, account):
2340 if 'privacy_lists' in gajim.interface.instances[account]:
2341 gajim.interface.instances[account]['privacy_lists'].window.present()
2342 else:
2343 gajim.interface.instances[account]['privacy_lists'] = \
2344 dialogs.PrivacyListsWindow(account)
2346 def on_set_motd_menuitem_activate(self, widget, account):
2347 server = gajim.config.get_per('accounts', account, 'hostname')
2348 server += '/announce/motd'
2349 dialogs.SingleMessageWindow(account, server, 'send')
2351 def on_update_motd_menuitem_activate(self, widget, account):
2352 server = gajim.config.get_per('accounts', account, 'hostname')
2353 server += '/announce/motd/update'
2354 dialogs.SingleMessageWindow(account, server, 'send')
2356 def on_delete_motd_menuitem_activate(self, widget, account):
2357 server = gajim.config.get_per('accounts', account, 'hostname')
2358 server += '/announce/motd/delete'
2359 gajim.connections[account].send_motd(server)
2361 def on_history_manager_menuitem_activate(self, widget):
2362 if os.name == 'nt':
2363 if os.path.exists('history_manager.exe'): # user is running stable
2364 helpers.exec_command('history_manager.exe')
2365 else: # user is running svn
2366 helpers.exec_command('%s history_manager.py' % sys.executable)
2367 else: # Unix user
2368 helpers.exec_command('%s history_manager.py &' % sys.executable)
2370 def on_info(self, widget, contact, account):
2371 '''Call vcard_information_window class to display contact's information'''
2372 if gajim.connections[account].is_zeroconf:
2373 self.on_info_zeroconf(widget, contact, account)
2374 return
2376 info = gajim.interface.instances[account]['infos']
2377 if contact.jid in info:
2378 info[contact.jid].window.present()
2379 else:
2380 info[contact.jid] = vcard.VcardWindow(contact, account)
2382 def on_info_zeroconf(self, widget, contact, account):
2383 info = gajim.interface.instances[account]['infos']
2384 if contact.jid in info:
2385 info[contact.jid].window.present()
2386 else:
2387 contact = gajim.contacts.get_first_contact_from_jid(account,
2388 contact.jid)
2389 if contact.show in ('offline', 'error'):
2390 # don't show info on offline contacts
2391 return
2392 info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
2394 def on_roster_treeview_leave_notify_event(self, widget, event):
2395 props = widget.get_path_at_pos(int(event.x), int(event.y))
2396 if self.tooltip.timeout > 0:
2397 if not props or self.tooltip.id == props[0]:
2398 self.tooltip.hide_tooltip()
2400 def on_roster_treeview_motion_notify_event(self, widget, event):
2401 model = widget.get_model()
2402 props = widget.get_path_at_pos(int(event.x), int(event.y))
2403 if self.tooltip.timeout > 0:
2404 if not props or self.tooltip.id != props[0]:
2405 self.tooltip.hide_tooltip()
2406 if props:
2407 row = props[0]
2408 titer = None
2409 try:
2410 titer = model.get_iter(row)
2411 except Exception:
2412 self.tooltip.hide_tooltip()
2413 return
2414 if model[titer][C_TYPE] in ('contact', 'self_contact'):
2415 # we're on a contact entry in the roster
2416 account = model[titer][C_ACCOUNT].decode('utf-8')
2417 jid = model[titer][C_JID].decode('utf-8')
2418 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2419 self.tooltip.id = row
2420 contacts = gajim.contacts.get_contacts(account, jid)
2421 connected_contacts = []
2422 for c in contacts:
2423 if c.show not in ('offline', 'error'):
2424 connected_contacts.append(c)
2425 if not connected_contacts:
2426 # no connected contacts, show the ofline one
2427 connected_contacts = contacts
2428 self.tooltip.account = account
2429 self.tooltip.timeout = gobject.timeout_add(500,
2430 self.show_tooltip, connected_contacts)
2431 elif model[titer][C_TYPE] == 'groupchat':
2432 account = model[titer][C_ACCOUNT].decode('utf-8')
2433 jid = model[titer][C_JID].decode('utf-8')
2434 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2435 self.tooltip.id = row
2436 contact = gajim.contacts.get_contacts(account, jid)
2437 self.tooltip.account = account
2438 self.tooltip.timeout = gobject.timeout_add(500,
2439 self.show_tooltip, contact)
2440 elif model[titer][C_TYPE] == 'account':
2441 # we're on an account entry in the roster
2442 account = model[titer][C_ACCOUNT].decode('utf-8')
2443 if account == 'all':
2444 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2445 self.tooltip.id = row
2446 self.tooltip.account = None
2447 self.tooltip.timeout = gobject.timeout_add(500,
2448 self.show_tooltip, [])
2449 return
2450 jid = gajim.get_jid_from_account(account)
2451 contacts = []
2452 connection = gajim.connections[account]
2453 # get our current contact info
2455 nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
2456 accounts = [account])
2457 account_name = account
2458 if gajim.account_is_connected(account):
2459 account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
2460 contact = gajim.contacts.create_contact(jid=jid, name=account_name,
2461 show=connection.get_status(), sub='', status=connection.status,
2462 resource=connection.server_resource,
2463 priority=connection.priority, mood=connection.mood,
2464 tune=connection.tune, activity=connection.activity)
2465 if gajim.connections[account].gpg:
2466 contact.keyID = gajim.config.get_per('accounts', connection.name,
2467 'keyid')
2468 contacts.append(contact)
2469 # if we're online ...
2470 if connection.connection:
2471 roster = connection.connection.getRoster()
2472 # in threadless connection when no roster stanza is sent,
2473 # 'roster' is None
2474 if roster and roster.getItem(jid):
2475 resources = roster.getResources(jid)
2476 # ...get the contact info for our other online resources
2477 for resource in resources:
2478 # Check if we already have this resource
2479 found = False
2480 for contact_ in contacts:
2481 if contact_.resource == resource:
2482 found = True
2483 break
2484 if found:
2485 continue
2486 show = roster.getShow(jid+'/'+resource)
2487 if not show:
2488 show = 'online'
2489 contact = gajim.contacts.create_contact(jid=jid,
2490 name=account, groups=['self_contact'], show=show,
2491 status=roster.getStatus(jid + '/' + resource),
2492 resource=resource,
2493 priority=roster.getPriority(jid + '/' + resource))
2494 contacts.append(contact)
2495 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2496 self.tooltip.id = row
2497 self.tooltip.account = None
2498 self.tooltip.timeout = gobject.timeout_add(500,
2499 self.show_tooltip, contacts)
2501 def on_agent_logging(self, widget, jid, state, account):
2502 '''When an agent is requested to log in or off'''
2503 gajim.connections[account].send_agent_status(jid, state)
2505 def on_edit_agent(self, widget, contact, account):
2506 '''When we want to modify the agent registration'''
2507 gajim.connections[account].request_register_agent_info(contact.jid)
2509 def on_remove_agent(self, widget, list_):
2510 '''When an agent is requested to be removed. list_ is a list of
2511 (contact, account) tuple'''
2512 for (contact, account) in list_:
2513 if gajim.config.get_per('accounts', account, 'hostname') == \
2514 contact.jid:
2515 # We remove the server contact
2516 # remove it from treeview
2517 gajim.connections[account].unsubscribe(contact.jid)
2518 self.remove_contact(contact.jid, account, backend=True)
2519 return
2521 def remove(list_):
2522 for (contact, account) in list_:
2523 full_jid = contact.get_full_jid()
2524 gajim.connections[account].unsubscribe_agent(full_jid)
2525 # remove transport from treeview
2526 self.remove_contact(contact.jid, account, backend=True)
2528 # Check if there are unread events from some contacts
2529 has_unread_events = False
2530 for (contact, account) in list_:
2531 for jid in gajim.events.get_events(account):
2532 if jid.endswith(contact.jid):
2533 has_unread_events = True
2534 break
2535 if has_unread_events:
2536 dialogs.ErrorDialog(_('You have unread messages'),
2537 _('You must read them before removing this transport.'))
2538 return
2539 if len(list_) == 1:
2540 pritext = _('Transport "%s" will be removed') % list_[0][0].jid
2541 sectext = _('You will no longer be able to send and receive messages '
2542 'from contacts using this transport.')
2543 else:
2544 pritext = _('Transports will be removed')
2545 jids = ''
2546 for (contact, account) in list_:
2547 jids += '\n ' + contact.get_shown_name() + ','
2548 jids = jids[:-1] + '.'
2549 sectext = _('You will no longer be able to send and receive messages '
2550 'to contacts from these transports: %s') % jids
2551 dialogs.ConfirmationDialog(pritext, sectext,
2552 on_response_ok = (remove, list_))
2554 def on_block(self, widget, list_, group=None):
2555 ''' When clicked on the 'block' button in context menu.
2556 list_ is a list of (contact, account)'''
2557 def on_continue(msg, pep_dict):
2558 if msg is None:
2559 # user pressed Cancel to change status message dialog
2560 return
2561 accounts = []
2562 if group is None:
2563 for (contact, account) in list_:
2564 if account not in accounts:
2565 if not gajim.connections[account].privacy_rules_supported:
2566 continue
2567 accounts.append(account)
2568 self.send_status(account, 'offline', msg, to=contact.jid)
2569 new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny',
2570 'value' : contact.jid, 'child': [u'message', u'iq',
2571 u'presence-out']}
2572 gajim.connections[account].blocked_list.append(new_rule)
2573 # needed for draw_contact:
2574 gajim.connections[account].blocked_contacts.append(
2575 contact.jid)
2576 self.draw_contact(contact.jid, account)
2577 else:
2578 for (contact, account) in list_:
2579 if account not in accounts:
2580 if not gajim.connections[account].privacy_rules_supported:
2581 continue
2582 accounts.append(account)
2583 # needed for draw_group:
2584 gajim.connections[account].blocked_groups.append(group)
2585 self.draw_group(group, account)
2586 self.send_status(account, 'offline', msg, to=contact.jid)
2587 self.draw_contact(contact.jid, account)
2588 new_rule = {'order': u'1', 'type': u'group', 'action': u'deny',
2589 'value' : group, 'child': [u'message', u'iq', u'presence-out']}
2590 gajim.connections[account].blocked_list.append(new_rule)
2591 for account in accounts:
2592 connection = gajim.connections[account]
2593 connection.set_privacy_list('block', connection.blocked_list)
2594 if len(connection.blocked_list) == 1:
2595 connection.set_active_list('block')
2596 connection.set_default_list('block')
2597 connection.get_privacy_list('block')
2599 self.get_status_message('offline', on_continue, show_pep=False)
2601 def on_unblock(self, widget, list_, group=None):
2602 ''' When clicked on the 'unblock' button in context menu. '''
2603 accounts = []
2604 if group is None:
2605 for (contact, account) in list_:
2606 if account not in accounts:
2607 if gajim.connections[account].privacy_rules_supported:
2608 accounts.append(account)
2609 gajim.connections[account].new_blocked_list = []
2610 gajim.connections[account].to_unblock = []
2611 gajim.connections[account].to_unblock.append(contact.jid)
2612 else:
2613 gajim.connections[account].to_unblock.append(contact.jid)
2614 # needed for draw_contact:
2615 if contact.jid in gajim.connections[account].blocked_contacts:
2616 gajim.connections[account].blocked_contacts.remove(contact.jid)
2617 self.draw_contact(contact.jid, account)
2618 for account in accounts:
2619 for rule in gajim.connections[account].blocked_list:
2620 if rule['action'] != 'deny' or rule['type'] != 'jid' \
2621 or rule['value'] not in gajim.connections[account].to_unblock:
2622 gajim.connections[account].new_blocked_list.append(rule)
2623 else:
2624 for (contact, account) in list_:
2625 if account not in accounts:
2626 if gajim.connections[account].privacy_rules_supported:
2627 accounts.append(account)
2628 # needed for draw_group:
2629 if group in gajim.connections[account].blocked_groups:
2630 gajim.connections[account].blocked_groups.remove(group)
2631 self.draw_group(group, account)
2632 gajim.connections[account].new_blocked_list = []
2633 for rule in gajim.connections[account].blocked_list:
2634 if rule['action'] != 'deny' or rule['type'] != 'group' \
2635 or rule['value'] != group:
2636 gajim.connections[account].new_blocked_list.append(rule)
2637 self.draw_contact(contact.jid, account)
2638 for account in accounts:
2639 gajim.connections[account].set_privacy_list('block',
2640 gajim.connections[account].new_blocked_list)
2641 gajim.connections[account].get_privacy_list('block')
2642 if len(gajim.connections[account].new_blocked_list) == 0:
2643 gajim.connections[account].blocked_list = []
2644 gajim.connections[account].blocked_contacts = []
2645 gajim.connections[account].blocked_groups = []
2646 gajim.connections[account].set_default_list('')
2647 gajim.connections[account].set_active_list('')
2648 gajim.connections[account].del_privacy_list('block')
2649 if 'blocked_contacts' in gajim.interface.instances[account]:
2650 gajim.interface.instances[account]['blocked_contacts'].\
2651 privacy_list_received([])
2652 for (contact, account) in list_:
2653 if not self.regroup:
2654 show = gajim.SHOW_LIST[gajim.connections[account].connected]
2655 else: # accounts merged
2656 show = helpers.get_global_show()
2657 if show == 'invisible':
2658 # Don't send our presence if we're invisible
2659 continue
2660 if account not in accounts:
2661 accounts.append(account)
2662 if gajim.connections[account].privacy_rules_supported:
2663 self.send_status(account, show,
2664 gajim.connections[account].status, to=contact.jid)
2665 else:
2666 self.send_status(account, show,
2667 gajim.connections[account].status, to=contact.jid)
2669 def on_rename(self, widget, row_type, jid, account):
2670 # this function is called either by F2 or by Rename menuitem
2671 if 'rename' in gajim.interface.instances:
2672 gajim.interface.instances['rename'].dialog.present()
2673 return
2675 # account is offline, don't allow to rename
2676 if gajim.connections[account].connected < 2:
2677 return
2678 if row_type in ('contact', 'agent'):
2679 # it's jid
2680 title = _('Rename Contact')
2681 message = _('Enter a new nickname for contact %s') % jid
2682 old_text = gajim.contacts.get_contact_with_highest_priority(account,
2683 jid).name
2684 elif row_type == 'group':
2685 if jid in helpers.special_groups + (_('General'),):
2686 return
2687 old_text = jid
2688 title = _('Rename Group')
2689 message = _('Enter a new name for group %s') % \
2690 gobject.markup_escape_text(jid)
2692 def on_renamed(new_text, account, row_type, jid, old_text):
2693 if 'rename' in gajim.interface.instances:
2694 del gajim.interface.instances['rename']
2695 if row_type in ('contact', 'agent'):
2696 if old_text == new_text:
2697 return
2698 for contact in gajim.contacts.get_contacts(account, jid):
2699 contact.name = new_text
2700 gajim.connections[account].update_contact(jid, new_text, \
2701 contact.groups)
2702 self.draw_contact(jid, account)
2703 # Update opened chats
2704 for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account):
2705 ctrl.update_ui()
2706 win = gajim.interface.msg_win_mgr.get_window(jid, account)
2707 win.redraw_tab(ctrl)
2708 win.show_title()
2709 elif row_type == 'group':
2710 # in C_JID column, we hold the group name (which is not escaped)
2711 if old_text == new_text:
2712 return
2713 # Groups may not change name from or to a special groups
2714 for g in helpers.special_groups:
2715 if g in (new_text, old_text):
2716 return
2717 # update all contacts in the given group
2718 if self.regroup:
2719 accounts = gajim.connections.keys()
2720 else:
2721 accounts = [account,]
2722 for acc in accounts:
2723 for jid in gajim.contacts.get_jid_list(acc):
2724 contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
2725 if old_text in contact.groups:
2726 self.add_contact_to_groups(jid, acc, [new_text,])
2727 self.remove_contact_from_groups(jid, acc, [old_text,])
2729 def on_canceled():
2730 if 'rename' in gajim.interface.instances:
2731 del gajim.interface.instances['rename']
2733 gajim.interface.instances['rename'] = dialogs.InputDialog(title, message,
2734 old_text, False, (on_renamed, account, row_type, jid, old_text),
2735 on_canceled)
2737 def on_remove_group_item_activated(self, widget, group, account):
2738 def on_ok(checked):
2739 for contact in gajim.contacts.get_contacts_from_group(account, group):
2740 if not checked:
2741 self.remove_contact_from_groups(contact.jid,account, [group])
2742 else:
2743 gajim.connections[account].unsubscribe(contact.jid)
2744 self.remove_contact(contact.jid, account, backend=True)
2746 dialogs.ConfirmationDialogCheck(_('Remove Group'),
2747 _('Do you want to remove group %s from the roster?') % group,
2748 _('Also remove all contacts in this group from your roster'),
2749 on_response_ok=on_ok)
2751 def on_assign_pgp_key(self, widget, contact, account):
2752 attached_keys = gajim.config.get_per('accounts', account,
2753 'attached_gpg_keys').split()
2754 keys = {}
2755 keyID = _('None')
2756 for i in xrange(len(attached_keys)/2):
2757 keys[attached_keys[2*i]] = attached_keys[2*i+1]
2758 if attached_keys[2*i] == contact.jid:
2759 keyID = attached_keys[2*i+1]
2760 public_keys = gajim.connections[account].ask_gpg_keys()
2761 public_keys[_('None')] = _('None')
2763 def on_key_selected(keyID):
2764 if keyID is None:
2765 return
2766 if keyID[0] == _('None'):
2767 if contact.jid in keys:
2768 del keys[contact.jid]
2769 keyID = ''
2770 else:
2771 keyID = keyID[0]
2772 keys[contact.jid] = keyID
2774 ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
2775 if ctrl:
2776 ctrl.update_ui()
2778 keys_str = ''
2779 for jid in keys:
2780 keys_str += jid + ' ' + keys[jid] + ' '
2781 gajim.config.set_per('accounts', account, 'attached_gpg_keys',
2782 keys_str)
2783 for u in gajim.contacts.get_contacts(account, contact.jid):
2784 u.keyID = helpers.prepare_and_validate_gpg_keyID(account,
2785 contact.jid, keyID)
2787 dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
2788 _('Select a key to apply to the contact'), public_keys,
2789 on_key_selected, selected=keyID)
2791 def on_set_custom_avatar_activate(self, widget, contact, account):
2792 def on_ok(widget, path_to_file):
2793 filesize = os.path.getsize(path_to_file) # in bytes
2794 invalid_file = False
2795 msg = ''
2796 if os.path.isfile(path_to_file):
2797 stat = os.stat(path_to_file)
2798 if stat[6] == 0:
2799 invalid_file = True
2800 msg = _('File is empty')
2801 else:
2802 invalid_file = True
2803 msg = _('File does not exist')
2804 if invalid_file:
2805 dialogs.ErrorDialog(_('Could not load image'), msg)
2806 return
2807 try:
2808 pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
2809 if filesize > 16384: # 16 kb
2810 # get the image at 'tooltip size'
2811 # and hope that user did not specify in ACE crazy size
2812 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip')
2813 except gobject.GError, msg: # unknown format
2814 # msg should be string, not object instance
2815 msg = str(msg)
2816 dialogs.ErrorDialog(_('Could not load image'), msg)
2817 return
2818 gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True)
2819 dlg.destroy()
2820 self.update_avatar_in_gui(contact.jid, account)
2822 def on_clear(widget):
2823 dlg.destroy()
2824 # Delete file:
2825 gajim.interface.remove_avatar_files(contact.jid, local=True)
2826 self.update_avatar_in_gui(contact.jid, account)
2828 dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok,
2829 on_response_clear=on_clear)
2831 def on_edit_groups(self, widget, list_):
2832 dialogs.EditGroupsDialog(list_)
2834 def on_history(self, widget, contact, account):
2835 '''When history menuitem is activated: call log window'''
2836 if 'logs' in gajim.interface.instances:
2837 gajim.interface.instances['logs'].window.present()
2838 gajim.interface.instances['logs'].open_history(contact.jid, account)
2839 else:
2840 gajim.interface.instances['logs'] = history_window.\
2841 HistoryWindow(contact.jid, account)
2843 def on_disconnect(self, widget, jid, account):
2844 '''When disconnect menuitem is activated: disconect from room'''
2845 if jid in gajim.interface.minimized_controls[account]:
2846 ctrl = gajim.interface.minimized_controls[account][jid]
2847 ctrl.shutdown()
2848 ctrl.got_disconnected()
2849 self.remove_groupchat(jid, account)
2851 def on_send_single_message_menuitem_activate(self, widget, account,
2852 contact = None):
2853 if contact is None:
2854 dialogs.SingleMessageWindow(account, action='send')
2855 elif isinstance(contact, list):
2856 dialogs.SingleMessageWindow(account, contact, 'send')
2857 else:
2858 jid = contact.jid
2859 if contact.jid == gajim.get_jid_from_account(account):
2860 jid += '/' + contact.resource
2861 dialogs.SingleMessageWindow(account, jid, 'send')
2863 def on_send_file_menuitem_activate(self, widget, contact, account,
2864 resource=None):
2865 gajim.interface.instances['file_transfers'].show_file_send_request(
2866 account, contact)
2868 def on_add_special_notification_menuitem_activate(self, widget, jid):
2869 dialogs.AddSpecialNotificationDialog(jid)
2871 def on_invite_to_new_room(self, widget, list_, resource=None):
2872 ''' resource parameter MUST NOT be used if more than one contact in
2873 list '''
2874 account_list = []
2875 jid_list = []
2876 for (contact, account) in list_:
2877 if contact.jid not in jid_list:
2878 if resource: # we MUST have one contact only in list_
2879 fjid = contact.jid + '/' + resource
2880 jid_list.append(fjid)
2881 else:
2882 jid_list.append(contact.jid)
2883 if account not in account_list:
2884 account_list.append(account)
2885 # transform None in 'jabber'
2886 type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
2887 for account in account_list:
2888 if gajim.connections[account].muc_jid[type_]:
2889 # create the room on this muc server
2890 if 'join_gc' in gajim.interface.instances[account]:
2891 gajim.interface.instances[account]['join_gc'].window.destroy()
2892 try:
2893 gajim.interface.instances[account]['join_gc'] = \
2894 dialogs.JoinGroupchatWindow(account,
2895 gajim.connections[account].muc_jid[type_],
2896 automatic = {'invities': jid_list})
2897 except GajimGeneralException:
2898 continue
2899 break
2901 def on_invite_to_room(self, widget, list_, room_jid, room_account,
2902 resource = None):
2903 ''' resource parameter MUST NOT be used if more than one contact in
2904 list '''
2905 for e in list_:
2906 contact = e[0]
2907 contact_jid = contact.jid
2908 if resource: # we MUST have one contact only in list_
2909 contact_jid += '/' + resource
2910 gajim.connections[room_account].send_invite(room_jid, contact_jid)
2912 def on_all_groupchat_maximized(self, widget, group_list):
2913 for (contact, account) in group_list:
2914 self.on_groupchat_maximized(widget, contact.jid, account)
2916 def on_groupchat_maximized(self, widget, jid, account):
2917 '''When a groupchat is maximised'''
2918 if not jid in gajim.interface.minimized_controls[account]:
2919 return
2920 ctrl = gajim.interface.minimized_controls[account][jid]
2921 mw = gajim.interface.msg_win_mgr.get_window(jid, account)
2922 if not mw:
2923 mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact,
2924 ctrl.account, ctrl.type_id)
2925 ctrl.parent_win = mw
2926 mw.new_tab(ctrl)
2927 mw.set_active_tab(ctrl)
2928 mw.window.window.focus()
2929 self.remove_groupchat(jid, account)
2931 def on_edit_account(self, widget, account):
2932 if 'accounts' in gajim.interface.instances:
2933 gajim.interface.instances['accounts'].window.present()
2934 else:
2935 gajim.interface.instances['accounts'] = config.AccountsWindow()
2936 gajim.interface.instances['accounts'].select_account(account)
2938 def on_zeroconf_properties(self, widget, account):
2939 if 'accounts' in gajim.interface.instances:
2940 gajim.interface.instances['accounts'].window.present()
2941 else:
2942 gajim.interface.instances['accounts'] = config.AccountsWindow()
2943 gajim.interface.instances['accounts'].select_account(account)
2945 def on_open_gmail_inbox(self, widget, account):
2946 url = gajim.connections[account].gmail_url
2947 if url:
2948 helpers.launch_browser_mailer('url', url)
2950 def on_change_status_message_activate(self, widget, account):
2951 show = gajim.SHOW_LIST[gajim.connections[account].connected]
2952 def on_response(message, pep_dict):
2953 if message is None: # None is if user pressed Cancel
2954 return
2955 self.send_status(account, show, message)
2956 self.send_pep(account, pep_dict)
2957 dialogs.ChangeStatusMessageDialog(on_response, show)
2959 def on_add_to_roster(self, widget, contact, account):
2960 dialogs.AddNewContactWindow(account, contact.jid, contact.name)
2963 def on_roster_treeview_scroll_event(self, widget, event):
2964 self.tooltip.hide_tooltip()
2966 def on_roster_treeview_key_press_event(self, widget, event):
2967 '''when a key is pressed in the treeviews'''
2968 self.tooltip.hide_tooltip()
2969 if event.keyval == gtk.keysyms.Escape:
2970 self.tree.get_selection().unselect_all()
2971 elif event.keyval == gtk.keysyms.F2:
2972 treeselection = self.tree.get_selection()
2973 model, list_of_paths = treeselection.get_selected_rows()
2974 if len(list_of_paths) != 1:
2975 return
2976 path = list_of_paths[0]
2977 type_ = model[path][C_TYPE]
2978 if type_ in ('contact', 'group', 'agent'):
2979 jid = model[path][C_JID].decode('utf-8')
2980 account = model[path][C_ACCOUNT].decode('utf-8')
2981 self.on_rename(widget, type_, jid, account)
2983 elif event.keyval == gtk.keysyms.Delete:
2984 treeselection = self.tree.get_selection()
2985 model, list_of_paths = treeselection.get_selected_rows()
2986 if not len(list_of_paths):
2987 return
2988 type_ = model[list_of_paths[0]][C_TYPE]
2989 account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8')
2990 list_ = []
2991 for path in list_of_paths:
2992 if model[path][C_TYPE] != type_:
2993 return
2994 jid = model[path][C_JID].decode('utf-8')
2995 account = model[path][C_ACCOUNT].decode('utf-8')
2996 contact = gajim.contacts.get_contact_with_highest_priority(account,
2997 jid)
2998 list_.append((contact, account))
2999 if type_ in ('account', 'group', 'self_contact') or \
3000 account == gajim.ZEROCONF_ACC_NAME:
3001 return
3002 if type_ == 'contact':
3003 self.on_req_usub(widget, list_)
3004 elif type_ == 'agent':
3005 self.on_remove_agent(widget, list_)
3007 def on_roster_treeview_button_release_event(self, widget, event):
3008 try:
3009 path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
3010 except TypeError:
3011 return False
3013 if event.button == 1: # Left click
3014 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3015 not event.state & gtk.gdk.CONTROL_MASK:
3016 # Check if button has been pressed on the same row
3017 if self.clicked_path == path:
3018 self.on_row_activated(widget, path)
3019 self.clicked_path = None
3021 def on_roster_treeview_button_press_event(self, widget, event):
3022 # hide tooltip, no matter the button is pressed
3023 self.tooltip.hide_tooltip()
3024 try:
3025 pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
3026 path, x = pos[0], pos[2]
3027 except TypeError:
3028 self.tree.get_selection().unselect_all()
3029 return False
3031 if event.button == 3: # Right click
3032 try:
3033 model, list_of_paths = self.tree.get_selection().get_selected_rows()
3034 except TypeError:
3035 list_of_paths = []
3036 if path not in list_of_paths:
3037 self.tree.get_selection().unselect_all()
3038 self.tree.get_selection().select_path(path)
3039 return self.show_treeview_menu(event)
3041 elif event.button == 2: # Middle click
3042 try:
3043 model, list_of_paths = self.tree.get_selection().get_selected_rows()
3044 except TypeError:
3045 list_of_paths = []
3046 if list_of_paths != [path]:
3047 self.tree.get_selection().unselect_all()
3048 self.tree.get_selection().select_path(path)
3049 type_ = model[path][C_TYPE]
3050 if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
3051 self.on_row_activated(widget, path)
3052 elif type_ == 'account':
3053 account = model[path][C_ACCOUNT].decode('utf-8')
3054 if account != 'all':
3055 show = gajim.connections[account].connected
3056 if show > 1: # We are connected
3057 self.on_change_status_message_activate(widget, account)
3058 return True
3059 show = helpers.get_global_show()
3060 if show == 'offline':
3061 return True
3062 def on_response(message, pep_dict):
3063 if message is None:
3064 return True
3065 for acct in gajim.connections:
3066 if not gajim.config.get_per('accounts', acct,
3067 'sync_with_global_status'):
3068 continue
3069 current_show = gajim.SHOW_LIST[gajim.connections[acct].\
3070 connected]
3071 self.send_status(acct, current_show, message)
3072 self.send_pep(acct, pep_dict)
3073 dialogs.ChangeStatusMessageDialog(on_response, show)
3074 return True
3076 elif event.button == 1: # Left click
3077 model = self.modelfilter
3078 type_ = model[path][C_TYPE]
3079 # x_min is the x start position of status icon column
3080 if gajim.config.get('avatar_position_in_roster') == 'left':
3081 x_min = gajim.config.get('roster_avatar_width')
3082 else:
3083 x_min = 0
3084 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
3085 not event.state & gtk.gdk.CONTROL_MASK:
3086 # Don't handle double click if we press icon of a metacontact
3087 titer = model.get_iter(path)
3088 if x > x_min and x < x_min + 27 and type_ == 'contact' and \
3089 model.iter_has_child(titer):
3090 if (self.tree.row_expanded(path)):
3091 self.tree.collapse_row(path)
3092 else:
3093 self.tree.expand_row(path, False)
3094 return
3095 # We just save on which row we press button, and open chat window on
3096 # button release to be able to do DND without opening chat window
3097 self.clicked_path = path
3098 return
3099 else:
3100 if type_ == 'group' and x < 27:
3101 # first cell in 1st column (the arrow SINGLE clicked)
3102 if (self.tree.row_expanded(path)):
3103 self.tree.collapse_row(path)
3104 else:
3105 self.tree.expand_row(path, False)
3107 elif type_ == 'contact' and x > x_min and x < x_min + 27:
3108 if (self.tree.row_expanded(path)):
3109 self.tree.collapse_row(path)
3110 else:
3111 self.tree.expand_row(path, False)
3113 def on_req_usub(self, widget, list_):
3114 '''Remove a contact. list_ is a list of (contact, account) tuples'''
3115 def on_ok(is_checked, list_):
3116 remove_auth = True
3117 if len(list_) == 1:
3118 contact = list_[0][0]
3119 if contact.sub != 'to' and is_checked:
3120 remove_auth = False
3121 for (contact, account) in list_:
3122 if _('Not in Roster') not in contact.get_shown_groups():
3123 gajim.connections[account].unsubscribe(contact.jid, remove_auth)
3124 self.remove_contact(contact.jid, account, backend=True)
3125 if not remove_auth and contact.sub == 'both':
3126 contact.name = ''
3127 contact.groups = []
3128 contact.sub = 'from'
3129 # we can't see him, but have to set it manually in contact
3130 contact.show = 'offline'
3131 gajim.contacts.add_contact(account, contact)
3132 self.add_contact(contact.jid, account)
3133 def on_ok2(list_):
3134 on_ok(False, list_)
3136 if len(list_) == 1:
3137 contact = list_[0][0]
3138 pritext = _('Contact "%s" will be removed from your roster') % \
3139 contact.get_shown_name()
3140 sectext = _('You are about to remove "%(name)s" (%(jid)s) from your '
3141 'roster.\n') % {'name': contact.get_shown_name(),
3142 'jid': contact.jid}
3143 if contact.sub == 'to':
3144 dialogs.ConfirmationDialog(pritext, sectext + \
3145 _('By removing this contact you also remove authorization '
3146 'resulting in him or her always seeing you as offline.'),
3147 on_response_ok = (on_ok2, list_))
3148 elif _('Not in Roster') in contact.get_shown_groups():
3149 # Contact is not in roster
3150 dialogs.ConfirmationDialog(pritext, sectext + \
3151 _('Do you want to continue?'), on_response_ok = (on_ok2, list_))
3152 else:
3153 dialogs.ConfirmationDialogCheck(pritext, sectext + \
3154 _('By removing this contact you also by default remove '
3155 'authorization resulting in him or her always seeing you as '
3156 'offline.'),
3157 _('I want this contact to know my status after removal'),
3158 on_response_ok = (on_ok, list_))
3159 else:
3160 # several contact to remove at the same time
3161 pritext = _('Contacts will be removed from your roster')
3162 jids = ''
3163 for (contact, account) in list_:
3164 jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\
3165 ','
3166 sectext = _('By removing these contacts:%s\nyou also remove '
3167 'authorization resulting in them always seeing you as offline.') % \
3168 jids
3169 dialogs.ConfirmationDialog(pritext, sectext,
3170 on_response_ok = (on_ok2, list_))
3172 def on_send_custom_status(self, widget, contact_list, show, group=None):
3173 '''send custom status'''
3174 # contact_list has only one element except if group != None
3175 def on_response(message, pep_dict):
3176 if message is None: # None if user pressed Cancel
3177 return
3178 account_list = []
3179 for (contact, account) in contact_list:
3180 if account not in account_list:
3181 account_list.append(account)
3182 # 1. update status_sent_to_[groups|users] list
3183 if group:
3184 for account in account_list:
3185 if account not in gajim.interface.status_sent_to_groups:
3186 gajim.interface.status_sent_to_groups[account] = {}
3187 gajim.interface.status_sent_to_groups[account][group] = show
3188 else:
3189 for (contact, account) in contact_list:
3190 if account not in gajim.interface.status_sent_to_users:
3191 gajim.interface.status_sent_to_users[account] = {}
3192 gajim.interface.status_sent_to_users[account][contact.jid] = show
3194 # 2. update privacy lists if main status is invisible
3195 for account in account_list:
3196 if gajim.SHOW_LIST[gajim.connections[account].connected] == \
3197 'invisible':
3198 gajim.connections[account].set_invisible_rule()
3200 # 3. send directed presence
3201 for (contact, account) in contact_list:
3202 our_jid = gajim.get_jid_from_account(account)
3203 jid = contact.jid
3204 if jid == our_jid:
3205 jid += '/' + contact.resource
3206 self.send_status(account, show, message, to=jid)
3208 self.get_status_message(show, on_response, show_pep=False,
3209 always_ask=True)
3211 def on_status_combobox_changed(self, widget):
3212 '''When we change our status via the combobox'''
3213 model = self.status_combobox.get_model()
3214 active = self.status_combobox.get_active()
3215 if active == -1: # no active item
3216 return
3217 if not self.combobox_callback_active:
3218 self.previous_status_combobox_active = active
3219 return
3220 accounts = gajim.connections.keys()
3221 if len(accounts) == 0:
3222 dialogs.ErrorDialog(_('No account available'),
3223 _('You must create an account before you can chat with other contacts.'))
3224 self.update_status_combobox()
3225 return
3226 status = model[active][2].decode('utf-8')
3227 statuses_unified = helpers.statuses_unified() # status "desync'ed" or not
3228 if (active == 7 and statuses_unified) or (active == 9 and \
3229 not statuses_unified):
3230 # 'Change status message' selected:
3231 # do not change show, just show change status dialog
3232 status = model[self.previous_status_combobox_active][2].decode('utf-8')
3233 def on_response(message, pep_dict):
3234 if message is not None: # None if user pressed Cancel
3235 for account in accounts:
3236 if not gajim.config.get_per('accounts', account,
3237 'sync_with_global_status'):
3238 continue
3239 current_show = gajim.SHOW_LIST[
3240 gajim.connections[account].connected]
3241 self.send_status(account, current_show, message)
3242 self.send_pep(account, pep_dict)
3243 self.combobox_callback_active = False
3244 self.status_combobox.set_active(
3245 self.previous_status_combobox_active)
3246 self.combobox_callback_active = True
3247 dialogs.ChangeStatusMessageDialog(on_response, status)
3248 return
3249 # we are about to change show, so save this new show so in case
3250 # after user chooses "Change status message" menuitem
3251 # we can return to this show
3252 self.previous_status_combobox_active = active
3253 connected_accounts = gajim.get_number_of_connected_accounts()
3255 def on_continue(message, pep_dict):
3256 if message is None:
3257 # user pressed Cancel to change status message dialog
3258 self.update_status_combobox()
3259 return
3260 global_sync_accounts = []
3261 for acct in accounts:
3262 if gajim.config.get_per('accounts', acct,
3263 'sync_with_global_status'):
3264 global_sync_accounts.append(acct)
3265 global_sync_connected_accounts = \
3266 gajim.get_number_of_connected_accounts(global_sync_accounts)
3267 for account in accounts:
3268 if not gajim.config.get_per('accounts', account,
3269 'sync_with_global_status'):
3270 continue
3271 # we are connected (so we wanna change show and status)
3272 # or no account is connected and we want to connect with new show
3273 # and status
3275 if not global_sync_connected_accounts > 0 or \
3276 gajim.connections[account].connected > 0:
3277 self.send_status(account, status, message)
3278 self.send_pep(account, pep_dict)
3279 self.update_status_combobox()
3281 if status == 'invisible':
3282 bug_user = False
3283 for account in accounts:
3284 if connected_accounts < 1 or gajim.account_is_connected(account):
3285 if not gajim.config.get_per('accounts', account,
3286 'sync_with_global_status'):
3287 continue
3288 # We're going to change our status to invisible
3289 if self.connected_rooms(account):
3290 bug_user = True
3291 break
3292 if bug_user:
3293 def on_ok():
3294 self.get_status_message(status, on_continue, show_pep=False)
3296 def on_cancel():
3297 self.update_status_combobox()
3299 dialogs.ConfirmationDialog(
3300 _('You are participating in one or more group chats'),
3301 _('Changing your status to invisible will result in '
3302 'disconnection from those group chats. Are you sure you want to '
3303 'go invisible?'), on_reponse_ok=on_ok,
3304 on_response_cancel=on_cancel)
3305 return
3307 self.get_status_message(status, on_continue)
3309 def on_preferences_menuitem_activate(self, widget):
3310 if 'preferences' in gajim.interface.instances:
3311 gajim.interface.instances['preferences'].window.present()
3312 else:
3313 gajim.interface.instances['preferences'] = config.PreferencesWindow()
3315 def on_publish_tune_toggled(self, widget, account):
3316 act = widget.get_active()
3317 gajim.config.set_per('accounts', account, 'publish_tune', act)
3318 if act:
3319 listener = MusicTrackListener.get()
3320 if not self.music_track_changed_signal:
3321 self.music_track_changed_signal = listener.connect(
3322 'music-track-changed', self.music_track_changed)
3323 track = listener.get_playing_track()
3324 self.music_track_changed(listener, track)
3325 else:
3326 # disable it only if no other account use it
3327 for acct in gajim.connections:
3328 if gajim.config.get_per('accounts', acct, 'publish_tune'):
3329 break
3330 else:
3331 listener = MusicTrackListener.get()
3332 listener.disconnect(self.music_track_changed_signal)
3333 self.music_track_changed_signal = None
3335 if gajim.connections[account].pep_supported:
3336 # As many implementations don't support retracting items, we send a
3337 # "Stopped" event first
3338 pep.user_send_tune(account, '')
3339 pep.user_retract_tune(account)
3340 helpers.update_optional_features(account)
3342 def on_pep_services_menuitem_activate(self, widget, account):
3343 if 'pep_services' in gajim.interface.instances[account]:
3344 gajim.interface.instances[account]['pep_services'].window.present()
3345 else:
3346 gajim.interface.instances[account]['pep_services'] = \
3347 config.ManagePEPServicesWindow(account)
3349 def on_add_new_contact(self, widget, account):
3350 dialogs.AddNewContactWindow(account)
3352 def on_join_gc_activate(self, widget, account):
3353 '''when the join gc menuitem is clicked, show the join gc window'''
3354 invisible_show = gajim.SHOW_LIST.index('invisible')
3355 if gajim.connections[account].connected == invisible_show:
3356 dialogs.ErrorDialog(_('You cannot join a group chat while you are '
3357 'invisible'))
3358 return
3359 if 'join_gc' in gajim.interface.instances[account]:
3360 gajim.interface.instances[account]['join_gc'].window.present()
3361 else:
3362 # c http://nkour.blogspot.com/2005/05/pythons-init-return-none-doesnt-return.html
3363 try:
3364 gajim.interface.instances[account]['join_gc'] = \
3365 dialogs.JoinGroupchatWindow(account)
3366 except GajimGeneralException:
3367 pass
3369 def on_new_chat_menuitem_activate(self, widget, account):
3370 dialogs.NewChatDialog(account)
3372 def on_contents_menuitem_activate(self, widget):
3373 helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
3375 def on_faq_menuitem_activate(self, widget):
3376 helpers.launch_browser_mailer('url',
3377 'http://trac.gajim.org/wiki/GajimFaq')
3379 def on_features_menuitem_activate(self, widget):
3380 features_window.FeaturesWindow()
3382 def on_about_menuitem_activate(self, widget):
3383 dialogs.AboutDialog()
3385 def on_accounts_menuitem_activate(self, widget):
3386 if 'accounts' in gajim.interface.instances:
3387 gajim.interface.instances['accounts'].window.present()
3388 else:
3389 gajim.interface.instances['accounts'] = config.AccountsWindow()
3391 def on_file_transfers_menuitem_activate(self, widget):
3392 if gajim.interface.instances['file_transfers'].window.get_property(
3393 'visible'):
3394 gajim.interface.instances['file_transfers'].window.present()
3395 else:
3396 gajim.interface.instances['file_transfers'].window.show_all()
3398 def on_history_menuitem_activate(self, widget):
3399 if 'logs' in gajim.interface.instances:
3400 gajim.interface.instances['logs'].window.present()
3401 else:
3402 gajim.interface.instances['logs'] = history_window.\
3403 HistoryWindow()
3405 def on_show_transports_menuitem_activate(self, widget):
3406 gajim.config.set('show_transports_group', widget.get_active())
3407 self.refilter_shown_roster_items()
3409 def on_manage_bookmarks_menuitem_activate(self, widget):
3410 config.ManageBookmarksWindow()
3412 def on_profile_avatar_menuitem_activate(self, widget, account):
3413 gajim.interface.edit_own_details(account)
3415 def on_execute_command(self, widget, contact, account, resource=None):
3416 '''Execute command. Full JID needed; if it is other contact,
3417 resource is necessary. Widget is unnecessary, only to be
3418 able to make this a callback.'''
3419 jid = contact.jid
3420 if resource is not None:
3421 jid = jid + u'/' + resource
3422 adhoc_commands.CommandWindow(account, jid)
3424 def on_roster_window_focus_in_event(self, widget, event):
3425 # roster received focus, so if we had urgency REMOVE IT
3426 # NOTE: we do not have to read the message to remove urgency
3427 # so this functions does that
3428 gtkgui_helpers.set_unset_urgency_hint(widget, False)
3430 # if a contact row is selected, update colors (eg. for status msg)
3431 # because gtk engines may differ in bg when window is selected
3432 # or not
3433 if len(self._last_selected_contact):
3434 for (jid, account) in self._last_selected_contact:
3435 self.draw_contact(jid, account, selected=True, focus=True)
3437 def on_roster_window_focus_out_event(self, widget, event):
3438 # if a contact row is selected, update colors (eg. for status msg)
3439 # because gtk engines may differ in bg when window is selected
3440 # or not
3441 if len(self._last_selected_contact):
3442 for (jid, account) in self._last_selected_contact:
3443 self.draw_contact(jid, account, selected=True, focus=False)
3445 def on_roster_window_key_press_event(self, widget, event):
3446 if event.keyval == gtk.keysyms.Escape:
3447 if gajim.interface.msg_win_mgr.mode == \
3448 MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
3449 gajim.interface.msg_win_mgr.one_window_opened():
3450 # let message window close the tab
3451 return
3452 list_of_paths = self.tree.get_selection().get_selected_rows()[1]
3453 if not len(list_of_paths) and gajim.interface.systray_enabled and \
3454 not gajim.config.get('quit_on_roster_x_button'):
3455 self.tooltip.hide_tooltip()
3456 self.window.hide()
3458 def on_roster_window_popup_menu(self, widget):
3459 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
3460 self.show_treeview_menu(event)
3462 def on_row_activated(self, widget, path):
3463 '''When an iter is activated (double-click or single click if gnome is
3464 set this way)'''
3465 model = self.modelfilter
3466 account = model[path][C_ACCOUNT].decode('utf-8')
3467 type_ = model[path][C_TYPE]
3468 if type_ in ('group', 'account'):
3469 if self.tree.row_expanded(path):
3470 self.tree.collapse_row(path)
3471 else:
3472 self.tree.expand_row(path, False)
3473 return
3474 jid = model[path][C_JID].decode('utf-8')
3475 resource = None
3476 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
3477 titer = model.get_iter(path)
3478 if contact.is_groupchat():
3479 first_ev = gajim.events.get_first_event(account, jid)
3480 if first_ev and self.open_event(account, jid, first_ev):
3481 # We are invited to a GC
3482 # open event cares about connecting to it
3483 self.remove_groupchat(jid, account)
3484 else:
3485 self.on_groupchat_maximized(None, jid, account)
3486 return
3488 # else
3489 first_ev = gajim.events.get_first_event(account, jid)
3490 if not first_ev:
3491 # look in other resources
3492 for c in gajim.contacts.get_contacts(account, jid):
3493 fjid = c.get_full_jid()
3494 first_ev = gajim.events.get_first_event(account, fjid)
3495 if first_ev:
3496 resource = c.resource
3497 break
3498 if not first_ev and model.iter_has_child(titer):
3499 child_iter = model.iter_children(titer)
3500 while not first_ev and child_iter:
3501 child_jid = model[child_iter][C_JID].decode('utf-8')
3502 first_ev = gajim.events.get_first_event(account, child_jid)
3503 if first_ev:
3504 jid = child_jid
3505 else:
3506 child_iter = model.iter_next(child_iter)
3507 session = None
3508 if first_ev:
3509 if first_ev.type_ in ('chat', 'normal'):
3510 session = first_ev.parameters[8]
3511 fjid = jid
3512 if resource:
3513 fjid += '/' + resource
3514 if self.open_event(account, fjid, first_ev):
3515 return
3516 # else
3517 contact = gajim.contacts.get_contact(account, jid, resource)
3518 if not contact or isinstance(contact, list):
3519 contact = gajim.contacts.get_contact_with_highest_priority(account,
3520 jid)
3521 if jid == gajim.get_jid_from_account(account):
3522 resource = contact.resource
3524 gajim.interface.on_open_chat_window(None, contact, account, \
3525 resource=resource, session=session)
3527 def on_roster_treeview_row_activated(self, widget, path, col=0):
3528 '''When an iter is double clicked: open the first event window'''
3529 if not gajim.single_click:
3530 self.on_row_activated(widget, path)
3532 def on_roster_treeview_row_expanded(self, widget, titer, path):
3533 '''When a row is expanded change the icon of the arrow'''
3534 self._toggeling_row = True
3535 model = widget.get_model()
3536 child_model = model.get_model()
3537 child_iter = model.convert_iter_to_child_iter(titer)
3539 if self.regroup: # merged accounts
3540 accounts = gajim.connections.keys()
3541 else:
3542 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
3544 type_ = model[titer][C_TYPE]
3545 if type_ == 'group':
3546 child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[
3547 '16']['opened']
3548 group = model[titer][C_JID].decode('utf-8')
3549 for account in accounts:
3550 if group in gajim.groups[account]: # This account has this group
3551 gajim.groups[account][group]['expand'] = True
3552 if account + group in self.collapsed_rows:
3553 self.collapsed_rows.remove(account + group)
3554 for contact in gajim.contacts.iter_contacts(account):
3555 jid = contact.jid
3556 if group in contact.groups and gajim.contacts.is_big_brother(
3557 account, jid, accounts) and account + group + jid \
3558 not in self.collapsed_rows:
3559 titers = self._get_contact_iter(jid, account)
3560 for titer in titers:
3561 path = model.get_path(titer)
3562 self.tree.expand_row(path, False)
3563 elif type_ == 'account':
3564 account = accounts[0] # There is only one cause we don't use merge
3565 if account in self.collapsed_rows:
3566 self.collapsed_rows.remove(account)
3567 self.draw_account(account)
3568 # When we expand, groups are collapsed. Restore expand state
3569 for group in gajim.groups[account]:
3570 if gajim.groups[account][group]['expand']:
3571 titer = self._get_group_iter(group, account)
3572 if titer:
3573 path = model.get_path(titer)
3574 self.tree.expand_row(path, False)
3575 elif type_ == 'contact':
3576 # Metacontact got toggled, update icon
3577 jid = model[titer][C_JID].decode('utf-8')
3578 account = model[titer][C_ACCOUNT].decode('utf-8')
3579 contact = gajim.contacts.get_contact(account, jid)
3580 for group in contact.groups:
3581 if account + group + jid in self.collapsed_rows:
3582 self.collapsed_rows.remove(account + group + jid)
3583 family = gajim.contacts.get_metacontacts_family(account, jid)
3584 nearby_family = \
3585 self._get_nearby_family_and_big_brother(family, account)[0]
3586 # Redraw all brothers to show pending events
3587 for data in nearby_family:
3588 self.draw_contact(data['jid'], data['account'])
3590 self._toggeling_row = False
3592 def on_roster_treeview_row_collapsed(self, widget, titer, path):
3593 '''When a row is collapsed change the icon of the arrow'''
3594 self._toggeling_row = True
3595 model = widget.get_model()
3596 child_model = model.get_model()
3597 child_iter = model.convert_iter_to_child_iter(titer)
3599 if self.regroup: # merged accounts
3600 accounts = gajim.connections.keys()
3601 else:
3602 accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
3604 type_ = model[titer][C_TYPE]
3605 if type_ == 'group':
3606 child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[
3607 '16']['closed']
3608 group = model[titer][C_JID].decode('utf-8')
3609 for account in accounts:
3610 if group in gajim.groups[account]: # This account has this group
3611 gajim.groups[account][group]['expand'] = False
3612 if account + group not in self.collapsed_rows:
3613 self.collapsed_rows.append(account + group)
3614 elif type_ == 'account':
3615 account = accounts[0] # There is only one cause we don't use merge
3616 if account not in self.collapsed_rows:
3617 self.collapsed_rows.append(account)
3618 self.draw_account(account)
3619 elif type_ == 'contact':
3620 # Metacontact got toggled, update icon
3621 jid = model[titer][C_JID].decode('utf-8')
3622 account = model[titer][C_ACCOUNT].decode('utf-8')
3623 contact = gajim.contacts.get_contact(account, jid)
3624 for group in contact.groups:
3625 if account + group + jid not in self.collapsed_rows:
3626 self.collapsed_rows.append(account + group + jid)
3627 family = gajim.contacts.get_metacontacts_family(account, jid)
3628 nearby_family = \
3629 self._get_nearby_family_and_big_brother(family, account)[0]
3630 # Redraw all brothers to show pending events
3631 for data in nearby_family:
3632 self.draw_contact(data['jid'], data['account'])
3634 self._toggeling_row = False
3636 def on_modelfilter_row_has_child_toggled(self, model, path, titer):
3637 '''Called when a row has gotten the first or lost its last child row.
3639 Expand Parent if necessary.
3640 '''
3641 if self._toggeling_row:
3642 # Signal is emitted when we write to our model
3643 return
3645 type_ = model[titer][C_TYPE]
3646 account = model[titer][C_ACCOUNT]
3647 if not account:
3648 return
3650 account = account.decode('utf-8')
3652 if type_ == 'contact':
3653 child_iter = model.convert_iter_to_child_iter(titer)
3654 if self.model.iter_has_child(child_iter):
3655 # we are a bigbrother metacontact
3656 # redraw us to show/hide expand icon
3657 if self.filtering:
3658 # Prevent endless loops
3659 jid = model[titer][C_JID].decode('utf-8')
3660 gobject.idle_add(self.draw_contact, jid, account)
3661 elif type_ == 'group':
3662 group = model[titer][C_JID].decode('utf-8')
3663 self._adjust_group_expand_collapse_state(group, account)
3664 elif type_ == 'account':
3665 self._adjust_account_expand_collapse_state(account)
3667 # Selection can change when the model is filtered
3668 # Only write to the model when filtering is finished!
3670 # FIXME: When we are filtering our custom colors are somehow lost
3672 # def on_treeview_selection_changed(self, selection):
3673 # '''Called when selection in TreeView has changed.
3675 # Redraw unselected rows to make status message readable
3676 # on all possible backgrounds.
3677 # '''
3678 # model, list_of_paths = selection.get_selected_rows()
3679 # if len(self._last_selected_contact):
3680 # # update unselected rows
3681 # for (jid, account) in self._last_selected_contact:
3682 # gobject.idle_add(self.draw_contact, jid, account)
3683 # self._last_selected_contact = []
3684 # if len(list_of_paths) == 0:
3685 # return
3686 # for path in list_of_paths:
3687 # row = model[path]
3688 # if row[C_TYPE] != 'contact':
3689 # self._last_selected_contact = []
3690 # return
3691 # jid = row[C_JID].decode('utf-8')
3692 # account = row[C_ACCOUNT].decode('utf-8')
3693 # self._last_selected_contact.append((jid, account))
3694 # gobject.idle_add(self.draw_contact, jid, account, True)
3696 def on_service_disco_menuitem_activate(self, widget, account):
3697 server_jid = gajim.config.get_per('accounts', account, 'hostname')
3698 if server_jid in gajim.interface.instances[account]['disco']:
3699 gajim.interface.instances[account]['disco'][server_jid].\
3700 window.present()
3701 else:
3702 try:
3703 # Object will add itself to the window dict
3704 disco.ServiceDiscoveryWindow(account, address_entry=True)
3705 except GajimGeneralException:
3706 pass
3708 def on_show_offline_contacts_menuitem_activate(self, widget):
3709 '''when show offline option is changed:
3710 redraw the treeview'''
3711 gajim.config.set('showoffline', not gajim.config.get('showoffline'))
3712 self.refilter_shown_roster_items()
3713 w = self.xml.get_widget('show_only_active_contacts_menuitem')
3714 if gajim.config.get('showoffline'):
3715 # We need to filter twice to show groups with no contacts inside
3716 # in the correct expand state
3717 self.refilter_shown_roster_items()
3718 w.set_sensitive(False)
3719 else:
3720 w.set_sensitive(True)
3722 def on_show_only_active_contacts_menuitem_activate(self, widget):
3723 '''when show only active contact option is changed:
3724 redraw the treeview'''
3725 gajim.config.set('show_only_chat_and_online', not gajim.config.get(
3726 'show_only_chat_and_online'))
3727 self.refilter_shown_roster_items()
3728 w = self.xml.get_widget('show_offline_contacts_menuitem')
3729 if gajim.config.get('show_only_chat_and_online'):
3730 # We need to filter twice to show groups with no contacts inside
3731 # in the correct expand state
3732 self.refilter_shown_roster_items()
3733 w.set_sensitive(False)
3734 else:
3735 w.set_sensitive(True)
3737 def on_view_menu_activate(self, widget):
3738 # Hide the show roster menu if we are not in the right windowing mode.
3739 if self.hpaned.get_child2() is not None:
3740 self.xml.get_widget('show_roster_menuitem').show()
3741 else:
3742 self.xml.get_widget('show_roster_menuitem').hide()
3744 def on_show_roster_menuitem_toggled(self, widget):
3745 # when num controls is 0 this menuitem is hidden, but still need to
3746 # disable keybinding
3747 if self.hpaned.get_child2() is not None:
3748 self.show_roster_vbox(widget.get_active())
3750 ################################################################################
3751 ### Drag and Drop handling
3752 ################################################################################
3754 def drag_data_get_data(self, treeview, context, selection, target_id, etime):
3755 model, list_of_paths = self.tree.get_selection().get_selected_rows()
3756 if len(list_of_paths) != 1:
3757 return
3758 path = list_of_paths[0]
3759 data = ''
3760 if len(path) >= 3:
3761 data = model[path][C_JID]
3762 selection.set(selection.target, 8, data)
3764 def drag_begin(self, treeview, context):
3765 self.dragging = True
3767 def drag_end(self, treeview, context):
3768 self.dragging = False
3770 def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
3771 c_dest, was_big_brother, context, etime):
3772 gajim.connections[account_dest].send_contacts([c_source], c_dest.jid)
3774 def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
3775 c_dest, was_big_brother, context, etime):
3777 if not gajim.connections[account_source].private_storage_supported or not\
3778 gajim.connections[account_dest].private_storage_supported:
3779 dialogs.WarningDialog(_('Metacontacts storage not supported by your '
3780 'server'),
3781 _('Your server does not support storing metacontacts information. '
3782 'So those information will not be saved on next reconnection.'))
3784 def merge_contacts(is_checked=None):
3785 contacts = 0
3786 if is_checked is not None: # dialog has been shown
3787 if is_checked: # user does not want to be asked again
3788 gajim.config.set('confirm_metacontacts', 'no')
3789 else:
3790 gajim.config.set('confirm_metacontacts', 'yes')
3792 # We might have dropped on a metacontact.
3793 # Remove it and readd later with updated family info
3794 dest_family = gajim.contacts.get_metacontacts_family(account_dest,
3795 c_dest.jid)
3796 if dest_family:
3797 self._remove_metacontact_family(dest_family, account_dest)
3798 source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid)
3799 if dest_family == source_family:
3800 n = contacts = len(dest_family)
3801 for tag in source_family:
3802 if tag['jid'] == c_source.jid:
3803 tag['order'] = contacts
3804 continue
3805 if 'order' in tag:
3806 n -= 1
3807 tag['order'] = n
3808 else:
3809 self._remove_entity(c_dest, account_dest)
3811 old_family = gajim.contacts.get_metacontacts_family(account_source,
3812 c_source.jid)
3813 old_groups = c_source.groups
3815 # Remove old source contact(s)
3816 if was_big_brother:
3817 # We have got little brothers. Readd them all
3818 self._remove_metacontact_family(old_family, account_source)
3819 else:
3820 # We are only a litle brother. Simply remove us from our big brother
3821 if self._get_contact_iter(c_source.jid, account_source):
3822 # When we have been in the group before.
3823 # Do not try to remove us again
3824 self._remove_entity(c_source, account_source)
3826 own_data = {}
3827 own_data['jid'] = c_source.jid
3828 own_data['account'] = account_source
3829 # Don't touch the rest of the family
3830 old_family = [own_data]
3832 # Apply new tag and update contact
3833 for data in old_family:
3834 if account_source != data['account'] and not self.regroup:
3835 continue
3837 _account = data['account']
3838 _jid = data['jid']
3839 _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
3841 _contact.groups = c_dest.groups[:]
3842 gajim.contacts.add_metacontact(account_dest, c_dest.jid,
3843 _account, _contact.jid, contacts)
3844 gajim.connections[account_source].update_contact(_contact.jid,
3845 _contact.name, _contact.groups)
3847 # Re-add all and update GUI
3848 new_family = gajim.contacts.get_metacontacts_family(account_source,
3849 c_source.jid)
3850 brothers = self._add_metacontact_family(new_family, account_source)
3852 for c, acc in brothers:
3853 self.draw_completely(c.jid, acc)
3855 old_groups.extend(c_dest.groups)
3856 for g in old_groups:
3857 self.draw_group(g, account_source)
3859 self.draw_account(account_source)
3860 context.finish(True, True, etime)
3862 confirm_metacontacts = gajim.config.get('confirm_metacontacts')
3863 if confirm_metacontacts == 'no':
3864 merge_contacts()
3865 return
3866 pritext = _('You are about to create a metacontact. Are you sure you want'
3867 ' to continue?')
3868 sectext = _('Metacontacts are a way to regroup several contacts in one '
3869 'line. Generally it is used when the same person has several Jabber '
3870 'accounts or transport accounts.')
3871 dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
3872 _('Do _not ask me again'), on_response_ok=merge_contacts)
3873 if not confirm_metacontacts: # First time we see this window
3874 dlg.checkbutton.set_active(True)
3877 def on_drop_in_group(self, widget, account, c_source, grp_dest,
3878 is_big_brother, context, etime, grp_source = None):
3879 if is_big_brother:
3880 # add whole metacontact to new group
3881 self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
3882 # remove afterwards so the contact is not moved to General in the
3883 # meantime
3884 if grp_dest != grp_source:
3885 self.remove_contact_from_groups(c_source.jid, account, [grp_source])
3886 else:
3887 # Normal contact or little brother
3888 family = gajim.contacts.get_metacontacts_family(account,
3889 c_source.jid)
3890 if family:
3891 # Little brother
3892 # Remove whole family. Remove us from the family.
3893 # Then re-add other family members.
3894 self._remove_metacontact_family(family, account)
3895 gajim.contacts.remove_metacontact(account, c_source.jid)
3896 for data in family:
3897 if account != data['account'] and not self.regroup:
3898 continue
3899 if data['jid'] == c_source.jid and\
3900 data['account'] == account:
3901 continue
3902 self.add_contact(data['jid'], data['account'])
3903 break
3905 self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
3907 else:
3908 # Normal contact
3909 self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
3910 # remove afterwards so the contact is not moved to General in the
3911 # meantime
3912 if grp_dest != grp_source:
3913 self.remove_contact_from_groups(c_source.jid, account,
3914 [grp_source])
3916 if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY):
3917 context.finish(True, True, etime)
3920 def drag_drop(self, treeview, context, x, y, timestamp):
3921 target_list = treeview.drag_dest_get_target_list()
3922 target = treeview.drag_dest_find_target(context, target_list)
3923 treeview.drag_get_data(context, target)
3924 context.finish(False, True)
3925 return True
3927 def drag_data_received_data(self, treeview, context, x, y, selection, info,
3928 etime):
3929 treeview.stop_emission('drag_data_received')
3930 drop_info = treeview.get_dest_row_at_pos(x, y)
3931 if not drop_info:
3932 return
3933 if not selection.data:
3934 return # prevents tb when several entrys are dragged
3935 model = treeview.get_model()
3936 data = selection.data
3937 path_dest, position = drop_info
3939 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \
3940 and path_dest[1] == 0: # dropped before the first group
3941 return
3942 if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
3943 # dropped before a group: we drop it in the previous group every time
3944 path_dest = (path_dest[0], path_dest[1]-1)
3945 # destination: the row something got dropped on
3946 iter_dest = model.get_iter(path_dest)
3947 type_dest = model[iter_dest][C_TYPE].decode('utf-8')
3948 jid_dest = model[iter_dest][C_JID].decode('utf-8')
3949 account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
3951 # drop on account row in merged mode, we cannot know the desired account
3952 if account_dest == 'all':
3953 return
3954 # nothing can be done, if destination account is offline
3955 if gajim.connections[account_dest].connected < 2:
3956 return
3958 # A file got dropped on the roster
3959 if info == self.TARGET_TYPE_URI_LIST:
3960 if len(path_dest) < 3:
3961 return
3962 if type_dest != 'contact':
3963 return
3964 c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
3965 jid_dest)
3966 uri = data.strip()
3967 uri_splitted = uri.split() # we may have more than one file dropped
3968 try:
3969 # This is always the last element in windows
3970 uri_splitted.remove('\0')
3971 except ValueError:
3972 pass
3973 nb_uri = len(uri_splitted)
3974 # Check the URIs
3975 bad_uris = []
3976 for a_uri in uri_splitted:
3977 path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
3978 if not os.path.isfile(path):
3979 bad_uris.append(a_uri)
3980 if len(bad_uris):
3981 dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
3982 return
3983 def _on_send_files(account, jid, uris):
3984 c = gajim.contacts.get_contact_with_highest_priority(account, jid)
3985 for uri in uris:
3986 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
3987 if os.path.isfile(path): # is it file?
3988 gajim.interface.instances['file_transfers'].send_file(
3989 account, c, path)
3990 # Popup dialog to confirm sending
3991 prim_text = 'Send file?'
3992 sec_text = i18n.ngettext('Do you want to send this file to %s:',
3993 'Do you want to send these files to %s:', nb_uri) %\
3994 c_dest.get_shown_name()
3995 for uri in uri_splitted:
3996 path = helpers.get_file_path_from_dnd_dropped_uri(uri)
3997 sec_text += '\n' + os.path.basename(path)
3998 dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
3999 on_response_ok = (_on_send_files, account_dest, jid_dest,
4000 uri_splitted))
4001 dialog.popup()
4002 return
4004 # a roster entry was dragged and dropped somewhere in the roster
4006 # source: the row that was dragged
4007 path_source = treeview.get_selection().get_selected_rows()[1][0]
4008 iter_source = model.get_iter(path_source)
4009 type_source = model[iter_source][C_TYPE]
4010 account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
4012 # Only normal contacts can be dragged
4013 if type_source != 'contact':
4014 return
4015 if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
4016 return
4018 # A contact was dropped
4019 if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
4020 # drop on zeroconf account, adding not possible
4021 return
4022 if type_dest == 'self_contact':
4023 # drop on self contact row
4024 return
4025 if type_dest == 'account' and account_source == account_dest:
4026 # drop on the account it was dragged from
4027 return
4028 if type_dest == 'groupchat':
4029 # drop on a minimized groupchat
4030 # TODO: Invite to groupchat
4031 return
4033 # Get valid source group, jid and contact
4034 it = iter_source
4035 while model[it][C_TYPE] == 'contact':
4036 it = model.iter_parent(it)
4037 grp_source = model[it][C_JID].decode('utf-8')
4038 if grp_source in helpers.special_groups and \
4039 grp_source not in ('Not in Roster', 'Observers'):
4040 # a transport or a minimized groupchat was dragged
4041 # we can add it to other accounts but not move it to another group,
4042 # see below
4043 return
4044 jid_source = data.decode('utf-8')
4045 c_source = gajim.contacts.get_contact_with_highest_priority(
4046 account_source, jid_source)
4048 # Get destination group
4049 grp_dest = None
4050 if type_dest == 'group':
4051 grp_dest = model[iter_dest][C_JID].decode('utf-8')
4052 elif type_dest in ('contact', 'agent'):
4053 it = iter_dest
4054 while model[it][C_TYPE] != 'group':
4055 it = model.iter_parent(it)
4056 grp_dest = model[it][C_JID].decode('utf-8')
4057 if grp_dest in helpers.special_groups:
4058 return
4060 if jid_source == jid_dest:
4061 if grp_source == grp_dest and account_source == account_dest:
4062 # Drop on self
4063 return
4065 # contact drop somewhere in or on a foreign account
4066 if (type_dest == 'account' or not self.regroup) and \
4067 account_source != account_dest:
4068 # add to account in specified group
4069 dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
4070 user_nick=c_source.name, group=grp_dest)
4071 return
4073 # we may not add contacts from special_groups
4074 if grp_source in helpers.special_groups :
4075 return
4077 # Is the contact we drag a meta contact?
4078 accounts = (self.regroup and gajim.contacts.get_accounts()) or account_source
4079 is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source, accounts)
4081 # Contact drop on group row or between two contacts
4082 if type_dest == 'group' or position == gtk.TREE_VIEW_DROP_BEFORE or \
4083 position == gtk.TREE_VIEW_DROP_AFTER:
4084 self.on_drop_in_group(None, account_source, c_source, grp_dest,
4085 is_big_brother, context, etime, grp_source)
4086 return
4088 # Contact drop on another contact, make meta contacts
4089 if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \
4090 position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE:
4091 c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
4092 jid_dest)
4093 if not c_dest:
4094 # c_dest is None if jid_dest doesn't belong to account
4095 return
4096 menu = gtk.Menu()
4097 item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(),
4098 c_dest.get_shown_name()))
4099 item.connect('activate', self.on_drop_rosterx, account_source,
4100 c_source, account_dest, c_dest, is_big_brother, context, etime)
4101 menu.append(item)
4103 item = gtk.MenuItem(_('Make %s and %s metacontacts') % (
4104 c_source.get_shown_name(), c_dest.get_shown_name()))
4105 item.connect('activate', self.on_drop_in_contact, account_source,
4106 c_source, account_dest, c_dest, is_big_brother, context, etime)
4108 menu.append(item)
4110 menu.attach_to_widget(self.tree, None)
4111 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
4112 menu.show_all()
4113 menu.popup(None, None, None, 1, etime)
4114 # self.on_drop_in_contact(treeview, account_source, c_source,
4115 # account_dest, c_dest, is_big_brother, context, etime)
4117 ################################################################################
4118 ### Everything about images and icons....
4119 ### Cleanup assigned to Jim++ :-)
4120 ################################################################################
4122 def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
4123 '''check jid and return the appropriate state images dict for
4124 the demanded size. icon_name is taken into account when jid is from
4125 transport: transport iconset doesn't contain all icons, so we fall back
4126 to jabber one'''
4127 transport = gajim.get_transport_name_from_jid(jid)
4128 if transport and size in self.transports_state_images:
4129 if transport not in self.transports_state_images[size]:
4130 # we don't have iconset for this transport loaded yet. Let's do it
4131 self.make_transport_state_images(transport)
4132 if transport in self.transports_state_images[size] and \
4133 icon_name in self.transports_state_images[size][transport]:
4134 return self.transports_state_images[size][transport]
4135 return gajim.interface.jabber_state_images[size]
4137 def make_transport_state_images(self, transport):
4138 '''initialise opened and closed 'transport' iconset dict'''
4139 if gajim.config.get('use_transports_iconsets'):
4140 folder = os.path.join(helpers.get_transport_path(transport),
4141 '16x16')
4142 pixo, pixc = gtkgui_helpers.load_icons_meta()
4143 self.transports_state_images['opened'][transport] = \
4144 gtkgui_helpers.load_iconset(folder, pixo, transport=True)
4145 self.transports_state_images['closed'][transport] = \
4146 gtkgui_helpers.load_iconset(folder, pixc, transport=True)
4147 folder = os.path.join(helpers.get_transport_path(transport), '32x32')
4148 self.transports_state_images['32'][transport] = \
4149 gtkgui_helpers.load_iconset(folder, transport=True)
4150 folder = os.path.join(helpers.get_transport_path(transport), '16x16')
4151 self.transports_state_images['16'][transport] = \
4152 gtkgui_helpers.load_iconset(folder, transport=True)
4154 def update_jabber_state_images(self):
4155 # Update the roster
4156 self.setup_and_draw_roster()
4157 # Update the status combobox
4158 model = self.status_combobox.get_model()
4159 titer = model.get_iter_root()
4160 while titer:
4161 if model[titer][2] != '':
4162 # If it's not change status message iter
4163 # eg. if it has show parameter not ''
4164 model[titer][1] = gajim.interface.jabber_state_images['16'][model[
4165 titer][2]]
4166 titer = model.iter_next(titer)
4167 # Update the systray
4168 if gajim.interface.systray_enabled:
4169 gajim.interface.systray.set_img()
4171 for win in gajim.interface.msg_win_mgr.windows():
4172 for ctrl in win.controls():
4173 ctrl.update_ui()
4174 win.redraw_tab(ctrl)
4176 self.update_status_combobox()
4178 def set_account_status_icon(self, account):
4179 status = gajim.connections[account].connected
4180 child_iterA = self._get_account_iter(account, self.model)
4181 if not child_iterA:
4182 return
4183 if not self.regroup:
4184 show = gajim.SHOW_LIST[status]
4185 else: # accounts merged
4186 show = helpers.get_global_show()
4187 self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[
4188 '16'][show]
4190 ################################################################################
4191 ### Style and theme related methods
4192 ################################################################################
4194 def show_title(self):
4195 change_title_allowed = gajim.config.get('change_roster_title')
4196 if not change_title_allowed:
4197 return
4199 if gajim.config.get('one_message_window') == 'always_with_roster':
4200 # always_with_roster mode defers to the MessageWindow
4201 if not gajim.interface.msg_win_mgr.one_window_opened():
4202 # No MessageWindow to defer to
4203 self.window.set_title('Gajim')
4204 return
4206 nb_unread = 0
4207 start = ''
4208 for account in gajim.connections:
4209 # Count events in roster title only if we don't auto open them
4210 if not helpers.allow_popup_window(account):
4211 nb_unread += gajim.events.get_nb_events(['chat', 'normal',
4212 'file-request', 'file-error', 'file-completed',
4213 'file-request-error', 'file-send-error', 'file-stopped',
4214 'printed_chat'], account)
4215 if nb_unread > 1:
4216 start = '[' + str(nb_unread) + '] '
4217 elif nb_unread == 1:
4218 start = '* '
4220 self.window.set_title(start + 'Gajim')
4222 gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
4224 def _change_style(self, model, path, titer, option):
4225 if option is None or model[titer][C_TYPE] == option:
4226 # We changed style for this type of row
4227 model[titer][C_NAME] = model[titer][C_NAME]
4229 def change_roster_style(self, option):
4230 self.model.foreach(self._change_style, option)
4231 for win in gajim.interface.msg_win_mgr.windows():
4232 win.repaint_themed_widgets()
4234 def repaint_themed_widgets(self):
4235 '''Notify windows that contain themed widgets to repaint them'''
4236 for win in gajim.interface.msg_win_mgr.windows():
4237 win.repaint_themed_widgets()
4238 for account in gajim.connections:
4239 for addr in gajim.interface.instances[account]['disco']:
4240 gajim.interface.instances[account]['disco'][addr].paint_banner()
4241 for ctrl in gajim.interface.minimized_controls[account].values():
4242 ctrl.repaint_themed_widgets()
4244 def update_avatar_in_gui(self, jid, account):
4245 # Update roster
4246 self.draw_avatar(jid, account)
4247 # Update chat window
4249 ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
4250 if ctrl:
4251 ctrl.show_avatar()
4253 def on_roster_treeview_style_set(self, treeview, style):
4254 '''When style (theme) changes, redraw all contacts'''
4255 for contact in self._iter_contact_rows():
4256 self.draw_contact(contact[C_JID].decode('utf-8'),
4257 contact[C_ACCOUNT].decode('utf-8'))
4259 def set_renderer_color(self, renderer, style, set_background=True):
4260 '''set style for treeview cell, using PRELIGHT system color'''
4261 if set_background:
4262 bgcolor = self.tree.style.bg[style]
4263 renderer.set_property('cell-background-gdk', bgcolor)
4264 else:
4265 fgcolor = self.tree.style.fg[style]
4266 renderer.set_property('foreground-gdk', fgcolor)
4268 def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
4269 '''When a row is added, set properties for icon renderer'''
4270 theme = gajim.config.get('roster_theme')
4271 type_ = model[titer][C_TYPE]
4272 if type_ == 'account':
4273 color = gajim.config.get_per('themes', theme, 'accountbgcolor')
4274 if color:
4275 renderer.set_property('cell-background', color)
4276 else:
4277 self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
4278 renderer.set_property('xalign', 0)
4279 elif type_ == 'group':
4280 color = gajim.config.get_per('themes', theme, 'groupbgcolor')
4281 if color:
4282 renderer.set_property('cell-background', color)
4283 else:
4284 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
4285 renderer.set_property('xalign', 0.2)
4286 elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4287 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4288 # This can append when at the moment we add the row
4289 return
4290 jid = model[titer][C_JID].decode('utf-8')
4291 account = model[titer][C_ACCOUNT].decode('utf-8')
4292 if jid in gajim.newly_added[account]:
4293 renderer.set_property('cell-background', gajim.config.get(
4294 'just_connected_bg_color'))
4295 elif jid in gajim.to_be_removed[account]:
4296 renderer.set_property('cell-background', gajim.config.get(
4297 'just_disconnected_bg_color'))
4298 else:
4299 color = gajim.config.get_per('themes', theme, 'contactbgcolor')
4300 if color:
4301 renderer.set_property('cell-background', color)
4302 else:
4303 renderer.set_property('cell-background', None)
4304 parent_iter = model.iter_parent(titer)
4305 if model[parent_iter][C_TYPE] == 'contact':
4306 renderer.set_property('xalign', 1)
4307 else:
4308 renderer.set_property('xalign', 0.4)
4309 renderer.set_property('width', 26)
4311 def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
4312 '''When a row is added, set properties for name renderer'''
4313 theme = gajim.config.get('roster_theme')
4314 type_ = model[titer][C_TYPE]
4315 if type_ == 'account':
4316 color = gajim.config.get_per('themes', theme, 'accounttextcolor')
4317 if color:
4318 renderer.set_property('foreground', color)
4319 else:
4320 self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False)
4321 color = gajim.config.get_per('themes', theme, 'accountbgcolor')
4322 if color:
4323 renderer.set_property('cell-background', color)
4324 else:
4325 self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
4326 renderer.set_property('font',
4327 gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
4328 renderer.set_property('xpad', 0)
4329 renderer.set_property('width', 3)
4330 elif type_ == 'group':
4331 color = gajim.config.get_per('themes', theme, 'grouptextcolor')
4332 if color:
4333 renderer.set_property('foreground', color)
4334 else:
4335 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
4336 color = gajim.config.get_per('themes', theme, 'groupbgcolor')
4337 if color:
4338 renderer.set_property('cell-background', color)
4339 else:
4340 self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
4341 renderer.set_property('font',
4342 gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
4343 renderer.set_property('xpad', 4)
4344 elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4345 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4346 # This can append when at the moment we add the row
4347 return
4348 jid = model[titer][C_JID].decode('utf-8')
4349 account = model[titer][C_ACCOUNT].decode('utf-8')
4350 color = None
4351 if type_ == 'groupchat':
4352 ctrl = gajim.interface.minimized_controls[account].get(jid, None)
4353 if ctrl and ctrl.attention_flag:
4354 color = gajim.config.get_per('themes', theme,
4355 'state_muc_directed_msg_color')
4356 renderer.set_property('foreground', 'red')
4357 if not color:
4358 color = gajim.config.get_per('themes', theme, 'contacttextcolor')
4359 if color:
4360 renderer.set_property('foreground', color)
4361 else:
4362 renderer.set_property('foreground', None)
4363 if jid in gajim.newly_added[account]:
4364 renderer.set_property('cell-background', gajim.config.get(
4365 'just_connected_bg_color'))
4366 elif jid in gajim.to_be_removed[account]:
4367 renderer.set_property('cell-background', gajim.config.get(
4368 'just_disconnected_bg_color'))
4369 else:
4370 color = gajim.config.get_per('themes', theme, 'contactbgcolor')
4371 if color:
4372 renderer.set_property('cell-background', color)
4373 else:
4374 renderer.set_property('cell-background', None)
4375 renderer.set_property('font',
4376 gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
4377 parent_iter = model.iter_parent(titer)
4378 if model[parent_iter][C_TYPE] == 'contact':
4379 renderer.set_property('xpad', 16)
4380 else:
4381 renderer.set_property('xpad', 8)
4384 def _fill_mood_pixbuf_renderer(self, column, renderer, model, titer,
4385 data = None):
4386 '''When a row is added, set properties for avatar renderer'''
4387 theme = gajim.config.get('roster_theme')
4388 type_ = model[titer][C_TYPE]
4389 if type_ == 'group':
4390 renderer.set_property('visible', False)
4391 return
4393 # allocate space for the icon only if needed
4394 if model[titer][C_MOOD_PIXBUF]:
4395 renderer.set_property('visible', True)
4396 else:
4397 renderer.set_property('visible', False)
4398 if type_ == 'account':
4399 color = gajim.config.get_per('themes', theme,
4400 'accountbgcolor')
4401 if color:
4402 renderer.set_property('cell-background', color)
4403 else:
4404 self.set_renderer_color(renderer,
4405 gtk.STATE_ACTIVE)
4406 # align pixbuf to the right)
4407 renderer.set_property('xalign', 1)
4408 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4409 elif type_:
4410 if not model[titer][C_JID] \
4411 or not model[titer][C_ACCOUNT]:
4412 # This can append at the moment we add the row
4413 return
4414 jid = model[titer][C_JID].decode('utf-8')
4415 account = model[titer][C_ACCOUNT].decode('utf-8')
4416 if jid in gajim.newly_added[account]:
4417 renderer.set_property('cell-background',
4418 gajim.config.get(
4419 'just_connected_bg_color'))
4420 elif jid in gajim.to_be_removed[account]:
4421 renderer.set_property('cell-background',
4422 gajim.config.get(
4423 'just_disconnected_bg_color'))
4424 else:
4425 color = gajim.config.get_per('themes',
4426 theme, 'contactbgcolor')
4427 if color:
4428 renderer.set_property(
4429 'cell-background', color)
4430 else:
4431 renderer.set_property(
4432 'cell-background', None)
4433 # align pixbuf to the right
4434 renderer.set_property('xalign', 1)
4437 def _fill_activity_pixbuf_renderer(self, column, renderer, model, titer,
4438 data = None):
4439 '''When a row is added, set properties for avatar renderer'''
4440 theme = gajim.config.get('roster_theme')
4441 type_ = model[titer][C_TYPE]
4442 if type_ == 'group':
4443 renderer.set_property('visible', False)
4444 return
4446 # allocate space for the icon only if needed
4447 if model[titer][C_ACTIVITY_PIXBUF]:
4448 renderer.set_property('visible', True)
4449 else:
4450 renderer.set_property('visible', False)
4451 if type_ == 'account':
4452 color = gajim.config.get_per('themes', theme,
4453 'accountbgcolor')
4454 if color:
4455 renderer.set_property('cell-background', color)
4456 else:
4457 self.set_renderer_color(renderer,
4458 gtk.STATE_ACTIVE)
4459 # align pixbuf to the right)
4460 renderer.set_property('xalign', 1)
4461 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4462 elif type_:
4463 if not model[titer][C_JID] \
4464 or not model[titer][C_ACCOUNT]:
4465 # This can append at the moment we add the row
4466 return
4467 jid = model[titer][C_JID].decode('utf-8')
4468 account = model[titer][C_ACCOUNT].decode('utf-8')
4469 if jid in gajim.newly_added[account]:
4470 renderer.set_property('cell-background',
4471 gajim.config.get(
4472 'just_connected_bg_color'))
4473 elif jid in gajim.to_be_removed[account]:
4474 renderer.set_property('cell-background',
4475 gajim.config.get(
4476 'just_disconnected_bg_color'))
4477 else:
4478 color = gajim.config.get_per('themes',
4479 theme, 'contactbgcolor')
4480 if color:
4481 renderer.set_property(
4482 'cell-background', color)
4483 else:
4484 renderer.set_property(
4485 'cell-background', None)
4486 # align pixbuf to the right
4487 renderer.set_property('xalign', 1)
4490 def _fill_tune_pixbuf_renderer(self, column, renderer, model, titer,
4491 data = None):
4492 '''When a row is added, set properties for avatar renderer'''
4493 theme = gajim.config.get('roster_theme')
4494 type_ = model[titer][C_TYPE]
4495 if type_ == 'group':
4496 renderer.set_property('visible', False)
4497 return
4499 # allocate space for the icon only if needed
4500 if model[titer][C_TUNE_PIXBUF]:
4501 renderer.set_property('visible', True)
4502 else:
4503 renderer.set_property('visible', False)
4504 if type_ == 'account':
4505 color = gajim.config.get_per('themes', theme,
4506 'accountbgcolor')
4507 if color:
4508 renderer.set_property('cell-background', color)
4509 else:
4510 self.set_renderer_color(renderer,
4511 gtk.STATE_ACTIVE)
4512 # align pixbuf to the right)
4513 renderer.set_property('xalign', 1)
4514 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4515 elif type_:
4516 if not model[titer][C_JID] \
4517 or not model[titer][C_ACCOUNT]:
4518 # This can append at the moment we add the row
4519 return
4520 jid = model[titer][C_JID].decode('utf-8')
4521 account = model[titer][C_ACCOUNT].decode('utf-8')
4522 if jid in gajim.newly_added[account]:
4523 renderer.set_property('cell-background',
4524 gajim.config.get(
4525 'just_connected_bg_color'))
4526 elif jid in gajim.to_be_removed[account]:
4527 renderer.set_property('cell-background',
4528 gajim.config.get(
4529 'just_disconnected_bg_color'))
4530 else:
4531 color = gajim.config.get_per('themes',
4532 theme, 'contactbgcolor')
4533 if color:
4534 renderer.set_property(
4535 'cell-background', color)
4536 else:
4537 renderer.set_property(
4538 'cell-background', None)
4539 # align pixbuf to the right
4540 renderer.set_property('xalign', 1)
4543 def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer,
4544 data = None):
4545 '''When a row is added, set properties for avatar renderer'''
4546 theme = gajim.config.get('roster_theme')
4547 type_ = model[titer][C_TYPE]
4548 if type_ in ('group', 'account'):
4549 renderer.set_property('visible', False)
4550 return
4552 # allocate space for the icon only if needed
4553 if model[titer][C_AVATAR_PIXBUF] or \
4554 gajim.config.get('avatar_position_in_roster') == 'left':
4555 renderer.set_property('visible', True)
4556 else:
4557 renderer.set_property('visible', False)
4558 if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4559 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
4560 # This can append at the moment we add the row
4561 return
4562 jid = model[titer][C_JID].decode('utf-8')
4563 account = model[titer][C_ACCOUNT].decode('utf-8')
4564 if jid in gajim.newly_added[account]:
4565 renderer.set_property('cell-background', gajim.config.get(
4566 'just_connected_bg_color'))
4567 elif jid in gajim.to_be_removed[account]:
4568 renderer.set_property('cell-background', gajim.config.get(
4569 'just_disconnected_bg_color'))
4570 else:
4571 color = gajim.config.get_per('themes', theme, 'contactbgcolor')
4572 if color:
4573 renderer.set_property('cell-background', color)
4574 else:
4575 renderer.set_property('cell-background', None)
4576 if gajim.config.get('avatar_position_in_roster') == 'left':
4577 renderer.set_property('width', gajim.config.get('roster_avatar_width'))
4578 renderer.set_property('xalign', 0.5)
4579 else:
4580 renderer.set_property('xalign', 1) # align pixbuf to the right
4582 def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer,
4583 data = None):
4584 '''When a row is added, set properties for padlock renderer'''
4585 theme = gajim.config.get('roster_theme')
4586 type_ = model[titer][C_TYPE]
4587 # allocate space for the icon only if needed
4588 if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]:
4589 renderer.set_property('visible', True)
4590 color = gajim.config.get_per('themes', theme, 'accountbgcolor')
4591 if color:
4592 renderer.set_property('cell-background', color)
4593 else:
4594 self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
4595 renderer.set_property('xalign', 1) # align pixbuf to the right
4596 else:
4597 renderer.set_property('visible', False)
4599 ################################################################################
4600 ### Everything about building menus
4601 ### FIXME: We really need to make it simpler! 1465 lines are a few to much....
4602 ################################################################################
4604 def make_menu(self, force=False):
4605 '''create the main window\'s menus'''
4606 if not force and not self.actions_menu_needs_rebuild:
4607 return
4608 new_chat_menuitem = self.xml.get_widget('new_chat_menuitem')
4609 single_message_menuitem = self.xml.get_widget(
4610 'send_single_message_menuitem')
4611 join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
4612 muc_icon = gtkgui_helpers.load_icon('muc_active')
4613 if muc_icon:
4614 join_gc_menuitem.set_image(muc_icon)
4615 add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem')
4616 service_disco_menuitem = self.xml.get_widget('service_disco_menuitem')
4617 advanced_menuitem = self.xml.get_widget('advanced_menuitem')
4618 profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem')
4620 # destroy old advanced menus
4621 for m in self.advanced_menus:
4622 m.destroy()
4624 # make it sensitive. it is insensitive only if no accounts are *available*
4625 advanced_menuitem.set_sensitive(True)
4627 if self.add_new_contact_handler_id:
4628 add_new_contact_menuitem.handler_disconnect(
4629 self.add_new_contact_handler_id)
4630 self.add_new_contact_handler_id = None
4632 if self.service_disco_handler_id:
4633 service_disco_menuitem.handler_disconnect(
4634 self.service_disco_handler_id)
4635 self.service_disco_handler_id = None
4637 if self.new_chat_menuitem_handler_id:
4638 new_chat_menuitem.handler_disconnect(
4639 self.new_chat_menuitem_handler_id)
4640 self.new_chat_menuitem_handler_id = None
4642 if self.single_message_menuitem_handler_id:
4643 single_message_menuitem.handler_disconnect(
4644 self.single_message_menuitem_handler_id)
4645 self.single_message_menuitem_handler_id = None
4647 if self.profile_avatar_menuitem_handler_id:
4648 profile_avatar_menuitem.handler_disconnect(
4649 self.profile_avatar_menuitem_handler_id)
4650 self.profile_avatar_menuitem_handler_id = None
4652 # remove the existing submenus
4653 add_new_contact_menuitem.remove_submenu()
4654 service_disco_menuitem.remove_submenu()
4655 join_gc_menuitem.remove_submenu()
4656 single_message_menuitem.remove_submenu()
4657 new_chat_menuitem.remove_submenu()
4658 advanced_menuitem.remove_submenu()
4659 profile_avatar_menuitem.remove_submenu()
4661 # remove the existing accelerator
4662 if self.have_new_chat_accel:
4663 ag = gtk.accel_groups_from_object(self.window)[0]
4664 new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n,
4665 gtk.gdk.CONTROL_MASK)
4666 self.have_new_chat_accel = False
4668 gc_sub_menu = gtk.Menu() # gc is always a submenu
4669 join_gc_menuitem.set_submenu(gc_sub_menu)
4671 connected_accounts = gajim.get_number_of_connected_accounts()
4673 connected_accounts_with_private_storage = 0
4675 # items that get shown whether an account is zeroconf or not
4676 accounts_list = sorted(gajim.contacts.get_accounts())
4677 if connected_accounts > 1: # 2 or more accounts? make submenus
4678 new_chat_sub_menu = gtk.Menu()
4680 for account in accounts_list:
4681 if gajim.connections[account].connected <= 1:
4682 # if offline or connecting
4683 continue
4685 # new chat
4686 new_chat_item = gtk.MenuItem(_('using account %s') % account,
4687 False)
4688 new_chat_sub_menu.append(new_chat_item)
4689 new_chat_item.connect('activate',
4690 self.on_new_chat_menuitem_activate, account)
4692 new_chat_menuitem.set_submenu(new_chat_sub_menu)
4693 new_chat_sub_menu.show_all()
4695 elif connected_accounts == 1: # user has only one account
4696 for account in gajim.connections:
4697 if gajim.account_is_connected(account): # THE connected account
4698 # new chat
4699 if not self.new_chat_menuitem_handler_id:
4700 self.new_chat_menuitem_handler_id = new_chat_menuitem.\
4701 connect('activate', self.on_new_chat_menuitem_activate,
4702 account)
4704 break
4706 # menu items that don't apply to zeroconf connections
4707 if connected_accounts == 1 or (connected_accounts == 2 and \
4708 gajim.zeroconf_is_connected()):
4709 # only one 'real' (non-zeroconf) account is connected, don't need submenus
4711 for account in accounts_list:
4712 if gajim.account_is_connected(account) and \
4713 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
4714 # gc
4715 if gajim.connections[account].private_storage_supported:
4716 connected_accounts_with_private_storage += 1
4717 self.add_bookmarks_list(gc_sub_menu, account)
4718 gc_sub_menu.show_all()
4719 # add
4720 if not self.add_new_contact_handler_id:
4721 self.add_new_contact_handler_id =\
4722 add_new_contact_menuitem.connect(
4723 'activate', self.on_add_new_contact, account)
4724 # disco
4725 if not self.service_disco_handler_id:
4726 self.service_disco_handler_id = service_disco_menuitem.\
4727 connect('activate',
4728 self.on_service_disco_menuitem_activate, account)
4730 # single message
4731 if not self.single_message_menuitem_handler_id:
4732 self.single_message_menuitem_handler_id = \
4733 single_message_menuitem.connect('activate', \
4734 self.on_send_single_message_menuitem_activate, account)
4736 # new chat accel
4737 if not self.have_new_chat_accel:
4738 ag = gtk.accel_groups_from_object(self.window)[0]
4739 new_chat_menuitem.add_accelerator('activate', ag,
4740 gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
4741 self.have_new_chat_accel = True
4743 break # No other account connected
4744 else:
4745 # 2 or more 'real' accounts are connected, make submenus
4746 single_message_sub_menu = gtk.Menu()
4747 add_sub_menu = gtk.Menu()
4748 disco_sub_menu = gtk.Menu()
4750 for account in accounts_list:
4751 if gajim.connections[account].connected <= 1 or \
4752 gajim.config.get_per('accounts', account, 'is_zeroconf'):
4753 # skip account if it's offline or connecting or is zeroconf
4754 continue
4756 # single message
4757 single_message_item = gtk.MenuItem(_('using account %s') % account,
4758 False)
4759 single_message_sub_menu.append(single_message_item)
4760 single_message_item.connect('activate',
4761 self.on_send_single_message_menuitem_activate, account)
4763 # join gc
4764 if gajim.connections[account].private_storage_supported:
4765 connected_accounts_with_private_storage += 1
4766 gc_item = gtk.MenuItem(_('using account %s') % account, False)
4767 gc_sub_menu.append(gc_item)
4768 gc_menuitem_menu = gtk.Menu()
4769 self.add_bookmarks_list(gc_menuitem_menu, account)
4770 gc_item.set_submenu(gc_menuitem_menu)
4772 # add
4773 add_item = gtk.MenuItem(_('to %s account') % account, False)
4774 add_sub_menu.append(add_item)
4775 add_item.connect('activate', self.on_add_new_contact, account)
4777 # disco
4778 disco_item = gtk.MenuItem(_('using %s account') % account, False)
4779 disco_sub_menu.append(disco_item)
4780 disco_item.connect('activate',
4781 self.on_service_disco_menuitem_activate, account)
4783 single_message_menuitem.set_submenu(single_message_sub_menu)
4784 single_message_sub_menu.show_all()
4785 gc_sub_menu.show_all()
4786 add_new_contact_menuitem.set_submenu(add_sub_menu)
4787 add_sub_menu.show_all()
4788 service_disco_menuitem.set_submenu(disco_sub_menu)
4789 disco_sub_menu.show_all()
4791 if connected_accounts == 0:
4792 # no connected accounts, make the menuitems insensitive
4793 for item in (new_chat_menuitem, join_gc_menuitem,\
4794 add_new_contact_menuitem, service_disco_menuitem,\
4795 single_message_menuitem):
4796 item.set_sensitive(False)
4797 else: # we have one or more connected accounts
4798 for item in (new_chat_menuitem, join_gc_menuitem,
4799 add_new_contact_menuitem, service_disco_menuitem,
4800 single_message_menuitem):
4801 item.set_sensitive(True)
4802 # disable some fields if only local account is there
4803 if connected_accounts == 1:
4804 for account in gajim.connections:
4805 if gajim.account_is_connected(account) and \
4806 gajim.connections[account].is_zeroconf:
4807 for item in (join_gc_menuitem, add_new_contact_menuitem,
4808 service_disco_menuitem, single_message_menuitem):
4809 item.set_sensitive(False)
4811 # Manage GC bookmarks
4812 newitem = gtk.SeparatorMenuItem() # separator
4813 gc_sub_menu.append(newitem)
4815 newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
4816 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
4817 gtk.ICON_SIZE_MENU)
4818 newitem.set_image(img)
4819 newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
4820 gc_sub_menu.append(newitem)
4821 gc_sub_menu.show_all()
4822 if connected_accounts_with_private_storage == 0:
4823 newitem.set_sensitive(False)
4825 connected_accounts_with_vcard = []
4826 for account in gajim.connections:
4827 if gajim.account_is_connected(account) and \
4828 gajim.connections[account].vcard_supported:
4829 connected_accounts_with_vcard.append(account)
4830 if len(connected_accounts_with_vcard) > 1:
4831 # 2 or more accounts? make submenus
4832 profile_avatar_sub_menu = gtk.Menu()
4833 for account in connected_accounts_with_vcard:
4834 # profile, avatar
4835 profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
4836 False)
4837 profile_avatar_sub_menu.append(profile_avatar_item)
4838 profile_avatar_item.connect('activate',
4839 self.on_profile_avatar_menuitem_activate, account)
4840 profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
4841 profile_avatar_sub_menu.show_all()
4842 elif len(connected_accounts_with_vcard) == 1: # user has only one account
4843 account = connected_accounts_with_vcard[0]
4844 # profile, avatar
4845 if not self.profile_avatar_menuitem_handler_id:
4846 self.profile_avatar_menuitem_handler_id = \
4847 profile_avatar_menuitem.connect('activate',
4848 self.on_profile_avatar_menuitem_activate, account)
4850 if len(connected_accounts_with_vcard) == 0:
4851 profile_avatar_menuitem.set_sensitive(False)
4852 else:
4853 profile_avatar_menuitem.set_sensitive(True)
4855 # Advanced Actions
4856 if len(gajim.connections) == 0: # user has no accounts
4857 advanced_menuitem.set_sensitive(False)
4858 elif len(gajim.connections) == 1: # we have one acccount
4859 account = gajim.connections.keys()[0]
4860 advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
4861 account)
4862 self.advanced_menus.append(advanced_menuitem_menu)
4864 self.add_history_manager_menuitem(advanced_menuitem_menu)
4866 advanced_menuitem.set_submenu(advanced_menuitem_menu)
4867 advanced_menuitem_menu.show_all()
4868 else: # user has *more* than one account : build advanced submenus
4869 advanced_sub_menu = gtk.Menu()
4870 accounts = [] # Put accounts in a list to sort them
4871 for account in gajim.connections:
4872 accounts.append(account)
4873 accounts.sort()
4874 for account in accounts:
4875 advanced_item = gtk.MenuItem(_('for account %s') % account, False)
4876 advanced_sub_menu.append(advanced_item)
4877 advanced_menuitem_menu = \
4878 self.get_and_connect_advanced_menuitem_menu(account)
4879 self.advanced_menus.append(advanced_menuitem_menu)
4880 advanced_item.set_submenu(advanced_menuitem_menu)
4882 self.add_history_manager_menuitem(advanced_sub_menu)
4884 advanced_menuitem.set_submenu(advanced_sub_menu)
4885 advanced_sub_menu.show_all()
4887 self.actions_menu_needs_rebuild = False
4889 def build_account_menu(self, account):
4890 # we have to create our own set of icons for the menu
4891 # using self.jabber_status_images is poopoo
4892 iconset = gajim.config.get('iconset')
4893 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
4894 state_images = gtkgui_helpers.load_iconset(path)
4896 if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
4897 xml = gtkgui_helpers.get_glade('account_context_menu.glade')
4898 account_context_menu = xml.get_widget('account_context_menu')
4900 status_menuitem = xml.get_widget('status_menuitem')
4901 start_chat_menuitem = xml.get_widget('start_chat_menuitem')
4902 join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem')
4903 muc_icon = gtkgui_helpers.load_icon('muc_active')
4904 if muc_icon:
4905 join_group_chat_menuitem.set_image(muc_icon)
4906 open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
4907 add_contact_menuitem = xml.get_widget('add_contact_menuitem')
4908 service_discovery_menuitem = xml.get_widget(
4909 'service_discovery_menuitem')
4910 execute_command_menuitem = xml.get_widget('execute_command_menuitem')
4911 edit_account_menuitem = xml.get_widget('edit_account_menuitem')
4912 sub_menu = gtk.Menu()
4913 status_menuitem.set_submenu(sub_menu)
4915 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
4916 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
4917 item = gtk.ImageMenuItem(uf_show)
4918 icon = state_images[show]
4919 item.set_image(icon)
4920 sub_menu.append(item)
4921 con = gajim.connections[account]
4922 if show == 'invisible' and con.connected > 1 and \
4923 not con.privacy_rules_supported:
4924 item.set_sensitive(False)
4925 else:
4926 item.connect('activate', self.change_status, account, show)
4928 item = gtk.SeparatorMenuItem()
4929 sub_menu.append(item)
4931 item = gtk.ImageMenuItem(_('_Change Status Message'))
4932 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
4933 img = gtk.Image()
4934 img.set_from_file(path)
4935 item.set_image(img)
4936 sub_menu.append(item)
4937 item.connect('activate', self.on_change_status_message_activate,
4938 account)
4939 if gajim.connections[account].connected < 2:
4940 item.set_sensitive(False)
4942 item = gtk.SeparatorMenuItem()
4943 sub_menu.append(item)
4945 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
4946 item = gtk.ImageMenuItem(uf_show)
4947 icon = state_images['offline']
4948 item.set_image(icon)
4949 sub_menu.append(item)
4950 item.connect('activate', self.change_status, account, 'offline')
4952 pep_menuitem = xml.get_widget('pep_menuitem')
4953 if gajim.connections[account].pep_supported:
4954 have_tune = gajim.config.get_per('accounts', account,
4955 'publish_tune')
4956 pep_submenu = gtk.Menu()
4957 pep_menuitem.set_submenu(pep_submenu)
4958 item = gtk.CheckMenuItem(_('Publish Tune'))
4959 pep_submenu.append(item)
4960 if not dbus_support.supported:
4961 item.set_sensitive(False)
4962 else:
4963 item.set_active(have_tune)
4964 item.connect('toggled', self.on_publish_tune_toggled, account)
4966 pep_config = gtk.ImageMenuItem(_('Configure Services...'))
4967 item = gtk.SeparatorMenuItem()
4968 pep_submenu.append(item)
4969 pep_config.set_sensitive(True)
4970 pep_submenu.append(pep_config)
4971 pep_config.connect('activate',
4972 self.on_pep_services_menuitem_activate, account)
4973 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
4974 gtk.ICON_SIZE_MENU)
4975 pep_config.set_image(img)
4977 else:
4978 pep_menuitem.set_sensitive(False)
4980 if not gajim.connections[account].gmail_url:
4981 open_gmail_inbox_menuitem.set_no_show_all(True)
4982 open_gmail_inbox_menuitem.hide()
4983 else:
4984 open_gmail_inbox_menuitem.connect('activate',
4985 self.on_open_gmail_inbox, account)
4987 edit_account_menuitem.connect('activate', self.on_edit_account,
4988 account)
4989 add_contact_menuitem.connect('activate', self.on_add_new_contact,
4990 account)
4991 service_discovery_menuitem.connect('activate',
4992 self.on_service_disco_menuitem_activate, account)
4993 hostname = gajim.config.get_per('accounts', account, 'hostname')
4994 contact = gajim.contacts.create_contact(jid=hostname) # Fake contact
4995 execute_command_menuitem.connect('activate',
4996 self.on_execute_command, contact, account)
4998 start_chat_menuitem.connect('activate',
4999 self.on_new_chat_menuitem_activate, account)
5001 gc_sub_menu = gtk.Menu() # gc is always a submenu
5002 join_group_chat_menuitem.set_submenu(gc_sub_menu)
5003 self.add_bookmarks_list(gc_sub_menu, account)
5005 # make some items insensitive if account is offline
5006 if gajim.connections[account].connected < 2:
5007 for widget in (add_contact_menuitem, service_discovery_menuitem,
5008 join_group_chat_menuitem, execute_command_menuitem, pep_menuitem,
5009 start_chat_menuitem):
5010 widget.set_sensitive(False)
5011 else:
5012 xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade')
5013 account_context_menu = xml.get_widget('zeroconf_context_menu')
5015 status_menuitem = xml.get_widget('status_menuitem')
5016 zeroconf_properties_menuitem = xml.get_widget(
5017 'zeroconf_properties_menuitem')
5018 sub_menu = gtk.Menu()
5019 status_menuitem.set_submenu(sub_menu)
5021 for show in ('online', 'away', 'dnd', 'invisible'):
5022 uf_show = helpers.get_uf_show(show, use_mnemonic=True)
5023 item = gtk.ImageMenuItem(uf_show)
5024 icon = state_images[show]
5025 item.set_image(icon)
5026 sub_menu.append(item)
5027 item.connect('activate', self.change_status, account, show)
5029 item = gtk.SeparatorMenuItem()
5030 sub_menu.append(item)
5032 item = gtk.ImageMenuItem(_('_Change Status Message'))
5033 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
5034 img = gtk.Image()
5035 img.set_from_file(path)
5036 item.set_image(img)
5037 sub_menu.append(item)
5038 item.connect('activate', self.on_change_status_message_activate,
5039 account)
5040 if gajim.connections[account].connected < 2:
5041 item.set_sensitive(False)
5043 uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
5044 item = gtk.ImageMenuItem(uf_show)
5045 icon = state_images['offline']
5046 item.set_image(icon)
5047 sub_menu.append(item)
5048 item.connect('activate', self.change_status, account, 'offline')
5050 zeroconf_properties_menuitem.connect('activate',
5051 self.on_zeroconf_properties, account)
5052 #gc_sub_menu = gtk.Menu() # gc is always a submenu
5053 #join_group_chat_menuitem.set_submenu(gc_sub_menu)
5054 #self.add_bookmarks_list(gc_sub_menu, account)
5055 #new_message_menuitem.connect('activate',
5056 # self.on_new_message_menuitem_activate, account)
5058 # make some items insensitive if account is offline
5059 #if gajim.connections[account].connected < 2:
5060 # for widget in [join_group_chat_menuitem, new_message_menuitem]:
5061 # widget.set_sensitive(False)
5062 # new_message_menuitem.set_sensitive(False)
5064 return account_context_menu
5066 def make_account_menu(self, event, titer):
5067 '''Make account's popup menu'''
5068 model = self.modelfilter
5069 account = model[titer][C_ACCOUNT].decode('utf-8')
5071 if account != 'all': # not in merged mode
5072 menu = self.build_account_menu(account)
5073 else:
5074 menu = gtk.Menu()
5075 iconset = gajim.config.get('iconset')
5076 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5077 accounts = [] # Put accounts in a list to sort them
5078 for account in gajim.connections:
5079 accounts.append(account)
5080 accounts.sort()
5081 for account in accounts:
5082 state_images = gtkgui_helpers.load_iconset(path)
5083 item = gtk.ImageMenuItem(account)
5084 show = gajim.SHOW_LIST[gajim.connections[account].connected]
5085 icon = state_images[show]
5086 item.set_image(icon)
5087 account_menu = self.build_account_menu(account)
5088 item.set_submenu(account_menu)
5089 menu.append(item)
5091 event_button = gtkgui_helpers.get_possible_button_event(event)
5093 menu.attach_to_widget(self.tree, None)
5094 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5095 menu.show_all()
5096 menu.popup(None, None, None, event_button, event.time)
5098 def make_group_menu(self, event, titer):
5099 '''Make group's popup menu'''
5100 model = self.modelfilter
5101 path = model.get_path(titer)
5102 group = model[titer][C_JID].decode('utf-8')
5103 account = model[titer][C_ACCOUNT].decode('utf-8')
5105 list_ = [] # list of (jid, account) tuples
5106 list_online = [] # list of (jid, account) tuples
5108 group = model[titer][C_JID]
5109 for jid in gajim.contacts.get_jid_list(account):
5110 contact = gajim.contacts.get_contact_with_highest_priority(account,
5111 jid)
5112 if group in contact.get_shown_groups():
5113 if contact.show not in ('offline', 'error'):
5114 list_online.append((contact, account))
5115 list_.append((contact, account))
5116 menu = gtk.Menu()
5118 # Make special context menu if group is Groupchats
5119 if group == _('Groupchats'):
5120 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All'))
5121 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU)
5122 maximize_menuitem.set_image(icon)
5123 maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\
5124 list_)
5125 menu.append(maximize_menuitem)
5126 else:
5127 # Send Group Message
5128 send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage'))
5129 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5130 send_group_message_item.set_image(icon)
5132 send_group_message_submenu = gtk.Menu()
5133 send_group_message_item.set_submenu(send_group_message_submenu)
5134 menu.append(send_group_message_item)
5136 group_message_to_all_item = gtk.MenuItem(_('To all users'))
5137 send_group_message_submenu.append(group_message_to_all_item)
5139 group_message_to_all_online_item = gtk.MenuItem(
5140 _('To all online users'))
5141 send_group_message_submenu.append(group_message_to_all_online_item)
5143 group_message_to_all_online_item.connect('activate',
5144 self.on_send_single_message_menuitem_activate, account, list_online)
5145 group_message_to_all_item.connect('activate',
5146 self.on_send_single_message_menuitem_activate, account, list_)
5148 # Invite to
5149 invite_menuitem = gtk.ImageMenuItem(_('In_vite to'))
5150 muc_icon = gtkgui_helpers.load_icon('muc_active')
5151 if muc_icon:
5152 invite_menuitem.set_image(muc_icon)
5154 self.build_invite_submenu(invite_menuitem, list_online)
5155 menu.append(invite_menuitem)
5157 # Send Custom Status
5158 send_custom_status_menuitem = gtk.ImageMenuItem(
5159 _('Send Cus_tom Status'))
5160 # add a special img for this menuitem
5161 if helpers.group_is_blocked(account, group):
5162 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5163 'offline'))
5164 send_custom_status_menuitem.set_sensitive(False)
5165 else:
5166 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5167 gtk.ICON_SIZE_MENU)
5168 send_custom_status_menuitem.set_image(icon)
5169 status_menuitems = gtk.Menu()
5170 send_custom_status_menuitem.set_submenu(status_menuitems)
5171 iconset = gajim.config.get('iconset')
5172 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5173 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5174 # icon MUST be different instance for every item
5175 state_images = gtkgui_helpers.load_iconset(path)
5176 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5177 status_menuitem.connect('activate', self.on_send_custom_status,
5178 list_, s, group)
5179 icon = state_images[s]
5180 status_menuitem.set_image(icon)
5181 status_menuitems.append(status_menuitem)
5182 menu.append(send_custom_status_menuitem)
5184 # there is no singlemessage and custom status for zeroconf
5185 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
5186 send_custom_status_menuitem.set_sensitive(False)
5187 send_group_message_item.set_sensitive(False)
5189 if not group in helpers.special_groups:
5190 item = gtk.SeparatorMenuItem() # separator
5191 menu.append(item)
5193 # Rename
5194 rename_item = gtk.ImageMenuItem(_('Re_name'))
5195 # add a special img for rename menuitem
5196 path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
5197 'kbd_input.png')
5198 img = gtk.Image()
5199 img.set_from_file(path_to_kbd_input_img)
5200 rename_item.set_image(img)
5201 menu.append(rename_item)
5202 rename_item.connect('activate', self.on_rename, 'group', group,
5203 account)
5205 # Block group
5206 is_blocked = False
5207 if self.regroup:
5208 for g_account in gajim.connections:
5209 if helpers.group_is_blocked(g_account, group):
5210 is_blocked = True
5211 else:
5212 if helpers.group_is_blocked(account, group):
5213 is_blocked = True
5215 if is_blocked and gajim.connections[account].privacy_rules_supported:
5216 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5217 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5218 unblock_menuitem.set_image(icon)
5219 unblock_menuitem.connect('activate', self.on_unblock, list_, group)
5220 menu.append(unblock_menuitem)
5221 else:
5222 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5223 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5224 block_menuitem.set_image(icon)
5225 block_menuitem.connect('activate', self.on_block, list_, group)
5226 menu.append(block_menuitem)
5227 if not gajim.connections[account].privacy_rules_supported:
5228 block_menuitem.set_sensitive(False)
5230 # Remove group
5231 remove_item = gtk.ImageMenuItem(_('_Remove'))
5232 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5233 remove_item.set_image(icon)
5234 menu.append(remove_item)
5235 remove_item.connect('activate', self.on_remove_group_item_activated,
5236 group, account)
5238 # unsensitive if account is not connected
5239 if gajim.connections[account].connected < 2:
5240 rename_item.set_sensitive(False)
5242 # General group cannot be changed
5243 if group == _('General'):
5244 rename_item.set_sensitive(False)
5245 block_menuitem.set_sensitive(False)
5246 remove_item.set_sensitive(False)
5248 event_button = gtkgui_helpers.get_possible_button_event(event)
5250 menu.attach_to_widget(self.tree, None)
5251 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5252 menu.show_all()
5253 menu.popup(None, None, None, event_button, event.time)
5255 def make_contact_menu(self, event, titer):
5256 '''Make contact\'s popup menu'''
5257 model = self.modelfilter
5258 jid = model[titer][C_JID].decode('utf-8')
5259 tree_path = model.get_path(titer)
5260 account = model[titer][C_ACCOUNT].decode('utf-8')
5261 our_jid = jid == gajim.get_jid_from_account(account)
5262 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5263 if not contact:
5264 return
5266 # Zeroconf Account
5267 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
5268 xml = gtkgui_helpers.get_glade('zeroconf_contact_context_menu.glade')
5269 zeroconf_contact_context_menu = xml.get_widget(
5270 'zeroconf_contact_context_menu')
5272 start_chat_menuitem = xml.get_widget('start_chat_menuitem')
5273 rename_menuitem = xml.get_widget('rename_menuitem')
5274 edit_groups_menuitem = xml.get_widget('edit_groups_menuitem')
5275 send_file_menuitem = xml.get_widget('send_file_menuitem')
5276 assign_openpgp_key_menuitem = xml.get_widget(
5277 'assign_openpgp_key_menuitem')
5278 add_special_notification_menuitem = xml.get_widget(
5279 'add_special_notification_menuitem')
5281 # add a special img for send file menuitem
5282 path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
5283 'upload.png')
5284 img = gtk.Image()
5285 img.set_from_file(path_to_upload_img)
5286 send_file_menuitem.set_image(img)
5288 if not our_jid:
5289 # add a special img for rename menuitem
5290 path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
5291 'kbd_input.png')
5292 img = gtk.Image()
5293 img.set_from_file(path_to_kbd_input_img)
5294 rename_menuitem.set_image(img)
5296 above_information_separator = xml.get_widget(
5297 'above_information_separator')
5299 information_menuitem = xml.get_widget('information_menuitem')
5300 history_menuitem = xml.get_widget('history_menuitem')
5302 contacts = gajim.contacts.get_contacts(account, jid)
5303 if len(contacts) > 1: # several resources
5304 sub_menu = gtk.Menu()
5305 start_chat_menuitem.set_submenu(sub_menu)
5307 iconset = gajim.config.get('iconset')
5308 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5309 for c in contacts:
5310 # icon MUST be different instance for every item
5311 state_images = gtkgui_helpers.load_iconset(path)
5312 item = gtk.ImageMenuItem('%s (%s)' % (c.resource,
5313 str(c.priority)))
5314 icon_name = helpers.get_icon_name_to_show(c, account)
5315 icon = state_images[icon_name]
5316 item.set_image(icon)
5317 sub_menu.append(item)
5318 item.connect('activate', gajim.interface.on_open_chat_window, \
5319 c, account, c.resource)
5321 else: # one resource
5322 start_chat_menuitem.connect('activate',
5323 self.on_roster_treeview_row_activated, tree_path)
5325 if gajim.capscache.is_supported(contact, NS_FILE):
5326 send_file_menuitem.set_sensitive(True)
5327 send_file_menuitem.connect('activate',
5328 self.on_send_file_menuitem_activate, contact, account)
5329 else:
5330 send_file_menuitem.set_sensitive(False)
5332 rename_menuitem.connect('activate', self.on_rename, 'contact', jid,
5333 account)
5334 if contact.show in ('offline', 'error'):
5335 information_menuitem.set_sensitive(False)
5336 send_file_menuitem.set_sensitive(False)
5337 else:
5338 information_menuitem.connect('activate', self.on_info_zeroconf,
5339 contact, account)
5340 history_menuitem.connect('activate', self.on_history, contact,
5341 account)
5343 if _('Not in Roster') not in contact.get_shown_groups():
5344 # contact is in normal group
5345 edit_groups_menuitem.set_no_show_all(False)
5346 assign_openpgp_key_menuitem.set_no_show_all(False)
5347 edit_groups_menuitem.connect('activate', self.on_edit_groups, [(
5348 contact,account)])
5350 if gajim.connections[account].gpg:
5351 assign_openpgp_key_menuitem.connect('activate',
5352 self.on_assign_pgp_key, contact, account)
5353 else:
5354 assign_openpgp_key_menuitem.set_sensitive(False)
5356 else: # contact is in group 'Not in Roster'
5357 edit_groups_menuitem.set_sensitive(False)
5358 edit_groups_menuitem.set_no_show_all(True)
5359 assign_openpgp_key_menuitem.set_sensitive(False)
5361 # Remove many items when it's self contact row
5362 if our_jid:
5363 for menuitem in (rename_menuitem, edit_groups_menuitem,
5364 above_information_separator):
5365 menuitem.set_no_show_all(True)
5366 menuitem.hide()
5368 # Unsensitive many items when account is offline
5369 if gajim.connections[account].connected < 2:
5370 for widget in (start_chat_menuitem, rename_menuitem,
5371 edit_groups_menuitem, send_file_menuitem):
5372 widget.set_sensitive(False)
5374 event_button = gtkgui_helpers.get_possible_button_event(event)
5376 zeroconf_contact_context_menu.attach_to_widget(self.tree, None)
5377 zeroconf_contact_context_menu.connect('selection-done',
5378 gtkgui_helpers.destroy_widget)
5379 zeroconf_contact_context_menu.show_all()
5380 zeroconf_contact_context_menu.popup(None, None, None, event_button,
5381 event.time)
5382 return
5384 # normal account
5385 xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade')
5386 roster_contact_context_menu = xml.get_widget(
5387 'roster_contact_context_menu')
5389 start_chat_menuitem = xml.get_widget('start_chat_menuitem')
5390 send_custom_status_menuitem = xml.get_widget(
5391 'send_custom_status_menuitem')
5392 send_single_message_menuitem = xml.get_widget(
5393 'send_single_message_menuitem')
5394 invite_menuitem = xml.get_widget('invite_menuitem')
5395 block_menuitem = xml.get_widget('block_menuitem')
5396 unblock_menuitem = xml.get_widget('unblock_menuitem')
5397 ignore_menuitem = xml.get_widget('ignore_menuitem')
5398 unignore_menuitem = xml.get_widget('unignore_menuitem')
5399 rename_menuitem = xml.get_widget('rename_menuitem')
5400 edit_groups_menuitem = xml.get_widget('edit_groups_menuitem')
5401 send_file_menuitem = xml.get_widget('send_file_menuitem')
5402 assign_openpgp_key_menuitem = xml.get_widget(
5403 'assign_openpgp_key_menuitem')
5404 set_custom_avatar_menuitem = xml.get_widget('set_custom_avatar_menuitem')
5405 add_special_notification_menuitem = xml.get_widget(
5406 'add_special_notification_menuitem')
5407 execute_command_menuitem = xml.get_widget(
5408 'execute_command_menuitem')
5410 # add a special img for send file menuitem
5411 path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png')
5412 img = gtk.Image()
5413 img.set_from_file(path_to_upload_img)
5414 send_file_menuitem.set_image(img)
5416 # send custom status icon
5417 blocked = False
5418 if helpers.jid_is_blocked(account, jid):
5419 blocked = True
5420 else:
5421 for group in contact.get_shown_groups():
5422 if helpers.group_is_blocked(account, group):
5423 blocked = True
5424 break
5425 if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
5426 # Transport contact, send custom status unavailable
5427 send_custom_status_menuitem.set_sensitive(False)
5428 elif blocked:
5429 send_custom_status_menuitem.set_image( \
5430 gtkgui_helpers.load_icon('offline'))
5431 send_custom_status_menuitem.set_sensitive(False)
5432 elif account in gajim.interface.status_sent_to_users and \
5433 jid in gajim.interface.status_sent_to_users[account]:
5434 send_custom_status_menuitem.set_image(
5435 gtkgui_helpers.load_icon( \
5436 gajim.interface.status_sent_to_users[account][jid]))
5437 else:
5438 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU)
5439 send_custom_status_menuitem.set_image(icon)
5441 if not our_jid:
5442 # add a special img for rename menuitem
5443 path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
5444 'kbd_input.png')
5445 img = gtk.Image()
5446 img.set_from_file(path_to_kbd_input_img)
5447 rename_menuitem.set_image(img)
5449 muc_icon = gtkgui_helpers.load_icon('muc_active')
5450 if muc_icon:
5451 invite_menuitem.set_image(muc_icon)
5453 self.build_invite_submenu(invite_menuitem, [(contact, account)])
5455 # Subscription submenu
5456 subscription_menuitem = xml.get_widget('subscription_menuitem')
5457 send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem =\
5458 subscription_menuitem.get_submenu().get_children()
5459 add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem')
5460 remove_from_roster_menuitem = xml.get_widget(
5461 'remove_from_roster_menuitem')
5463 information_menuitem = xml.get_widget('information_menuitem')
5464 history_menuitem = xml.get_widget('history_menuitem')
5466 contacts = gajim.contacts.get_contacts(account, jid)
5468 # One or several resource, we do the same for send_custom_status
5469 status_menuitems = gtk.Menu()
5470 send_custom_status_menuitem.set_submenu(status_menuitems)
5471 iconset = gajim.config.get('iconset')
5472 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5473 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5474 # icon MUST be different instance for every item
5475 state_images = gtkgui_helpers.load_iconset(path)
5476 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5477 status_menuitem.connect('activate', self.on_send_custom_status,
5478 [(contact, account)], s)
5479 icon = state_images[s]
5480 status_menuitem.set_image(icon)
5481 status_menuitems.append(status_menuitem)
5482 if len(contacts) > 1: # several resources
5483 start_chat_menuitem.set_submenu(self.build_resources_submenu(contacts,
5484 account, gajim.interface.on_open_chat_window))
5485 send_file_menuitem.set_submenu(self.build_resources_submenu(contacts,
5486 account, self.on_send_file_menuitem_activate,
5487 cap=NS_FILE))
5488 execute_command_menuitem.set_submenu(self.build_resources_submenu(
5489 contacts, account, self.on_execute_command,
5490 cap=NS_COMMANDS))
5492 else: # one resource
5493 start_chat_menuitem.connect('activate',
5494 gajim.interface.on_open_chat_window, contact, account)
5495 if gajim.capscache.is_supported(contact, NS_COMMANDS):
5496 execute_command_menuitem.set_sensitive(True)
5497 execute_command_menuitem.connect('activate', self.on_execute_command,
5498 contact, account, contact.resource)
5499 else:
5500 execute_command_menuitem.set_sensitive(False)
5502 # This does nothing:
5503 # our_jid_other_resource = None
5504 # if our_jid:
5505 # # It's another resource of us, be sure to send invite to her
5506 # our_jid_other_resource = contact.resource
5507 # Else this var is useless but harmless in next connect calls
5509 if gajim.capscache.is_supported(contact, NS_FILE):
5510 send_file_menuitem.set_sensitive(True)
5511 send_file_menuitem.connect('activate',
5512 self.on_send_file_menuitem_activate, contact, account)
5513 else:
5514 send_file_menuitem.set_sensitive(False)
5516 send_single_message_menuitem.connect('activate',
5517 self.on_send_single_message_menuitem_activate, account, contact)
5519 rename_menuitem.connect('activate', self.on_rename, 'contact', jid,
5520 account)
5521 remove_from_roster_menuitem.connect('activate', self.on_req_usub,
5522 [(contact, account)])
5523 information_menuitem.connect('activate', self.on_info, contact,
5524 account)
5525 history_menuitem.connect('activate', self.on_history, contact,
5526 account)
5528 if _('Not in Roster') not in contact.get_shown_groups():
5529 # contact is in normal group
5530 add_to_roster_menuitem.hide()
5531 add_to_roster_menuitem.set_no_show_all(True)
5532 edit_groups_menuitem.connect('activate', self.on_edit_groups, [(
5533 contact,account)])
5535 if gajim.connections[account].gpg:
5536 assign_openpgp_key_menuitem.connect('activate',
5537 self.on_assign_pgp_key, contact, account)
5538 else:
5539 assign_openpgp_key_menuitem.set_sensitive(False)
5541 if contact.sub in ('from', 'both'):
5542 send_auth_menuitem.set_sensitive(False)
5543 else:
5544 send_auth_menuitem.connect('activate', self.authorize, jid, account)
5545 if contact.sub in ('to', 'both'):
5546 ask_auth_menuitem.set_sensitive(False)
5547 add_special_notification_menuitem.connect('activate',
5548 self.on_add_special_notification_menuitem_activate, jid)
5549 else:
5550 ask_auth_menuitem.connect('activate', self.req_sub, jid,
5551 _('I would like to add you to my roster'), account,
5552 contact.groups, contact.name)
5553 if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid(
5554 jid, use_config_setting=False):
5555 revoke_auth_menuitem.set_sensitive(False)
5556 else:
5557 revoke_auth_menuitem.connect('activate', self.revoke_auth, jid,
5558 account)
5560 else: # contact is in group 'Not in Roster'
5561 add_to_roster_menuitem.set_no_show_all(False)
5562 edit_groups_menuitem.set_sensitive(False)
5563 assign_openpgp_key_menuitem.set_sensitive(False)
5564 subscription_menuitem.set_sensitive(False)
5566 add_to_roster_menuitem.connect('activate',
5567 self.on_add_to_roster, contact, account)
5569 set_custom_avatar_menuitem.connect('activate',
5570 self.on_set_custom_avatar_activate, contact, account)
5571 # Hide items when it's self contact row
5572 if our_jid:
5573 menuitem = xml.get_widget('manage_contact')
5574 menuitem.set_sensitive(False)
5576 # Unsensitive many items when account is offline
5577 if gajim.connections[account].connected < 2:
5578 for widget in (start_chat_menuitem, send_single_message_menuitem,
5579 rename_menuitem, edit_groups_menuitem, send_file_menuitem,
5580 subscription_menuitem, add_to_roster_menuitem,
5581 remove_from_roster_menuitem, execute_command_menuitem,
5582 send_custom_status_menuitem):
5583 widget.set_sensitive(False)
5585 if gajim.connections[account] and gajim.connections[account].\
5586 privacy_rules_supported:
5587 if helpers.jid_is_blocked(account, jid):
5588 block_menuitem.set_no_show_all(True)
5589 block_menuitem.hide()
5590 if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
5591 unblock_menuitem.set_no_show_all(True)
5592 unblock_menuitem.hide()
5593 unignore_menuitem.set_no_show_all(False)
5594 unignore_menuitem.connect('activate', self.on_unblock, [(contact,
5595 account)])
5596 else:
5597 unblock_menuitem.connect('activate', self.on_unblock, [(contact,
5598 account)])
5599 else:
5600 unblock_menuitem.set_no_show_all(True)
5601 unblock_menuitem.hide()
5602 if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
5603 block_menuitem.set_no_show_all(True)
5604 block_menuitem.hide()
5605 ignore_menuitem.set_no_show_all(False)
5606 ignore_menuitem.connect('activate', self.on_block, [(contact,
5607 account)])
5608 else:
5609 block_menuitem.connect('activate', self.on_block, [(contact,
5610 account)])
5611 else:
5612 unblock_menuitem.set_no_show_all(True)
5613 block_menuitem.set_sensitive(False)
5614 unblock_menuitem.hide()
5616 event_button = gtkgui_helpers.get_possible_button_event(event)
5618 roster_contact_context_menu.attach_to_widget(self.tree, None)
5619 roster_contact_context_menu.connect('selection-done',
5620 gtkgui_helpers.destroy_widget)
5621 roster_contact_context_menu.show_all()
5622 roster_contact_context_menu.popup(None, None, None, event_button,
5623 event.time)
5625 def make_multiple_contact_menu(self, event, iters):
5626 '''Make group's popup menu'''
5627 model = self.modelfilter
5628 list_ = [] # list of (jid, account) tuples
5629 one_account_offline = False
5630 is_blocked = True
5631 privacy_rules_supported = True
5632 for titer in iters:
5633 jid = model[titer][C_JID].decode('utf-8')
5634 account = model[titer][C_ACCOUNT].decode('utf-8')
5635 if gajim.connections[account].connected < 2:
5636 one_account_offline = True
5637 if not gajim.connections[account].privacy_rules_supported:
5638 privacy_rules_supported = False
5639 contact = gajim.contacts.get_contact_with_highest_priority(account,
5640 jid)
5641 if helpers.jid_is_blocked(account, jid):
5642 is_blocked = False
5643 list_.append((contact, account))
5645 menu = gtk.Menu()
5646 account = None
5647 for (contact, current_account) in list_:
5648 # check that we use the same account for every sender
5649 if account is not None and account != current_account:
5650 account = None
5651 break
5652 account = current_account
5653 if account is not None:
5654 send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage'))
5655 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5656 send_group_message_item.set_image(icon)
5657 menu.append(send_group_message_item)
5658 send_group_message_item.connect('activate',
5659 self.on_send_single_message_menuitem_activate, account, list_)
5661 # Invite to Groupchat
5662 invite_item = gtk.ImageMenuItem(_('In_vite to'))
5663 muc_icon = gtkgui_helpers.load_icon('muc_active')
5664 if muc_icon:
5665 invite_item.set_image(muc_icon)
5667 self.build_invite_submenu(invite_item, list_)
5668 menu.append(invite_item)
5670 item = gtk.SeparatorMenuItem() # separator
5671 menu.append(item)
5673 # Manage Transport submenu
5674 item = gtk.ImageMenuItem(_('_Manage Contacts'))
5675 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU)
5676 item.set_image(icon)
5677 manage_contacts_submenu = gtk.Menu()
5678 item.set_submenu(manage_contacts_submenu)
5679 menu.append(item)
5681 # Edit Groups
5682 edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups'))
5683 icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
5684 edit_groups_item.set_image(icon)
5685 manage_contacts_submenu.append(edit_groups_item)
5686 edit_groups_item.connect('activate', self.on_edit_groups, list_)
5688 item = gtk.SeparatorMenuItem() # separator
5689 manage_contacts_submenu.append(item)
5691 # Block
5692 if is_blocked and privacy_rules_supported:
5693 unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
5694 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5695 unblock_menuitem.set_image(icon)
5696 unblock_menuitem.connect('activate', self.on_unblock, list_)
5697 manage_contacts_submenu.append(unblock_menuitem)
5698 else:
5699 block_menuitem = gtk.ImageMenuItem(_('_Block'))
5700 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5701 block_menuitem.set_image(icon)
5702 block_menuitem.connect('activate', self.on_block, list_)
5703 manage_contacts_submenu.append(block_menuitem)
5705 if not privacy_rules_supported:
5706 block_menuitem.set_sensitive(False)
5708 # Remove
5709 remove_item = gtk.ImageMenuItem(_('_Remove'))
5710 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5711 remove_item.set_image(icon)
5712 manage_contacts_submenu.append(remove_item)
5713 remove_item.connect('activate', self.on_req_usub, list_)
5714 # unsensitive remove if one account is not connected
5715 if one_account_offline:
5716 remove_item.set_sensitive(False)
5718 event_button = gtkgui_helpers.get_possible_button_event(event)
5720 menu.attach_to_widget(self.tree, None)
5721 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5722 menu.show_all()
5723 menu.popup(None, None, None, event_button, event.time)
5725 def make_transport_menu(self, event, titer):
5726 '''Make transport\'s popup menu'''
5727 model = self.modelfilter
5728 jid = model[titer][C_JID].decode('utf-8')
5729 path = model.get_path(titer)
5730 account = model[titer][C_ACCOUNT].decode('utf-8')
5731 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5732 menu = gtk.Menu()
5734 # Send single message
5735 item = gtk.ImageMenuItem(_('Send Single Message'))
5736 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5737 item.set_image(icon)
5738 item.connect('activate',
5739 self.on_send_single_message_menuitem_activate, account, contact)
5740 menu.append(item)
5742 blocked = False
5743 if helpers.jid_is_blocked(account, jid):
5744 blocked = True
5746 # Send Custom Status
5747 send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status'))
5748 # add a special img for this menuitem
5749 if blocked:
5750 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5751 'offline'))
5752 send_custom_status_menuitem.set_sensitive(False)
5753 else:
5754 if account in gajim.interface.status_sent_to_users and \
5755 jid in gajim.interface.status_sent_to_users[account]:
5756 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
5757 gajim.interface.status_sent_to_users[account][jid]))
5758 else:
5759 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
5760 gtk.ICON_SIZE_MENU)
5761 send_custom_status_menuitem.set_image(icon)
5762 status_menuitems = gtk.Menu()
5763 send_custom_status_menuitem.set_submenu(status_menuitems)
5764 iconset = gajim.config.get('iconset')
5765 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5766 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5767 # icon MUST be different instance for every item
5768 state_images = gtkgui_helpers.load_iconset(path)
5769 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
5770 status_menuitem.connect('activate', self.on_send_custom_status,
5771 [(contact, account)], s)
5772 icon = state_images[s]
5773 status_menuitem.set_image(icon)
5774 status_menuitems.append(status_menuitem)
5775 menu.append(send_custom_status_menuitem)
5777 item = gtk.SeparatorMenuItem() # separator
5778 menu.append(item)
5780 # Execute Command
5781 item = gtk.ImageMenuItem(_('Execute Command...'))
5782 icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
5783 item.set_image(icon)
5784 menu.append(item)
5785 item.connect('activate', self.on_execute_command, contact, account,
5786 contact.resource)
5787 if gajim.account_is_disconnected(account):
5788 item.set_sensitive(False)
5790 # Manage Transport submenu
5791 item = gtk.ImageMenuItem(_('_Manage Transport'))
5792 icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU)
5793 item.set_image(icon)
5794 manage_transport_submenu = gtk.Menu()
5795 item.set_submenu(manage_transport_submenu)
5796 menu.append(item)
5798 # Modify Transport
5799 item = gtk.ImageMenuItem(_('_Modify Transport'))
5800 icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
5801 item.set_image(icon)
5802 manage_transport_submenu.append(item)
5803 item.connect('activate', self.on_edit_agent, contact, account)
5804 if gajim.account_is_disconnected(account):
5805 item.set_sensitive(False)
5807 # Rename
5808 item = gtk.ImageMenuItem(_('_Rename'))
5809 # add a special img for rename menuitem
5810 path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
5811 'kbd_input.png')
5812 img = gtk.Image()
5813 img.set_from_file(path_to_kbd_input_img)
5814 item.set_image(img)
5815 manage_transport_submenu.append(item)
5816 item.connect('activate', self.on_rename, 'agent', jid, account)
5817 if gajim.account_is_disconnected(account):
5818 item.set_sensitive(False)
5820 item = gtk.SeparatorMenuItem() # separator
5821 manage_transport_submenu.append(item)
5823 # Block
5824 if blocked:
5825 item = gtk.ImageMenuItem(_('_Unblock'))
5826 item.connect('activate', self.on_unblock, [(contact, account)])
5827 else:
5828 item = gtk.ImageMenuItem(_('_Block'))
5829 item.connect('activate', self.on_block, [(contact, account)])
5831 icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
5832 item.set_image(icon)
5833 manage_transport_submenu.append(item)
5834 if gajim.account_is_disconnected(account):
5835 item.set_sensitive(False)
5837 # Remove
5838 item = gtk.ImageMenuItem(_('_Remove'))
5839 icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
5840 item.set_image(icon)
5841 manage_transport_submenu.append(item)
5842 item.connect('activate', self.on_remove_agent, [(contact, account)])
5843 if gajim.account_is_disconnected(account):
5844 item.set_sensitive(False)
5846 item = gtk.SeparatorMenuItem() # separator
5847 menu.append(item)
5849 # Information
5850 information_menuitem = gtk.ImageMenuItem(_('_Information'))
5851 icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
5852 information_menuitem.set_image(icon)
5853 menu.append(information_menuitem)
5854 information_menuitem.connect('activate', self.on_info, contact, account)
5857 event_button = gtkgui_helpers.get_possible_button_event(event)
5859 menu.attach_to_widget(self.tree, None)
5860 menu.connect('selection-done', gtkgui_helpers.destroy_widget)
5861 menu.show_all()
5862 menu.popup(None, None, None, event_button, event.time)
5864 def make_groupchat_menu(self, event, titer):
5865 model = self.modelfilter
5867 jid = model[titer][C_JID].decode('utf-8')
5868 account = model[titer][C_ACCOUNT].decode('utf-8')
5869 contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
5870 menu = gtk.Menu()
5872 if jid in gajim.interface.minimized_controls[account]:
5873 maximize_menuitem = gtk.ImageMenuItem(_('_Maximize'))
5874 icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU)
5875 maximize_menuitem.set_image(icon)
5876 maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
5877 jid, account)
5878 menu.append(maximize_menuitem)
5880 disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect'))
5881 disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \
5882 gtk.ICON_SIZE_MENU)
5883 disconnect_menuitem.set_image(disconnect_icon)
5884 disconnect_menuitem.connect('activate', self.on_disconnect, jid, account)
5885 menu.append(disconnect_menuitem)
5887 item = gtk.SeparatorMenuItem() # separator
5888 menu.append(item)
5890 history_menuitem = gtk.ImageMenuItem(_('_History'))
5891 history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \
5892 gtk.ICON_SIZE_MENU)
5893 history_menuitem.set_image(history_icon)
5894 history_menuitem .connect('activate', self.on_history, \
5895 contact, account)
5896 menu.append(history_menuitem)
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 build_resources_submenu(self, contacts, account, action, room_jid=None,
5906 room_account=None, cap=None):
5907 ''' Build a submenu with contact's resources.
5908 room_jid and room_account are for action self.on_invite_to_room '''
5909 sub_menu = gtk.Menu()
5911 iconset = gajim.config.get('iconset')
5912 if not iconset:
5913 iconset = gajim.config.DEFAULT_ICONSET
5914 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
5915 for c in contacts:
5916 # icon MUST be different instance for every item
5917 state_images = gtkgui_helpers.load_iconset(path)
5918 item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority)))
5919 icon_name = helpers.get_icon_name_to_show(c, account)
5920 icon = state_images[icon_name]
5921 item.set_image(icon)
5922 sub_menu.append(item)
5923 if action == self.on_invite_to_room:
5924 item.connect('activate', action, [(c, account)],
5925 room_jid, room_account, c.resource)
5926 elif action == self.on_invite_to_new_room:
5927 item.connect('activate', action, [(c, account)], c.resource)
5928 else: # start_chat, execute_command, send_file
5929 item.connect('activate', action, c, account, c.resource)
5930 if cap and \
5931 not gajim.capscache.is_supported(c, cap):
5932 item.set_sensitive(False)
5933 return sub_menu
5935 def build_invite_submenu(self, invite_menuitem, list_):
5936 '''list_ in a list of (contact, account)'''
5937 # used if we invite only one contact with several resources
5938 contact_list = []
5939 if len(list_) == 1:
5940 contact, account = list_[0]
5941 contact_list = gajim.contacts.get_contacts(account, contact.jid)
5942 contacts_transport = -1
5943 connected_accounts = []
5944 # -1 is at start, False when not from the same, None when jabber
5945 for (contact, account) in list_:
5946 if not account in connected_accounts:
5947 connected_accounts.append(account)
5948 transport = gajim.get_transport_name_from_jid(contact.jid)
5949 if contacts_transport == -1:
5950 contacts_transport = transport
5951 elif contacts_transport != transport:
5952 contacts_transport = False
5954 if contacts_transport == False:
5955 # they are not all from the same transport
5956 invite_menuitem.set_sensitive(False)
5957 return
5958 invite_to_submenu = gtk.Menu()
5959 invite_menuitem.set_submenu(invite_to_submenu)
5960 invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat'))
5961 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
5962 invite_to_new_room_menuitem.set_image(icon)
5963 if len(contact_list) > 1: # several resources
5964 invite_to_new_room_menuitem.set_submenu(self.build_resources_submenu(
5965 contact_list, account, self.on_invite_to_new_room, cap=NS_MUC))
5966 elif len(list_) == 1 and gajim.capscache.is_supported(contact, NS_MUC):
5967 invite_menuitem.set_sensitive(True)
5968 # use resource if it's self contact
5969 if contact.jid == gajim.get_jid_from_account(account):
5970 resource = contact.resource
5971 else:
5972 resource = None
5973 invite_to_new_room_menuitem.connect('activate',
5974 self.on_invite_to_new_room, list_, resource)
5975 else:
5976 invite_menuitem.set_sensitive(False)
5977 # transform None in 'jabber'
5978 c_t = contacts_transport or 'jabber'
5979 muc_jid = {}
5980 for account in connected_accounts:
5981 for t in gajim.connections[account].muc_jid:
5982 muc_jid[t] = gajim.connections[account].muc_jid[t]
5983 if c_t not in muc_jid:
5984 invite_to_new_room_menuitem.set_sensitive(False)
5985 rooms = [] # a list of (room_jid, account) tuple
5986 invite_to_submenu.append(invite_to_new_room_menuitem)
5987 rooms = [] # a list of (room_jid, account) tuple
5988 minimized_controls = []
5989 for account in connected_accounts:
5990 minimized_controls += \
5991 gajim.interface.minimized_controls[account].values()
5992 for gc_control in gajim.interface.msg_win_mgr.get_controls(
5993 message_control.TYPE_GC) + minimized_controls:
5994 acct = gc_control.account
5995 room_jid = gc_control.room_jid
5996 if room_jid in gajim.gc_connected[acct] and \
5997 gajim.gc_connected[acct][room_jid] and \
5998 contacts_transport == gajim.get_transport_name_from_jid(room_jid):
5999 rooms.append((room_jid, acct))
6000 if len(rooms):
6001 item = gtk.SeparatorMenuItem() # separator
6002 invite_to_submenu.append(item)
6003 for (room_jid, account) in rooms:
6004 menuitem = gtk.MenuItem(room_jid.split('@')[0])
6005 if len(contact_list) > 1: # several resources
6006 menuitem.set_submenu(self.build_resources_submenu(
6007 contact_list, account, self.on_invite_to_room, room_jid,
6008 account))
6009 else:
6010 # use resource if it's self contact
6011 if contact.jid == gajim.get_jid_from_account(account):
6012 resource = contact.resource
6013 else:
6014 resource = None
6015 menuitem.connect('activate', self.on_invite_to_room, list_,
6016 room_jid, account, resource)
6017 invite_to_submenu.append(menuitem)
6019 def get_and_connect_advanced_menuitem_menu(self, account):
6020 '''adds FOR ACCOUNT options'''
6021 xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade')
6022 advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu')
6024 xml_console_menuitem = xml.get_widget('xml_console_menuitem')
6025 privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem')
6026 administrator_menuitem = xml.get_widget('administrator_menuitem')
6027 send_server_message_menuitem = xml.get_widget(
6028 'send_server_message_menuitem')
6029 set_motd_menuitem = xml.get_widget('set_motd_menuitem')
6030 update_motd_menuitem = xml.get_widget('update_motd_menuitem')
6031 delete_motd_menuitem = xml.get_widget('delete_motd_menuitem')
6033 xml_console_menuitem.connect('activate',
6034 self.on_xml_console_menuitem_activate, account)
6036 if gajim.connections[account] and gajim.connections[account].\
6037 privacy_rules_supported:
6038 privacy_lists_menuitem.connect('activate',
6039 self.on_privacy_lists_menuitem_activate, account)
6040 else:
6041 privacy_lists_menuitem.set_sensitive(False)
6043 if gajim.connections[account].is_zeroconf:
6044 administrator_menuitem.set_sensitive(False)
6045 send_server_message_menuitem.set_sensitive(False)
6046 set_motd_menuitem.set_sensitive(False)
6047 update_motd_menuitem.set_sensitive(False)
6048 delete_motd_menuitem.set_sensitive(False)
6049 else:
6050 send_server_message_menuitem.connect('activate',
6051 self.on_send_server_message_menuitem_activate, account)
6053 set_motd_menuitem.connect('activate',
6054 self.on_set_motd_menuitem_activate, account)
6056 update_motd_menuitem.connect('activate',
6057 self.on_update_motd_menuitem_activate, account)
6059 delete_motd_menuitem.connect('activate',
6060 self.on_delete_motd_menuitem_activate, account)
6062 advanced_menuitem_menu.show_all()
6064 return advanced_menuitem_menu
6066 def add_history_manager_menuitem(self, menu):
6067 '''adds a seperator and History Manager menuitem BELOW for account
6068 menuitems'''
6069 item = gtk.SeparatorMenuItem() # separator
6070 menu.append(item)
6072 # History manager
6073 item = gtk.ImageMenuItem(_('History Manager'))
6074 icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
6075 gtk.ICON_SIZE_MENU)
6076 item.set_image(icon)
6077 menu.append(item)
6078 item.connect('activate', self.on_history_manager_menuitem_activate)
6080 def add_bookmarks_list(self, gc_sub_menu, account):
6081 '''Show join new group chat item and bookmarks list for an account'''
6082 item = gtk.ImageMenuItem(_('_Join New Group Chat'))
6083 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
6084 item.set_image(icon)
6085 item.connect('activate', self.on_join_gc_activate, account)
6086 gc_sub_menu.append(item)
6088 # user has at least one bookmark
6089 if len(gajim.connections[account].bookmarks) > 0:
6090 item = gtk.SeparatorMenuItem() # separator
6091 gc_sub_menu.append(item)
6093 for bookmark in gajim.connections[account].bookmarks:
6094 item = gtk.MenuItem(bookmark['name'], False) # Do not use underline
6095 item.connect('activate', self.on_bookmark_menuitem_activate,
6096 account, bookmark)
6097 gc_sub_menu.append(item)
6099 def set_actions_menu_needs_rebuild(self):
6100 self.actions_menu_needs_rebuild = True
6102 def show_appropriate_context_menu(self, event, iters):
6103 # iters must be all of the same type
6104 model = self.modelfilter
6105 type_ = model[iters[0]][C_TYPE]
6106 for titer in iters[1:]:
6107 if model[titer][C_TYPE] != type_:
6108 return
6109 if type_ == 'group' and len(iters) == 1:
6110 self.make_group_menu(event, iters[0])
6111 if type_ == 'groupchat' and len(iters) == 1:
6112 self.make_groupchat_menu(event, iters[0])
6113 elif type_ == 'agent' and len(iters) == 1:
6114 self.make_transport_menu(event, iters[0])
6115 elif type_ in ('contact', 'self_contact') and len(iters) == 1:
6116 self.make_contact_menu(event, iters[0])
6117 elif type_ == 'contact':
6118 self.make_multiple_contact_menu(event, iters)
6119 elif type_ == 'account' and len(iters) == 1:
6120 self.make_account_menu(event, iters[0])
6122 def show_treeview_menu(self, event):
6123 try:
6124 model, list_of_paths = self.tree.get_selection().get_selected_rows()
6125 except TypeError:
6126 self.tree.get_selection().unselect_all()
6127 return
6128 if not len(list_of_paths):
6129 # no row is selected
6130 return
6131 if len(list_of_paths) > 1:
6132 iters = []
6133 for path in list_of_paths:
6134 iters.append(model.get_iter(path))
6135 else:
6136 path = list_of_paths[0]
6137 iters = [model.get_iter(path)]
6138 self.show_appropriate_context_menu(event, iters)
6140 return True
6142 ################################################################################
6143 ###
6144 ################################################################################
6146 def __init__(self):
6147 self.filtering = False
6148 self.xml = gtkgui_helpers.get_glade('roster_window.glade')
6149 self.window = self.xml.get_widget('roster_window')
6150 self.hpaned = self.xml.get_widget('roster_hpaned')
6151 self.music_track_changed_signal = None
6152 gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
6153 gajim.interface.msg_win_mgr.connect('window-delete',
6154 self.on_message_window_delete)
6155 self.advanced_menus = [] # We keep them to destroy them
6156 if gajim.config.get('roster_window_skip_taskbar'):
6157 self.window.set_property('skip-taskbar-hint', True)
6158 self.tree = self.xml.get_widget('roster_treeview')
6159 sel = self.tree.get_selection()
6160 sel.set_mode(gtk.SELECTION_MULTIPLE)
6161 #sel.connect('changed',
6162 # self.on_treeview_selection_changed)
6164 self._last_selected_contact = [] # holds a list of (jid, account) tupples
6165 self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
6166 'closed': {}}
6168 self.last_save_dir = None
6169 self.editing_path = None # path of row with cell in edit mode
6170 self.add_new_contact_handler_id = False
6171 self.service_disco_handler_id = False
6172 self.new_chat_menuitem_handler_id = False
6173 self.single_message_menuitem_handler_id = False
6174 self.profile_avatar_menuitem_handler_id = False
6175 self.actions_menu_needs_rebuild = True
6176 self.regroup = gajim.config.get('mergeaccounts')
6177 self.clicked_path = None # Used remember on wich row we clicked
6178 if len(gajim.connections) < 2: # Do not merge accounts if only one exists
6179 self.regroup = False
6180 #FIXME: When list_accel_closures will be wrapped in pygtk
6181 # no need of this variable
6182 self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
6183 gtkgui_helpers.resize_window(self.window,
6184 gajim.config.get('roster_width'),
6185 gajim.config.get('roster_height'))
6186 gtkgui_helpers.move_window(self.window,
6187 gajim.config.get('roster_x-position'),
6188 gajim.config.get('roster_y-position'))
6190 self.popups_notification_height = 0
6191 self.popup_notification_windows = []
6193 # Remove contact from roster when last event opened
6194 # { (contact, account): { backend: boolean }
6195 self.contacts_to_be_removed = {}
6196 gajim.events.event_removed_subscribe(self.on_event_removed)
6198 # when this value become 0 we quit main application. If it's more than 0
6199 # it means we are waiting for this number of accounts to disconnect before
6200 # quitting
6201 self.quit_on_next_offline = -1
6203 # uf_show, img, show, sensitive
6204 liststore = gtk.ListStore(str, gtk.Image, str, bool)
6205 self.status_combobox = self.xml.get_widget('status_combobox')
6207 cell = cell_renderer_image.CellRendererImage(0, 1)
6208 self.status_combobox.pack_start(cell, False)
6210 # img to show is in in 2nd column of liststore
6211 self.status_combobox.add_attribute(cell, 'image', 1)
6212 # if it will be sensitive or not it is in the fourth column
6213 # all items in the 'row' must have sensitive to False
6214 # if we want False (so we add it for img_cell too)
6215 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6217 cell = gtk.CellRendererText()
6218 cell.set_property('xpad', 5) # padding for status text
6219 self.status_combobox.pack_start(cell, True)
6220 # text to show is in in first column of liststore
6221 self.status_combobox.add_attribute(cell, 'text', 0)
6222 # if it will be sensitive or not it is in the fourth column
6223 self.status_combobox.add_attribute(cell, 'sensitive', 3)
6225 self.status_combobox.set_row_separator_func(self._iter_is_separator)
6227 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
6228 uf_show = helpers.get_uf_show(show)
6229 liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
6230 show], show, True])
6231 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6232 liststore.append(['SEPARATOR', None, '', True])
6234 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
6235 img = gtk.Image()
6236 img.set_from_file(path)
6237 # sensitivity to False because by default we're offline
6238 self.status_message_menuitem_iter = liststore.append(
6239 [_('Change Status Message...'), img, '', False])
6240 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6241 liststore.append(['SEPARATOR', None, '', True])
6243 uf_show = helpers.get_uf_show('offline')
6244 liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
6245 'offline'], 'offline', True])
6247 status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
6248 'invisible', 'separator1', 'change_status_msg', 'separator2',
6249 'offline']
6250 self.status_combobox.set_model(liststore)
6252 # default to offline
6253 number_of_menuitem = status_combobox_items.index('offline')
6254 self.status_combobox.set_active(number_of_menuitem)
6256 # holds index to previously selected item so if "change status message..."
6257 # is selected we can fallback to previously selected item and not stay
6258 # with that item selected
6259 self.previous_status_combobox_active = number_of_menuitem
6261 showOffline = gajim.config.get('showoffline')
6262 showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online')
6264 w = self.xml.get_widget('show_offline_contacts_menuitem')
6265 w.set_active(showOffline)
6266 if showOnlyChatAndOnline:
6267 w.set_sensitive(False)
6269 w = self.xml.get_widget('show_only_active_contacts_menuitem')
6270 w.set_active(showOnlyChatAndOnline)
6271 if showOffline:
6272 w.set_sensitive(False)
6274 show_transports_group = gajim.config.get('show_transports_group')
6275 self.xml.get_widget('show_transports_menuitem').set_active(
6276 show_transports_group)
6278 self.xml.get_widget('show_roster_menuitem').set_active(True)
6280 # columns
6282 # this col has 3 cells:
6283 # first one img, second one text, third is sec pixbuf
6284 col = gtk.TreeViewColumn()
6286 def add_avatar_renderer():
6287 render_pixbuf = gtk.CellRendererPixbuf() # avatar img
6288 col.pack_start(render_pixbuf, expand=False)
6289 col.add_attribute(render_pixbuf, 'pixbuf',
6290 C_AVATAR_PIXBUF)
6291 col.set_cell_data_func(render_pixbuf,
6292 self._fill_avatar_pixbuf_renderer, None)
6294 if gajim.config.get('avatar_position_in_roster') == 'left':
6295 add_avatar_renderer()
6297 render_image = cell_renderer_image.CellRendererImage(0, 0)
6298 # show img or +-
6299 col.pack_start(render_image, expand=False)
6300 col.add_attribute(render_image, 'image', C_IMG)
6301 col.set_cell_data_func(render_image, self._iconCellDataFunc, None)
6303 render_text = gtk.CellRendererText() # contact or group or account name
6304 render_text.set_property('ellipsize', pango.ELLIPSIZE_END)
6305 col.pack_start(render_text, expand=True)
6306 col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name
6307 col.set_cell_data_func(render_text, self._nameCellDataFunc, None)
6309 render_pixbuf = gtk.CellRendererPixbuf()
6310 col.pack_start(render_pixbuf, expand=False)
6311 col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF)
6312 col.set_cell_data_func(render_pixbuf,
6313 self._fill_mood_pixbuf_renderer, None)
6315 render_pixbuf = gtk.CellRendererPixbuf()
6316 col.pack_start(render_pixbuf, expand=False)
6317 col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF)
6318 col.set_cell_data_func(render_pixbuf,
6319 self._fill_activity_pixbuf_renderer, None)
6321 render_pixbuf = gtk.CellRendererPixbuf()
6322 col.pack_start(render_pixbuf, expand=False)
6323 col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF)
6324 col.set_cell_data_func(render_pixbuf,
6325 self._fill_tune_pixbuf_renderer, None)
6327 if gajim.config.get('avatar_position_in_roster') == 'right':
6328 add_avatar_renderer()
6330 render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img
6331 col.pack_start(render_pixbuf, expand=False)
6332 col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF)
6333 col.set_cell_data_func(render_pixbuf,
6334 self._fill_padlock_pixbuf_renderer, None)
6335 self.tree.append_column(col)
6337 # do not show gtk arrows workaround
6338 col = gtk.TreeViewColumn()
6339 render_pixbuf = gtk.CellRendererPixbuf()
6340 col.pack_start(render_pixbuf, expand=False)
6341 self.tree.append_column(col)
6342 col.set_visible(False)
6343 self.tree.set_expander_column(col)
6345 # set search function
6346 self.tree.set_search_equal_func(self._search_roster_func)
6348 # signals
6349 self.TARGET_TYPE_URI_LIST = 80
6350 TARGETS = [('MY_TREE_MODEL_ROW',
6351 gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)]
6352 TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
6353 ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)]
6354 self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS,
6355 gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY)
6356 self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT)
6357 self.tree.connect('drag_begin', self.drag_begin)
6358 self.tree.connect('drag_end', self.drag_end)
6359 self.tree.connect('drag_drop', self.drag_drop)
6360 self.tree.connect('drag_data_get', self.drag_data_get_data)
6361 self.tree.connect('drag_data_received', self.drag_data_received_data)
6362 self.dragging = False
6363 self.xml.signal_autoconnect(self)
6364 self.combobox_callback_active = True
6366 self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
6367 self.tooltip = tooltips.RosterTooltip()
6368 # Workaroung: For strange reasons signal is behaving like row-changed
6369 self._toggeling_row = False
6370 self.setup_and_draw_roster()
6372 for account in gajim.connections:
6373 if gajim.config.get_per('accounts', account, 'publish_tune') and \
6374 dbus_support.supported:
6375 listener = MusicTrackListener.get()
6376 self.music_track_changed_signal = listener.connect(
6377 'music-track-changed', self.music_track_changed)
6378 track = listener.get_playing_track()
6379 self.music_track_changed(listener, track)
6380 break
6382 if gajim.config.get('show_roster_on_startup'):
6383 self.window.show_all()
6384 else:
6385 if not gajim.config.get('trayicon') or not \
6386 gajim.interface.systray_capabilities:
6387 # cannot happen via GUI, but I put this incase user touches
6388 # config. without trayicon, he or she should see the roster!
6389 self.window.show_all()
6390 gajim.config.set('show_roster_on_startup', True)
6392 if len(gajim.connections) == 0: # if we have no account
6393 gajim.interface.instances['account_creation_wizard'] = \
6394 config.AccountCreationWizardWindow()
6395 if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
6396 # Create zeroconf in config file
6397 from common.zeroconf import connection_zeroconf
6398 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
6400 # vim: se ts=3: