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