1 # -*- coding: utf-8 -*-
2 ## src/roster_window.py
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
6 ## Stéphan Kochen <stephan AT kochen.nl>
7 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
8 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
9 ## Nikos Kouremenos <kourem AT gmail.com>
10 ## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
11 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
12 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
13 ## James Newton <redshodan AT gmail.com>
14 ## Tomasz Melcer <liori AT exroot.org>
15 ## Julien Pivotto <roidelapluie AT gmail.com>
16 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
17 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
18 ## Jonathan Schleifer <js-gajim AT webkeks.org>
20 ## This file is part of Gajim.
22 ## Gajim is free software; you can redistribute it and/or modify
23 ## it under the terms of the GNU General Public License as published
24 ## by the Free Software Foundation; version 3 only.
26 ## Gajim is distributed in the hope that it will be useful,
27 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
28 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 ## GNU General Public License for more details.
31 ## You should have received a copy of the GNU General Public License
32 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
50 import gui_menu_builder
51 import cell_renderer_image
53 import message_control
55 import features_window
59 from common
import gajim
60 from common
import helpers
61 from common
.exceptions
import GajimGeneralException
62 from common
import i18n
63 from common
import pep
64 from common
import location_listener
65 from common
import ged
67 from message_window
import MessageWindowMgr
69 from common
import dbus_support
70 if dbus_support
.supported
:
73 from common
.xmpp
.protocol
import NS_FILE
74 from common
.pep
import MOODS
, ACTIVITIES
76 #(icon, name, type, jid, account, editable, second pixbuf)
78 C_IMG
, # image to show state (online, new message etc)
79 C_NAME
, # cellrenderer text that holds contact nickame
80 C_TYPE
, # account, group or contact?
81 C_JID
, # the jid of the row
82 C_ACCOUNT
, # cellrenderer text that holds account name
87 C_AVATAR_PIXBUF
, # avatar_pixbuf
88 C_PADLOCK_PIXBUF
, # use for account row only
93 Class for main window of the GTK+ interface
96 def _get_account_iter(self
, name
, model
=None):
98 Return the gtk.TreeIter of the given account or None if not found
101 name -- the account name
102 model -- the data model (default TreeFilterModel)
105 model
= self
.modelfilter
111 it
= self
._iters
[name
]['account']
113 if model
== self
.model
or it
is None:
116 return self
.modelfilter
.convert_child_iter_to_iter(it
)
121 def _get_group_iter(self
, name
, account
, model
=None):
123 Return the gtk.TreeIter of the given group or None if not found
126 name -- the group name
127 account -- the account name
128 model -- the data model (default TreeFilterModel)
131 model
= self
.modelfilter
138 if name
not in self
._iters
[account
]['groups']:
141 it
= self
._iters
[account
]['groups'][name
]
142 if model
== self
.model
or it
is None:
145 return self
.modelfilter
.convert_child_iter_to_iter(it
)
150 def _get_self_contact_iter(self
, account
, model
=None):
152 Return the gtk.TreeIter of SelfContact or None if not found
155 account -- the account of SelfContact
156 model -- the data model (default TreeFilterModel)
158 jid
= gajim
.get_jid_from_account(account
)
159 its
= self
._get
_contact
_iter
(jid
, account
, model
=model
)
165 def _get_contact_iter(self
, jid
, account
, contact
=None, model
=None):
167 Return a list of gtk.TreeIter of the given contact
170 jid -- the jid without resource
171 account -- the account
172 contact -- the contact (default None)
173 model -- the data model (default TreeFilterModel)
176 model
= self
.modelfilter
177 # when closing Gajim model can be none (async pbs?)
182 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
184 # We don't know this contact
187 if account
not in self
._iters
:
190 if jid
not in self
._iters
[account
]['contacts']:
193 its
= self
._iters
[account
]['contacts'][jid
]
198 if model
== self
.model
:
204 its2
.append(self
.modelfilter
.convert_child_iter_to_iter(it
))
210 def _iter_is_separator(self
, model
, titer
):
212 Return True if the given iter is a separator
215 model -- the data model
216 iter -- the gtk.TreeIter to test
218 if model
[titer
][0] == 'SEPARATOR':
223 #############################################################################
224 ### Methods for adding and removing roster window items
225 #############################################################################
227 def add_account(self
, account
):
229 Add account to roster and draw it. Do nothing if it is already in
231 if self
._get
_account
_iter
(account
):
232 # Will happen on reconnect or for merged accounts
236 # Merged accounts view
237 show
= helpers
.get_global_show()
238 it
= self
.model
.append(None, [
239 gajim
.interface
.jabber_state_images
['16'][show
],
240 _('Merged accounts'), 'account', '', 'all', None, None, None,
241 None, None, None] + [None] * self
.nb_ext_renderers
)
242 self
._iters
['MERGED']['account'] = it
244 show
= gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
]
245 our_jid
= gajim
.get_jid_from_account(account
)
248 if gajim
.account_is_securely_connected(account
):
249 # the only way to create a pixbuf from stock
250 tls_pixbuf
= self
.window
.render_icon(
251 gtk
.STOCK_DIALOG_AUTHENTICATION
,
254 it
= self
.model
.append(None, [
255 gajim
.interface
.jabber_state_images
['16'][show
],
256 gobject
.markup_escape_text(account
), 'account', our_jid
,
257 account
, None, None, None, None, None, tls_pixbuf
] +
258 [None] * self
.nb_ext_renderers
)
259 self
._iters
[account
]['account'] = it
261 self
.draw_account(account
)
264 def add_account_contacts(self
, account
):
266 Add all contacts and groups of the given account to roster, draw them
270 jids
= gajim
.contacts
.get_jid_list(account
)
273 self
.add_contact(jid
, account
)
275 # Do not freeze the GUI when drawing the contacts
277 # Overhead is big, only invoke when needed
278 self
._idle
_draw
_jids
_of
_account
(jids
, account
)
280 # Draw all known groups
281 for group
in gajim
.groups
[account
]:
282 self
.draw_group(group
, account
)
283 self
.draw_account(account
)
285 self
.starting
= False
287 def _add_group_iter(self
, account
, group
):
289 Add a group iter in roster and return the newly created iter
292 account_group
= 'MERGED'
294 account_group
= account
295 delimiter
= gajim
.connections
[account
].nested_group_delimiter
296 group_splited
= group
.split(delimiter
)
297 parent_group
= delimiter
.join(group_splited
[:-1])
298 if parent_group
in self
._iters
[account_group
]['groups']:
299 iter_parent
= self
._iters
[account_group
]['groups'][parent_group
]
301 iter_parent
= self
._add
_group
_iter
(account
, parent_group
)
302 if parent_group
not in gajim
.groups
[account
]:
303 if account
+ parent_group
in self
.collapsed_rows
:
307 gajim
.groups
[account
][parent_group
] = {'expand': is_expanded
}
309 iter_parent
= self
._get
_account
_iter
(account
, self
.model
)
310 iter_group
= self
.model
.append(iter_parent
,
311 [gajim
.interface
.jabber_state_images
['16']['closed'],
312 gobject
.markup_escape_text(group
), 'group', group
, account
, None,
313 None, None, None, None, None] + [None] * self
.nb_ext_renderers
)
314 self
.draw_group(group
, account
)
315 self
._iters
[account_group
]['groups'][group
] = iter_group
318 def _add_entity(self
, contact
, account
, groups
=None,
319 big_brother_contact
=None, big_brother_account
=None):
321 Add the given contact to roster data model
323 Contact is added regardless if he is already in roster or not. Return
324 list of newly added iters.
327 contact -- the contact to add
328 account -- the contacts account
329 groups -- list of groups to add the contact to.
330 (default groups in contact.get_shown_groups()).
331 Parameter ignored when big_brother_contact is specified.
332 big_brother_contact -- if specified contact is added as child
333 big_brother_contact. (default None)
336 if big_brother_contact
:
337 # Add contact under big brother
339 parent_iters
= self
._get
_contact
_iter
(
340 big_brother_contact
.jid
, big_brother_account
,
341 big_brother_contact
, self
.model
)
342 assert len(parent_iters
) > 0, 'Big brother is not yet in roster!'
344 # Do not confuse get_contact_iter: Sync groups of family members
345 contact
.groups
= big_brother_contact
.get_shown_groups()[:]
347 for child_iter
in parent_iters
:
348 it
= self
.model
.append(child_iter
, [None,
349 contact
.get_shown_name(), 'contact', contact
.jid
, account
,
350 None, None, None, None, None, None] + \
351 [None] * self
.nb_ext_renderers
)
352 added_iters
.append(it
)
353 if contact
.jid
in self
._iters
[account
]['contacts']:
354 self
._iters
[account
]['contacts'][contact
.jid
].append(it
)
356 self
._iters
[account
]['contacts'][contact
.jid
] = [it
]
358 # We are a normal contact. Add us to our groups.
360 groups
= contact
.get_shown_groups()
362 child_iterG
= self
._get
_group
_iter
(group
, account
,
365 # Group is not yet in roster, add it!
366 child_iterG
= self
._add
_group
_iter
(account
, group
)
368 if contact
.is_transport():
370 elif contact
.is_groupchat():
371 typestr
= 'groupchat'
375 # we add some values here. see draw_contact
377 i_
= self
.model
.append(child_iterG
, [None,
378 contact
.get_shown_name(), typestr
, contact
.jid
, account
,
379 None, None, None, None, None, None] + \
380 [None] * self
.nb_ext_renderers
)
381 added_iters
.append(i_
)
382 if contact
.jid
in self
._iters
[account
]['contacts']:
383 self
._iters
[account
]['contacts'][contact
.jid
].append(i_
)
385 self
._iters
[account
]['contacts'][contact
.jid
] = [i_
]
387 # Restore the group expand state
388 if account
+ group
in self
.collapsed_rows
:
392 if group
not in gajim
.groups
[account
]:
393 gajim
.groups
[account
][group
] = {'expand': is_expanded
}
395 assert len(added_iters
), '%s has not been added to roster!' % \
399 def _remove_entity(self
, contact
, account
, groups
=None):
401 Remove the given contact from roster data model
403 Empty groups after contact removal are removed too.
404 Return False if contact still has children and deletion was
406 Return True on success.
409 contact -- the contact to add
410 account -- the contacts account
411 groups -- list of groups to remove the contact from.
413 iters
= self
._get
_contact
_iter
(contact
.jid
, account
, contact
,
415 assert iters
, '%s shall be removed but is not in roster' % contact
.jid
417 parent_iter
= self
.model
.iter_parent(iters
[0])
418 parent_type
= self
.model
[parent_iter
][C_TYPE
]
421 # Only remove from specified groups
423 group_iters
= [self
._get
_group
_iter
(group
, account
)
425 iters
= [titer
for titer
in all_iters
426 if self
.model
.iter_parent(titer
) in group_iters
]
428 iter_children
= self
.model
.iter_children(iters
[0])
431 # We have children. We cannot be removed!
433 # Remove us and empty groups from the model
435 assert self
.model
[i
][C_JID
] == contact
.jid
and \
436 self
.model
[i
][C_ACCOUNT
] == account
, \
437 "Invalidated iters of %s" % contact
.jid
439 parent_i
= self
.model
.iter_parent(i
)
440 parent_type
= self
.model
[parent_i
][C_TYPE
]
443 while parent_type
== 'group' and \
444 self
.model
.iter_n_children(parent_i
) == 1:
446 account_group
= 'MERGED'
448 account_group
= account
449 group
= self
.model
[parent_i
][C_JID
].decode('utf-8')
450 if group
in gajim
.groups
[account
]:
451 del gajim
.groups
[account
][group
]
452 to_be_removed
= parent_i
453 del self
._iters
[account_group
]['groups'][group
]
454 parent_i
= self
.model
.iter_parent(parent_i
)
455 parent_type
= self
.model
[parent_i
][C_TYPE
]
456 self
.model
.remove(to_be_removed
)
458 del self
._iters
[account
]['contacts'][contact
.jid
]
461 def _add_metacontact_family(self
, family
, account
):
463 Add the give Metacontact family to roster data model
465 Add Big Brother to his groups and all others under him.
466 Return list of all added (contact, account) tuples with
467 Big Brother as first element.
470 family -- the family, see Contacts.get_metacontacts_family()
473 nearby_family
, big_brother_jid
, big_brother_account
= \
474 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)
475 big_brother_contact
= gajim
.contacts
.get_first_contact_from_jid(
476 big_brother_account
, big_brother_jid
)
478 assert len(self
._get
_contact
_iter
(big_brother_jid
,
479 big_brother_account
, big_brother_contact
, self
.model
)) == 0, \
480 'Big brother %s already in roster\n Family: %s' \
481 % (big_brother_jid
, family
)
482 self
._add
_entity
(big_brother_contact
, big_brother_account
)
485 # Filter family members
486 for data
in nearby_family
:
487 _account
= data
['account']
489 _contact
= gajim
.contacts
.get_first_contact_from_jid(
492 if not _contact
or _contact
== big_brother_contact
:
493 # Corresponding account is not connected
494 # or brother already added
497 assert len(self
._get
_contact
_iter
(_jid
, _account
,
498 _contact
, self
.model
)) == 0, \
499 "%s already in roster.\n Family: %s" % (_jid
, nearby_family
)
500 self
._add
_entity
(_contact
, _account
,
501 big_brother_contact
= big_brother_contact
,
502 big_brother_account
= big_brother_account
)
503 brothers
.append((_contact
, _account
))
505 brothers
.insert(0, (big_brother_contact
, big_brother_account
))
508 def _remove_metacontact_family(self
, family
, account
):
510 Remove the given Metacontact family from roster data model
512 See Contacts.get_metacontacts_family() and
513 RosterWindow._remove_entity()
515 nearby_family
= self
._get
_nearby
_family
_and
_big
_brother
(
518 # Family might has changed (actual big brother not on top).
519 # Remove childs first then big brother
520 family_in_roster
= False
521 for data
in nearby_family
:
522 _account
= data
['account']
524 _contact
= gajim
.contacts
.get_first_contact_from_jid(_account
, _jid
)
526 iters
= self
._get
_contact
_iter
(_jid
, _account
, _contact
, self
.model
)
527 if not iters
or not _contact
:
528 # Family might not be up to date.
529 # Only try to remove what is actually in the roster
531 assert iters
, '%s shall be removed but is not in roster \
532 \n Family: %s' % (_jid
, family
)
534 family_in_roster
= True
536 parent_iter
= self
.model
.iter_parent(iters
[0])
537 parent_type
= self
.model
[parent_iter
][C_TYPE
]
539 if parent_type
!= 'contact':
541 old_big_account
= _account
542 old_big_contact
= _contact
546 ok
= self
._remove
_entity
(_contact
, _account
)
547 assert ok
, '%s was not removed' % _jid
548 assert len(self
._get
_contact
_iter
(_jid
, _account
, _contact
,
549 self
.model
)) == 0, '%s is removed but still in roster' % _jid
551 if not family_in_roster
:
554 assert old_big_jid
, 'No Big Brother in nearby family % (Family: %)' % \
555 (nearby_family
, family
)
556 iters
= self
._get
_contact
_iter
(old_big_jid
, old_big_account
,
557 old_big_contact
, self
.model
)
558 assert len(iters
) > 0, 'Old Big Brother %s is not in roster anymore' % \
560 assert not self
.model
.iter_children(iters
[0]), \
561 'Old Big Brother %s still has children' % old_big_jid
563 ok
= self
._remove
_entity
(old_big_contact
, old_big_account
)
564 assert ok
, "Old Big Brother %s not removed" % old_big_jid
565 assert len(self
._get
_contact
_iter
(old_big_jid
, old_big_account
,
566 old_big_contact
, self
.model
)) == 0, \
567 'Old Big Brother %s is removed but still in roster' % old_big_jid
571 def _recalibrate_metacontact_family(self
, family
, account
):
573 Regroup metacontact family if necessary
577 nearby_family
, big_brother_jid
, big_brother_account
= \
578 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)
579 big_brother_contact
= gajim
.contacts
.get_contact(big_brother_account
,
581 child_iters
= self
._get
_contact
_iter
(big_brother_jid
,
582 big_brother_account
, model
=self
.model
)
584 parent_iter
= self
.model
.iter_parent(child_iters
[0])
585 parent_type
= self
.model
[parent_iter
][C_TYPE
]
587 # Check if the current BigBrother has even been before.
588 if parent_type
== 'contact':
589 for data
in nearby_family
:
590 # recalibrate after remove to keep highlight
591 if data
['jid'] in gajim
.to_be_removed
[data
['account']]:
594 self
._remove
_metacontact
_family
(family
, account
)
595 brothers
= self
._add
_metacontact
_family
(family
, account
)
597 for c
, acc
in brothers
:
598 self
.draw_completely(c
.jid
, acc
)
600 # Check is small brothers are under the big brother
601 for child
in nearby_family
:
603 _account
= child
['account']
604 if _account
== big_brother_account
and _jid
== big_brother_jid
:
606 child_iters
= self
._get
_contact
_iter
(_jid
, _account
,
610 parent_iter
= self
.model
.iter_parent(child_iters
[0])
611 parent_type
= self
.model
[parent_iter
][C_TYPE
]
612 if parent_type
!= 'contact':
613 _contact
= gajim
.contacts
.get_contact(_account
, _jid
)
614 self
._remove
_entity
(_contact
, _account
)
615 self
._add
_entity
(_contact
, _account
, groups
=None,
616 big_brother_contact
=big_brother_contact
,
617 big_brother_account
=big_brother_account
)
619 def _get_nearby_family_and_big_brother(self
, family
, account
):
620 return gajim
.contacts
.get_nearby_family_and_big_brother(family
, account
)
622 def _add_self_contact(self
, account
):
624 Add account's SelfContact to roster and draw it and the account
626 Return the SelfContact contact instance
628 jid
= gajim
.get_jid_from_account(account
)
629 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
631 assert len(self
._get
_contact
_iter
(jid
, account
, contact
,
632 self
.model
)) == 0, 'Self contact %s already in roster' % jid
634 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
635 self
._iters
[account
]['contacts'][jid
] = [self
.model
.append(child_iterA
,
636 [None, gajim
.nicks
[account
], 'self_contact', jid
, account
, None,
637 None, None, None, None, None] + [None] * self
.nb_ext_renderers
)]
639 self
.draw_completely(jid
, account
)
640 self
.draw_account(account
)
644 def redraw_metacontacts(self
, account
):
645 for family
in gajim
.contacts
.iter_metacontacts_families(account
):
646 self
._recalibrate
_metacontact
_family
(family
, account
)
648 def add_contact(self
, jid
, account
):
650 Add contact to roster and draw him
652 Add contact to all its group and redraw the groups, the contact and the
653 account. If it's a Metacontact, add and draw the whole family.
654 Do nothing if the contact is already in roster.
656 Return the added contact instance. If it is a Metacontact return
660 jid -- the contact's jid or SelfJid to add SelfContact
661 account -- the corresponding account.
663 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
664 if len(self
._get
_contact
_iter
(jid
, account
, contact
, self
.model
)):
665 # If contact already in roster, do nothing
668 if jid
== gajim
.get_jid_from_account(account
):
669 show_self_contact
= gajim
.config
.get('show_self_contact')
670 if show_self_contact
== 'never':
672 if (contact
.resource
!= gajim
.connections
[account
].server_resource \
673 and show_self_contact
== 'when_other_resource') or \
674 show_self_contact
== 'always':
675 return self
._add
_self
_contact
(account
)
678 is_observer
= contact
.is_observer()
680 # if he has a tag, remove it
681 gajim
.contacts
.remove_metacontact(account
, jid
)
683 # Add contact to roster
684 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
687 # We have a family. So we are a metacontact.
688 # Add all family members that we shall be grouped with
690 # remove existing family members to regroup them
691 self
._remove
_metacontact
_family
(family
, account
)
692 contacts
= self
._add
_metacontact
_family
(family
, account
)
694 # We are a normal contact
695 contacts
= [(contact
, account
), ]
696 self
._add
_entity
(contact
, account
)
698 # Draw the contact and its groups contact
699 if not self
.starting
:
700 for c
, acc
in contacts
:
701 self
.draw_completely(c
.jid
, acc
)
702 for group
in contact
.get_shown_groups():
703 self
.draw_group(group
, account
)
704 self
._adjust
_group
_expand
_collapse
_state
(group
, account
)
705 self
.draw_account(account
)
707 return contacts
[0][0] # it's contact/big brother with highest priority
709 def remove_contact(self
, jid
, account
, force
=False, backend
=False):
711 Remove contact from roster
713 Remove contact from all its group. Remove empty groups or redraw
716 If it's a Metacontact, remove the whole family.
717 Do nothing if the contact is not in roster.
720 jid -- the contact's jid or SelfJid to remove SelfContact
721 account -- the corresponding account.
722 force -- remove contact even it has pending evens (Default False)
723 backend -- also remove contact instance (Default False)
725 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
729 if not force
and (self
.contact_has_pending_roster_events(contact
,
730 account
) or gajim
.interface
.msg_win_mgr
.get_control(jid
, account
)):
731 # Contact has pending events or window
732 #TODO: or single message windows? Bur they are not listed for the
735 if not key
in self
.contacts_to_be_removed
:
736 self
.contacts_to_be_removed
[key
] = {'backend': backend
}
737 # if more pending event, don't remove from roster
738 if self
.contact_has_pending_roster_events(contact
, account
):
741 iters
= self
._get
_contact
_iter
(jid
, account
, contact
, self
.model
)
743 # no more pending events
744 # Remove contact from roster directly
745 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
747 # We have a family. So we are a metacontact.
748 self
._remove
_metacontact
_family
(family
, account
)
750 self
._remove
_entity
(contact
, account
)
752 if backend
and (not gajim
.interface
.msg_win_mgr
.get_control(jid
,
754 # If a window is still opened: don't remove contact instance
755 # Remove contact before redrawing, otherwise the old
756 # numbers will still be show
757 gajim
.contacts
.remove_jid(account
, jid
, remove_meta
=True)
759 rest_of_family
= [data
for data
in family
760 if account
!= data
['account'] or jid
!= data
['jid']]
762 # reshow the rest of the family
763 brothers
= self
._add
_metacontact
_family
(rest_of_family
,
765 for c
, acc
in brothers
:
766 self
.draw_completely(c
.jid
, acc
)
769 # Draw all groups of the contact
770 for group
in contact
.get_shown_groups():
771 self
.draw_group(group
, account
)
772 self
.draw_account(account
)
776 def rename_self_contact(self
, old_jid
, new_jid
, account
):
778 Rename the self_contact jid
781 old_jid -- our old jid
782 new_jid -- our new jid
783 account -- the corresponding account.
785 gajim
.contacts
.change_contact_jid(old_jid
, new_jid
, account
)
786 self_iter
= self
._get
_self
_contact
_iter
(account
, model
=self
.model
)
789 self
.model
[self_iter
][C_JID
] = new_jid
790 self
.draw_contact(new_jid
, account
)
792 def add_groupchat(self
, jid
, account
, status
=''):
794 Add groupchat to roster and draw it. Return the added contact instance
796 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
797 # Do not show gc if we are disconnected and minimize it
798 if gajim
.account_is_connected(account
):
805 gc_control
= gajim
.interface
.msg_win_mgr
.get_gc_control(jid
,
808 # there is a window that we can minimize
809 gajim
.interface
.minimized_controls
[account
][jid
] = gc_control
810 name
= gc_control
.name
811 elif jid
in gajim
.interface
.minimized_controls
[account
]:
812 name
= gajim
.interface
.minimized_controls
[account
][jid
].name
814 name
= jid
.split('@')[0]
816 contact
= gajim
.contacts
.create_contact(jid
=jid
, account
=account
,
817 name
=name
, groups
=[_('Groupchats')], show
=show
, status
=status
,
819 gajim
.contacts
.add_contact(account
, contact
)
820 self
.add_contact(jid
, account
)
822 if jid
not in gajim
.interface
.minimized_controls
[account
]:
823 # there is a window that we can minimize
824 gc_control
= gajim
.interface
.msg_win_mgr
.get_gc_control(jid
,
826 gajim
.interface
.minimized_controls
[account
][jid
] = gc_control
828 contact
.status
= status
829 self
.adjust_and_draw_contact_context(jid
, account
)
834 def remove_groupchat(self
, jid
, account
):
836 Remove groupchat from roster and redraw account and group
838 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
839 if contact
.is_groupchat():
840 if jid
in gajim
.interface
.minimized_controls
[account
]:
841 del gajim
.interface
.minimized_controls
[account
][jid
]
842 self
.remove_contact(jid
, account
, force
=True, backend
=True)
848 # FIXME: This function is yet unused! Port to new API
849 def add_transport(self
, jid
, account
):
851 Add transport to roster and draw it. Return the added contact instance
853 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
855 contact
= gajim
.contacts
.create_contact(jid
=jid
, account
=account
,
856 name
=jid
, groups
=[_('Transports')], show
='offline',
857 status
='offline', sub
='from')
858 gajim
.contacts
.add_contact(account
, contact
)
859 self
.add_contact(jid
, account
)
862 def remove_transport(self
, jid
, account
):
864 Remove transport from roster and redraw account and group
866 self
.remove_contact(jid
, account
, force
=True, backend
=True)
869 def rename_group(self
, old_name
, new_name
, account
):
871 Rename a roster group
873 if old_name
== new_name
:
876 # Groups may not change name from or to a special groups
877 for g
in helpers
.special_groups
:
878 if g
in (new_name
, old_name
):
881 # update all contacts in the given group
883 accounts
= gajim
.connections
.keys()
885 accounts
= [account
, ]
888 changed_contacts
= []
889 for jid
in gajim
.contacts
.get_jid_list(acc
):
890 contact
= gajim
.contacts
.get_first_contact_from_jid(acc
, jid
)
891 if old_name
not in contact
.groups
:
894 self
.remove_contact(jid
, acc
, force
=True)
896 contact
.groups
.remove(old_name
)
897 if new_name
not in contact
.groups
:
898 contact
.groups
.append(new_name
)
900 changed_contacts
.append({'jid': jid
, 'name': contact
.name
,
901 'groups':contact
.groups
})
903 gajim
.connections
[acc
].update_contacts(changed_contacts
)
905 for c
in changed_contacts
:
906 self
.add_contact(c
['jid'], acc
)
908 self
._adjust
_group
_expand
_collapse
_state
(new_name
, acc
)
910 self
.draw_group(old_name
, acc
)
911 self
.draw_group(new_name
, acc
)
914 def add_contact_to_groups(self
, jid
, account
, groups
, update
=True):
916 Add contact to given groups and redraw them
918 Contact on server is updated too. When the contact has a family,
919 the action will be performed for all members.
923 account -- the corresponding account
924 groups -- list of Groups to add the contact to.
925 update -- update contact on the server
927 self
.remove_contact(jid
, account
, force
=True)
928 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
930 if group
not in contact
.groups
:
931 # we might be dropped from meta to group
932 contact
.groups
.append(group
)
934 gajim
.connections
[account
].update_contact(jid
, contact
.name
,
937 self
.add_contact(jid
, account
)
940 self
._adjust
_group
_expand
_collapse
_state
(group
, account
)
942 def remove_contact_from_groups(self
, jid
, account
, groups
, update
=True):
944 Remove contact from given groups and redraw them
946 Contact on server is updated too. When the contact has a family,
947 the action will be performed for all members.
951 account -- the corresponding account
952 groups -- list of Groups to remove the contact from
953 update -- update contact on the server
955 self
.remove_contact(jid
, account
, force
=True)
956 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
958 if group
in contact
.groups
:
959 # Needed when we remove from "General" or "Observers"
960 contact
.groups
.remove(group
)
962 gajim
.connections
[account
].update_contact(jid
, contact
.name
,
964 self
.add_contact(jid
, account
)
966 # Also redraw old groups
968 self
.draw_group(group
, account
)
970 # FIXME: maybe move to gajim.py
971 def remove_newly_added(self
, jid
, account
):
972 if jid
in gajim
.newly_added
[account
]:
973 gajim
.newly_added
[account
].remove(jid
)
974 self
.draw_contact(jid
, account
)
976 # FIXME: maybe move to gajim.py
977 def remove_to_be_removed(self
, jid
, account
):
978 if account
not in gajim
.interface
.instances
:
979 # Account has been deleted during the timeout that called us
981 if jid
in gajim
.newly_added
[account
]:
983 if jid
in gajim
.to_be_removed
[account
]:
984 gajim
.to_be_removed
[account
].remove(jid
)
985 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
987 # Peform delayed recalibration
988 self
._recalibrate
_metacontact
_family
(family
, account
)
989 self
.draw_contact(jid
, account
)
991 # FIXME: integrate into add_contact()
992 def add_to_not_in_the_roster(self
, account
, jid
, nick
='', resource
=''):
994 attached_keys
= gajim
.config
.get_per('accounts', account
,
995 'attached_gpg_keys').split()
996 if jid
in attached_keys
:
997 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
998 contact
= gajim
.contacts
.create_not_in_roster_contact(jid
=jid
,
999 account
=account
, resource
=resource
, name
=nick
, keyID
=keyID
)
1000 gajim
.contacts
.add_contact(account
, contact
)
1001 self
.add_contact(contact
.jid
, account
)
1005 ################################################################################
1006 ### Methods for adding and removing roster window items
1007 ################################################################################
1009 def draw_account(self
, account
):
1010 child_iter
= self
._get
_account
_iter
(account
, self
.model
)
1012 assert False, 'Account iter of %s could not be found.' % account
1015 num_of_accounts
= gajim
.get_number_of_connected_accounts()
1016 num_of_secured
= gajim
.get_number_of_securely_connected_accounts()
1018 if gajim
.account_is_securely_connected(account
) and not self
.regroup
or\
1019 self
.regroup
and num_of_secured
and num_of_secured
== num_of_accounts
:
1020 # the only way to create a pixbuf from stock
1021 tls_pixbuf
= self
.window
.render_icon(
1022 gtk
.STOCK_DIALOG_AUTHENTICATION
, gtk
.ICON_SIZE_MENU
)
1023 self
.model
[child_iter
][C_PADLOCK_PIXBUF
] = tls_pixbuf
1025 self
.model
[child_iter
][C_PADLOCK_PIXBUF
] = None
1028 account_name
= _('Merged accounts')
1031 account_name
= account
1032 accounts
= [account
]
1034 if account
in self
.collapsed_rows
and \
1035 self
.model
.iter_has_child(child_iter
):
1036 account_name
= '[%s]' % account_name
1038 if (gajim
.account_is_connected(account
) or (self
.regroup
and \
1039 gajim
.get_number_of_connected_accounts())) and gajim
.config
.get(
1040 'show_contacts_number'):
1041 nbr_on
, nbr_total
= gajim
.contacts
.get_nb_online_total_contacts(
1042 accounts
= accounts
)
1043 account_name
+= ' (%s/%s)' % (repr(nbr_on
), repr(nbr_total
))
1045 self
.model
[child_iter
][C_NAME
] = account_name
1047 pep_dict
= gajim
.connections
[account
].pep
1048 if gajim
.config
.get('show_mood_in_roster') and 'mood' in pep_dict
:
1049 self
.model
[child_iter
][C_MOOD_PIXBUF
] = pep_dict
['mood'].\
1052 self
.model
[child_iter
][C_MOOD_PIXBUF
] = None
1054 if gajim
.config
.get('show_activity_in_roster') and 'activity' in \
1056 self
.model
[child_iter
][C_ACTIVITY_PIXBUF
] = pep_dict
['activity'].\
1059 self
.model
[child_iter
][C_ACTIVITY_PIXBUF
] = None
1061 if gajim
.config
.get('show_tunes_in_roster') and 'tune' in pep_dict
:
1062 self
.model
[child_iter
][C_TUNE_PIXBUF
] = pep_dict
['tune'].\
1065 self
.model
[child_iter
][C_TUNE_PIXBUF
] = None
1067 if gajim
.config
.get('show_location_in_roster') and 'location' in \
1069 self
.model
[child_iter
][C_LOCATION_PIXBUF
] = pep_dict
['location'].\
1072 self
.model
[child_iter
][C_LOCATION_PIXBUF
] = None
1075 def draw_group(self
, group
, account
):
1076 child_iter
= self
._get
_group
_iter
(group
, account
, model
=self
.model
)
1078 # Eg. We redraw groups after we removed a entitiy
1079 # and its empty groups
1084 accounts
= [account
]
1085 text
= gobject
.markup_escape_text(group
)
1086 if helpers
.group_is_blocked(account
, group
):
1087 text
= '<span strikethrough="true">%s</span>' % text
1088 if gajim
.config
.get('show_contacts_number'):
1089 nbr_on
, nbr_total
= gajim
.contacts
.get_nb_online_total_contacts(
1090 accounts
= accounts
, groups
= [group
])
1091 text
+= ' (%s/%s)' % (repr(nbr_on
), repr(nbr_total
))
1093 self
.model
[child_iter
][C_NAME
] = text
1096 def draw_parent_contact(self
, jid
, account
):
1097 child_iters
= self
._get
_contact
_iter
(jid
, account
, model
=self
.model
)
1100 parent_iter
= self
.model
.iter_parent(child_iters
[0])
1101 if self
.model
[parent_iter
][C_TYPE
] != 'contact':
1102 # parent is not a contact
1104 parent_jid
= self
.model
[parent_iter
][C_JID
].decode('utf-8')
1105 parent_account
= self
.model
[parent_iter
][C_ACCOUNT
].decode('utf-8')
1106 self
.draw_contact(parent_jid
, parent_account
)
1109 def draw_contact(self
, jid
, account
, selected
=False, focus
=False):
1111 Draw the correct state image, name BUT not avatar
1113 # focus is about if the roster window has toplevel-focus or not
1114 # FIXME: We really need a custom cell_renderer
1116 contact_instances
= gajim
.contacts
.get_contacts(account
, jid
)
1117 contact
= gajim
.contacts
.get_highest_prio_contact_from_contacts(
1122 child_iters
= self
._get
_contact
_iter
(jid
, account
, contact
, self
.model
)
1126 name
= gobject
.markup_escape_text(contact
.get_shown_name())
1128 # gets number of unread gc marked messages
1129 if jid
in gajim
.interface
.minimized_controls
[account
] and \
1130 gajim
.interface
.minimized_controls
[account
][jid
]:
1131 nb_unread
= len(gajim
.events
.get_events(account
, jid
,
1132 ['printed_marked_gc_msg']))
1133 nb_unread
+= gajim
.interface
.minimized_controls \
1134 [account
][jid
].get_nb_unread_pm()
1137 name
= '%s *' % name
1139 name
= '%s [%s]' % (name
, str(nb_unread
))
1141 # Strike name if blocked
1143 if helpers
.jid_is_blocked(account
, jid
):
1146 for group
in contact
.get_shown_groups():
1147 if helpers
.group_is_blocked(account
, group
):
1151 name
= '<span strikethrough="true">%s</span>' % name
1153 # Show resource counter
1154 nb_connected_contact
= 0
1155 for c
in contact_instances
:
1156 if c
.show
not in ('error', 'offline'):
1157 nb_connected_contact
+= 1
1158 if nb_connected_contact
> 1:
1159 # switch back to default writing direction
1160 name
+= i18n
.paragraph_direction_mark(unicode(name
))
1161 name
+= u
' (%d)' % nb_connected_contact
1163 # show (account_name) if there are 2 contact with same jid
1167 # look through all contacts of all accounts
1168 for account_
in gajim
.connections
:
1169 # useless to add account name
1170 if account_
== account
:
1172 for jid_
in gajim
.contacts
.get_jid_list(account_
):
1173 contact_
= gajim
.contacts
.get_first_contact_from_jid(
1175 if contact_
.get_shown_name() == contact
.get_shown_name() \
1176 and (jid_
, account_
) != (jid
, account
):
1180 # No need to continue in other account
1181 # if we already found one
1184 name
+= ' (' + account
+ ')'
1186 # add status msg, if not empty, under contact name in
1188 if contact
.status
and gajim
.config
.get('show_status_msgs_in_roster'):
1189 status
= contact
.status
.strip()
1191 status
= helpers
.reduce_chars_newlines(status
,
1193 # escape markup entities and make them small
1194 # italic and fg color color is calcuted to be
1196 color
= gtkgui_helpers
.get_fade_color(self
.tree
, selected
,
1198 colorstring
= '#%04x%04x%04x' % (color
.red
, color
.green
,
1200 name
+= '\n<span size="small" style="italic" ' \
1201 'foreground="%s">%s</span>' % (colorstring
,
1202 gobject
.markup_escape_text(status
))
1204 icon_name
= helpers
.get_icon_name_to_show(contact
, account
)
1205 # look if another resource has awaiting events
1206 for c
in contact_instances
:
1207 c_icon_name
= helpers
.get_icon_name_to_show(c
, account
)
1208 if c_icon_name
in ('event', 'muc_active', 'muc_inactive'):
1209 icon_name
= c_icon_name
1212 # Check for events of collapsed (hidden) brothers
1213 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
1214 is_big_brother
= False
1215 have_visible_children
= False
1217 bb_jid
, bb_account
= \
1218 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)[1:]
1219 is_big_brother
= (jid
, account
) == (bb_jid
, bb_account
)
1220 iters
= self
._get
_contact
_iter
(jid
, account
)
1221 have_visible_children
= iters
and \
1222 self
.modelfilter
.iter_has_child(iters
[0])
1224 if have_visible_children
:
1225 # We are the big brother and have a visible family
1226 for child_iter
in child_iters
:
1227 child_path
= self
.model
.get_path(child_iter
)
1228 path
= self
.modelfilter
.convert_child_path_to_path(child_path
)
1233 if not self
.tree
.row_expanded(path
) and icon_name
!= 'event':
1234 iterC
= self
.model
.iter_children(child_iter
)
1236 # a child has awaiting messages?
1237 jidC
= self
.model
[iterC
][C_JID
].decode('utf-8')
1238 accountC
= self
.model
[iterC
][C_ACCOUNT
].decode('utf-8')
1239 if len(gajim
.events
.get_events(accountC
, jidC
)):
1242 iterC
= self
.model
.iter_next(iterC
)
1244 if self
.tree
.row_expanded(path
):
1245 state_images
= self
.get_appropriate_state_images(
1246 jid
, size
= 'opened',
1247 icon_name
= icon_name
)
1249 state_images
= self
.get_appropriate_state_images(
1250 jid
, size
= 'closed',
1251 icon_name
= icon_name
)
1253 # Expand/collapse icon might differ per iter
1255 img
= state_images
[icon_name
]
1256 self
.model
[child_iter
][C_IMG
] = img
1257 self
.model
[child_iter
][C_NAME
] = name
1259 # A normal contact or little brother
1260 state_images
= self
.get_appropriate_state_images(jid
,
1261 icon_name
= icon_name
)
1263 # All iters have the same icon (no expand/collapse)
1264 img
= state_images
[icon_name
]
1265 for child_iter
in child_iters
:
1266 self
.model
[child_iter
][C_IMG
] = img
1267 self
.model
[child_iter
][C_NAME
] = name
1269 # We are a little brother
1270 if family
and not is_big_brother
and not self
.starting
:
1271 self
.draw_parent_contact(jid
, account
)
1273 delimiter
= gajim
.connections
[account
].nested_group_delimiter
1274 for group
in contact
.get_shown_groups():
1275 # We need to make sure that _visible_func is called for
1276 # our groups otherwise we might not be shown
1277 group_splited
= group
.split(delimiter
)
1279 while i
< len(group_splited
) + 1:
1280 g
= delimiter
.join(group_splited
[:i
])
1281 iterG
= self
._get
_group
_iter
(g
, account
, model
=self
.model
)
1283 # it's not self contact
1284 self
.model
[iterG
][C_JID
] = self
.model
[iterG
][C_JID
]
1287 gajim
.plugin_manager
.gui_extension_point('roster_draw_contact', self
,
1288 jid
, account
, contact
)
1292 def _is_pep_shown_in_roster(self
, pep_type
):
1293 if pep_type
== 'mood':
1294 return gajim
.config
.get('show_mood_in_roster')
1295 elif pep_type
== 'activity':
1296 return gajim
.config
.get('show_activity_in_roster')
1297 elif pep_type
== 'tune':
1298 return gajim
.config
.get('show_tunes_in_roster')
1299 elif pep_type
== 'location':
1300 return gajim
.config
.get('show_location_in_roster')
1304 def draw_all_pep_types(self
, jid
, account
):
1305 for pep_type
in self
._pep
_type
_to
_model
_column
:
1306 self
.draw_pep(jid
, account
, pep_type
)
1308 def draw_pep(self
, jid
, account
, pep_type
):
1309 if pep_type
not in self
._pep
_type
_to
_model
_column
:
1311 if not self
._is
_pep
_shown
_in
_roster
(pep_type
):
1314 model_column
= self
._pep
_type
_to
_model
_column
[pep_type
]
1315 iters
= self
._get
_contact
_iter
(jid
, account
, model
=self
.model
)
1318 contact
= gajim
.contacts
.get_contact(account
, jid
)
1319 if pep_type
in contact
.pep
:
1320 pixbuf
= contact
.pep
[pep_type
].asPixbufIcon()
1323 for child_iter
in iters
:
1324 self
.model
[child_iter
][model_column
] = pixbuf
1326 def draw_avatar(self
, jid
, account
):
1327 iters
= self
._get
_contact
_iter
(jid
, account
, model
=self
.model
)
1328 if not iters
or not gajim
.config
.get('show_avatars_in_roster'):
1330 jid
= self
.model
[iters
[0]][C_JID
].decode('utf-8')
1331 pixbuf
= gtkgui_helpers
.get_avatar_pixbuf_from_cache(jid
)
1332 if pixbuf
in (None, 'ask'):
1333 scaled_pixbuf
= None
1335 scaled_pixbuf
= gtkgui_helpers
.get_scaled_pixbuf(pixbuf
, 'roster')
1336 for child_iter
in iters
:
1337 self
.model
[child_iter
][C_AVATAR_PIXBUF
] = scaled_pixbuf
1340 def draw_completely(self
, jid
, account
):
1341 self
.draw_contact(jid
, account
)
1342 self
.draw_all_pep_types(jid
, account
)
1343 self
.draw_avatar(jid
, account
)
1345 def adjust_and_draw_contact_context(self
, jid
, account
):
1347 Draw contact, account and groups of given jid Show contact if it has
1350 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
1352 # idle draw or just removed SelfContact
1355 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
1357 # There might be a new big brother
1358 self
._recalibrate
_metacontact
_family
(family
, account
)
1359 self
.draw_contact(jid
, account
)
1360 self
.draw_account(account
)
1362 for group
in contact
.get_shown_groups():
1363 self
.draw_group(group
, account
)
1364 self
._adjust
_group
_expand
_collapse
_state
(group
, account
)
1366 def _idle_draw_jids_of_account(self
, jids
, account
):
1368 Draw given contacts and their avatars in a lazy fashion
1371 jids -- a list of jids to draw
1372 account -- the corresponding account
1374 def _draw_all_contacts(jids
, account
):
1376 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
1378 # For metacontacts over several accounts:
1379 # When we connect a new account existing brothers
1380 # must be redrawn (got removed and readded)
1382 self
.draw_completely(data
['jid'], data
['account'])
1384 self
.draw_completely(jid
, account
)
1386 self
.refilter_shown_roster_items()
1389 task
= _draw_all_contacts(jids
, account
)
1390 gobject
.idle_add(task
.next
)
1392 def _before_fill(self
):
1393 self
.tree
.freeze_child_notify()
1394 self
.tree
.set_model(None)
1396 self
.model
.set_sort_column_id(-2, gtk
.SORT_ASCENDING
)
1398 def _after_fill(self
):
1399 self
.model
.set_sort_column_id(1, gtk
.SORT_ASCENDING
)
1400 self
.tree
.set_model(self
.modelfilter
)
1401 self
.tree
.thaw_child_notify()
1403 def setup_and_draw_roster(self
):
1405 Create new empty model and draw roster
1407 self
.modelfilter
= None
1408 self
.model
= gtk
.TreeStore(*self
.columns
)
1410 self
.model
.set_sort_func(1, self
._compareIters
)
1411 self
.model
.set_sort_column_id(1, gtk
.SORT_ASCENDING
)
1412 self
.modelfilter
= self
.model
.filter_new()
1413 self
.modelfilter
.set_visible_func(self
._visible
_func
)
1414 self
.modelfilter
.connect('row-has-child-toggled',
1415 self
.on_modelfilter_row_has_child_toggled
)
1416 self
.tree
.set_model(self
.modelfilter
)
1420 self
._iters
['MERGED'] = {'account': None, 'groups': {}}
1422 for acct
in gajim
.contacts
.get_accounts():
1423 self
._iters
[acct
] = {'account': None, 'groups': {}, 'contacts': {}}
1424 self
.add_account(acct
)
1425 self
.add_account_contacts(acct
)
1427 # Recalculate column width for ellipsizing
1428 self
.tree
.columns_autosize()
1431 def select_contact(self
, jid
, account
):
1433 Select contact in roster. If contact is hidden but has events, show him
1435 # Refiltering SHOULD NOT be needed:
1436 # When a contact gets a new event he will be redrawn and his
1437 # icon changes, so _visible_func WILL be called on him anyway
1438 iters
= self
._get
_contact
_iter
(jid
, account
)
1440 # Not visible in roster
1442 path
= self
.modelfilter
.get_path(iters
[0])
1443 if self
.dragging
or not gajim
.config
.get(
1444 'scroll_roster_to_last_message'):
1445 # do not change selection while DND'ing
1447 # Expand his parent, so this path is visible, don't expand it.
1448 self
.tree
.expand_to_path(path
[:-1])
1449 self
.tree
.scroll_to_cell(path
)
1450 self
.tree
.set_cursor(path
)
1453 def _adjust_account_expand_collapse_state(self
, account
):
1455 Expand/collapse account row based on self.collapsed_rows
1457 if not self
.tree
.get_model():
1459 iterA
= self
._get
_account
_iter
(account
)
1461 # thank you modelfilter
1463 path
= self
.modelfilter
.get_path(iterA
)
1464 if account
in self
.collapsed_rows
:
1465 self
.tree
.collapse_row(path
)
1467 self
.tree
.expand_row(path
, False)
1471 def _adjust_group_expand_collapse_state(self
, group
, account
):
1473 Expand/collapse group row based on self.collapsed_rows
1475 if not self
.tree
.get_model():
1477 delimiter
= gajim
.connections
[account
].nested_group_delimiter
1478 group_splited
= group
.split(delimiter
)
1480 while i
< len(group_splited
) + 1:
1481 g
= delimiter
.join(group_splited
[:i
])
1482 iterG
= self
._get
_group
_iter
(g
, account
)
1486 path
= self
.modelfilter
.get_path(iterG
)
1487 if account
+ g
in self
.collapsed_rows
:
1488 self
.tree
.collapse_row(path
)
1490 self
.tree
.expand_row(path
, False)
1493 ##############################################################################
1494 ### Roster and Modelfilter handling
1495 ##############################################################################
1497 def _search_roster_func(self
, model
, column
, key
, titer
):
1498 key
= key
.decode('utf-8').lower()
1499 name
= model
[titer
][C_NAME
].decode('utf-8').lower()
1500 return not (key
in name
)
1502 def refilter_shown_roster_items(self
):
1503 self
.filtering
= True
1504 self
.modelfilter
.refilter()
1505 self
.filtering
= False
1507 def contact_has_pending_roster_events(self
, contact
, account
):
1509 Return True if the contact or one if it resources has pending events
1511 # jid has pending events
1512 if gajim
.events
.get_nb_roster_events(account
, contact
.jid
) > 0:
1514 # check events of all resources
1515 for contact_
in gajim
.contacts
.get_contacts(account
, contact
.jid
):
1516 if contact_
.resource
and gajim
.events
.get_nb_roster_events(account
,
1517 contact_
.get_full_jid()) > 0:
1521 def contact_is_visible(self
, contact
, account
):
1522 if self
.contact_has_pending_roster_events(contact
, account
):
1525 if contact
.show
in ('offline', 'error'):
1526 if contact
.jid
in gajim
.to_be_removed
[account
]:
1529 if gajim
.config
.get('show_only_chat_and_online') and contact
.show
in (
1530 'away', 'xa', 'busy'):
1534 def _visible_func(self
, model
, titer
):
1536 Determine whether iter should be visible in the treeview
1538 type_
= model
[titer
][C_TYPE
]
1541 if type_
== 'account':
1542 # Always show account
1545 account
= model
[titer
][C_ACCOUNT
]
1549 account
= account
.decode('utf-8')
1550 jid
= model
[titer
][C_JID
]
1553 jid
= jid
.decode('utf-8')
1554 if type_
== 'group':
1556 if group
== _('Transports'):
1558 accounts
= gajim
.contacts
.get_accounts()
1560 accounts
= [account
]
1561 for _acc
in accounts
:
1562 for contact
in gajim
.contacts
.iter_contacts(_acc
):
1563 if group
in contact
.get_shown_groups() and \
1564 self
.contact_has_pending_roster_events(contact
, _acc
):
1566 return gajim
.config
.get('show_transports_group') and \
1567 (gajim
.account_is_connected(account
) or \
1568 gajim
.config
.get('showoffline'))
1569 if gajim
.config
.get('showoffline'):
1573 # C_ACCOUNT for groups depends on the order
1574 # accounts were connected
1575 # Check all accounts for online group contacts
1576 accounts
= gajim
.contacts
.get_accounts()
1578 accounts
= [account
]
1579 for _acc
in accounts
:
1580 delimiter
= gajim
.connections
[_acc
].nested_group_delimiter
1581 for contact
in gajim
.contacts
.iter_contacts(_acc
):
1582 if not self
.contact_is_visible(contact
, _acc
):
1584 # Is this contact in this group?
1585 for grp
in contact
.get_shown_groups():
1589 grp
= delimiter
.join(grp
.split(delimiter
)[:-1])
1591 if type_
== 'contact':
1592 if self
.rfilter_enabled
:
1593 return self
.rfilter_string
in model
[titer
][C_NAME
].lower()
1594 if gajim
.config
.get('showoffline'):
1598 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
1600 nearby_family
, bb_jid
, bb_account
= \
1601 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)
1602 if (bb_jid
, bb_account
) == (jid
, account
):
1603 # Show the big brother if a child has pending events
1604 for data
in nearby_family
:
1606 account
= data
['account']
1607 contact
= gajim
.contacts
.get_contact_with_highest_priority(
1609 if contact
and self
.contact_is_visible(contact
, account
):
1613 contact
= gajim
.contacts
.get_contact_with_highest_priority(
1615 return self
.contact_is_visible(contact
, account
)
1616 if type_
== 'agent':
1617 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
1619 return self
.contact_has_pending_roster_events(contact
, account
) or \
1620 (gajim
.config
.get('show_transports_group') and \
1621 (gajim
.account_is_connected(account
) or \
1622 gajim
.config
.get('showoffline')))
1625 def _compareIters(self
, model
, iter1
, iter2
, data
=None):
1627 Compare two iters to sort them
1629 name1
= model
[iter1
][C_NAME
]
1630 name2
= model
[iter2
][C_NAME
]
1631 if not name1
or not name2
:
1633 name1
= name1
.decode('utf-8')
1634 name2
= name2
.decode('utf-8')
1635 type1
= model
[iter1
][C_TYPE
]
1636 type2
= model
[iter2
][C_TYPE
]
1637 if type1
== 'self_contact':
1639 if type2
== 'self_contact':
1641 if type1
== 'group':
1642 name1
= model
[iter1
][C_JID
]
1643 name2
= model
[iter2
][C_JID
]
1644 if name1
== _('Transports'):
1646 if name2
== _('Transports'):
1648 if name1
== _('Not in Roster'):
1650 if name2
== _('Not in Roster'):
1652 if name1
== _('Groupchats'):
1654 if name2
== _('Groupchats'):
1656 account1
= model
[iter1
][C_ACCOUNT
]
1657 account2
= model
[iter2
][C_ACCOUNT
]
1658 if not account1
or not account2
:
1660 account1
= account1
.decode('utf-8')
1661 account2
= account2
.decode('utf-8')
1662 if type1
== 'account':
1663 return locale
.strcoll(account1
, account2
)
1664 jid1
= model
[iter1
][C_JID
].decode('utf-8')
1665 jid2
= model
[iter2
][C_JID
].decode('utf-8')
1666 if type1
== 'contact':
1667 lcontact1
= gajim
.contacts
.get_contacts(account1
, jid1
)
1668 contact1
= gajim
.contacts
.get_first_contact_from_jid(account1
, jid1
)
1671 name1
= contact1
.get_shown_name()
1672 if type2
== 'contact':
1673 lcontact2
= gajim
.contacts
.get_contacts(account2
, jid2
)
1674 contact2
= gajim
.contacts
.get_first_contact_from_jid(account2
, jid2
)
1677 name2
= contact2
.get_shown_name()
1678 # We first compare by show if sort_by_show_in_roster is True or if it's
1680 if type1
== 'contact' and type2
== 'contact' and \
1681 gajim
.config
.get('sort_by_show_in_roster'):
1682 cshow
= {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
1683 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
1684 s
= self
.get_show(lcontact1
)
1685 show1
= cshow
.get(s
, 9)
1686 s
= self
.get_show(lcontact2
)
1687 show2
= cshow
.get(s
, 9)
1690 if show1
== 6 and jid1
in gajim
.to_be_removed
[account1
]:
1692 if show2
== 6 and jid2
in gajim
.to_be_removed
[account2
]:
1694 if removing1
and not removing2
:
1696 if removing2
and not removing1
:
1700 # none and from goes after
1701 if sub1
not in ['none', 'from'] and sub2
in ['none', 'from']:
1703 if sub1
in ['none', 'from'] and sub2
not in ['none', 'from']:
1710 cmp_result
= locale
.strcoll(name1
.lower(), name2
.lower())
1715 if type1
== 'contact' and type2
== 'contact':
1716 # We compare account names
1717 cmp_result
= locale
.strcoll(account1
.lower(), account2
.lower())
1723 cmp_result
= locale
.strcoll(jid1
.lower(), jid2
.lower())
1730 ################################################################################
1731 ### FIXME: Methods that don't belong to roster window...
1732 ### ... atleast not in there current form
1733 ################################################################################
1735 def fire_up_unread_messages_events(self
, account
):
1737 Read from db the unread messages, and fire them up, and if we find very
1738 old unread messages, delete them from unread table
1740 results
= gajim
.logger
.get_unread_msgs()
1741 for result
in results
:
1744 if gajim
.contacts
.get_first_contact_from_jid(account
, jid
) and not \
1746 # We have this jid in our contacts list
1747 # XXX unread messages should probably have their session saved
1749 session
= gajim
.connections
[account
].make_new_session(jid
)
1751 tim
= time
.localtime(float(result
[2]))
1752 session
.roster_message(jid
, result
[1], tim
, msg_type
='chat',
1754 gajim
.logger
.set_shown_unread_msgs(result
[0])
1756 elif (time
.time() - result
[2]) > 2592000:
1757 # ok, here we see that we have a message in unread messages
1758 # table that is older than a month. It is probably from someone
1759 # not in our roster for accounts we usually launch, so we will
1760 # delete this id from unread message tables.
1761 gajim
.logger
.set_read_messages([result
[0]])
1763 def fill_contacts_and_groups_dicts(self
, array
, account
):
1765 Fill gajim.contacts and gajim.groups
1767 # FIXME: This function needs to be splitted
1768 # Most of the logic SHOULD NOT be done at GUI level
1769 if account
not in gajim
.contacts
.get_accounts():
1770 gajim
.contacts
.add_account(account
)
1771 if not account
in self
._iters
:
1772 self
._iters
[account
] = {'account': None, 'groups': {},
1774 if account
not in gajim
.groups
:
1775 gajim
.groups
[account
] = {}
1776 if gajim
.config
.get('show_self_contact') == 'always':
1777 self_jid
= gajim
.get_jid_from_account(account
)
1778 if gajim
.connections
[account
].server_resource
:
1779 self_jid
+= '/' + gajim
.connections
[account
].server_resource
1780 array
[self_jid
] = {'name': gajim
.nicks
[account
],
1781 'groups': ['self_contact'], 'subscription': 'both',
1784 for jid
in array
.keys():
1785 # Remove the contact in roster. It might has changed
1786 self
.remove_contact(jid
, account
, force
=True)
1787 # Remove old Contact instances
1788 gajim
.contacts
.remove_jid(account
, jid
, remove_meta
=False)
1789 jids
= jid
.split('/')
1795 resource
= '/'.join(jids
[1:])
1797 name
= array
[jid
]['name'] or ''
1798 show
= 'offline' # show is offline by default
1799 status
= '' # no status message by default
1802 attached_keys
= gajim
.config
.get_per('accounts', account
,
1803 'attached_gpg_keys').split()
1804 if jid
in attached_keys
:
1805 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
1807 if gajim
.jid_is_transport(jid
):
1808 array
[jid
]['groups'] = [_('Transports')]
1810 contact1
= gajim
.contacts
.create_contact(jid
=ji
, account
=account
,
1811 name
=name
, groups
=array
[jid
]['groups'], show
=show
,
1812 status
=status
, sub
=array
[jid
]['subscription'],
1813 ask
=array
[jid
]['ask'], resource
=resource
, keyID
=keyID
)
1814 gajim
.contacts
.add_contact(account
, contact1
)
1816 if gajim
.config
.get('ask_avatars_on_startup'):
1817 pixbuf
= gtkgui_helpers
.get_avatar_pixbuf_from_cache(ji
)
1819 transport
= gajim
.get_transport_name_from_jid(contact1
.jid
)
1820 if not transport
or gajim
.jid_is_transport(contact1
.jid
):
1821 jid_with_resource
= contact1
.jid
1822 if contact1
.resource
:
1823 jid_with_resource
+= '/' + contact1
.resource
1824 gajim
.connections
[account
].request_vcard(
1827 host
= gajim
.get_server_from_jid(contact1
.jid
)
1828 if host
not in gajim
.transport_avatar
[account
]:
1829 gajim
.transport_avatar
[account
][host
] = \
1832 gajim
.transport_avatar
[account
][host
].append(
1835 # If we already have chat windows opened, update them with new
1837 chat_control
= gajim
.interface
.msg_win_mgr
.get_control(ji
, account
)
1839 chat_control
.contact
= contact1
1841 def connected_rooms(self
, account
):
1842 if account
in gajim
.gc_connected
[account
].values():
1846 def on_event_removed(self
, event_list
):
1848 Remove contacts on last events removed
1850 Only performed if removal was requested before but the contact still had
1853 contact_list
= ((event
.jid
.split('/')[0], event
.account
) for event
in \
1856 for jid
, account
in contact_list
:
1857 self
.draw_contact(jid
, account
)
1858 # Remove contacts in roster if removal was requested
1859 key
= (jid
, account
)
1860 if key
in self
.contacts_to_be_removed
.keys():
1861 backend
= self
.contacts_to_be_removed
[key
]['backend']
1862 del self
.contacts_to_be_removed
[key
]
1863 # Remove contact will delay removal if there are more events
1865 self
.remove_contact(jid
, account
, backend
=backend
)
1868 def open_event(self
, account
, jid
, event
):
1870 If an event was handled, return True, else return False
1872 data
= event
.parameters
1873 ft
= gajim
.interface
.instances
['file_transfers']
1874 event
= gajim
.events
.get_first_event(account
, jid
, event
.type_
)
1875 if event
.type_
== 'normal':
1876 dialogs
.SingleMessageWindow(account
, jid
,
1877 action
='receive', from_whom
=jid
, subject
=data
[1],
1878 message
=data
[0], resource
=data
[5], session
=data
[8],
1880 gajim
.events
.remove_events(account
, jid
, event
)
1882 elif event
.type_
== 'file-request':
1883 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
1885 ft
.show_file_request(account
, contact
, data
)
1886 gajim
.events
.remove_events(account
, jid
, event
)
1888 elif event
.type_
in ('file-request-error', 'file-send-error'):
1889 ft
.show_send_error(data
)
1890 gajim
.events
.remove_events(account
, jid
, event
)
1892 elif event
.type_
in ('file-error', 'file-stopped'):
1894 if data
['error'] == -1:
1895 msg_err
= _('Remote contact stopped transfer')
1896 elif data
['error'] == -6:
1897 msg_err
= _('Error opening file')
1898 ft
.show_stopped(jid
, data
, error_msg
=msg_err
)
1899 gajim
.events
.remove_events(account
, jid
, event
)
1901 elif event
.type_
== 'file-completed':
1902 ft
.show_completed(jid
, data
)
1903 gajim
.events
.remove_events(account
, jid
, event
)
1905 elif event
.type_
== 'gc-invitation':
1906 dialogs
.InvitationReceivedDialog(account
, data
[0], jid
, data
[2],
1908 gajim
.events
.remove_events(account
, jid
, event
)
1910 elif event
.type_
== 'subscription_request':
1911 dialogs
.SubscriptionRequestWindow(jid
, data
[0], account
, data
[1])
1912 gajim
.events
.remove_events(account
, jid
, event
)
1914 elif event
.type_
== 'unsubscribed':
1915 gajim
.interface
.show_unsubscribed_dialog(account
, data
)
1916 gajim
.events
.remove_events(account
, jid
, event
)
1918 elif event
.type_
== 'jingle-incoming':
1919 peerjid
, sid
, content_types
= data
1920 dialogs
.VoIPCallReceivedDialog(account
, peerjid
, sid
, content_types
)
1921 gajim
.events
.remove_events(account
, jid
, event
)
1925 ################################################################################
1926 ### This and that... random.
1927 ################################################################################
1929 def show_roster_vbox(self
, active
):
1930 vb
= self
.xml
.get_object('roster_vbox2')
1932 vb
.set_no_show_all(False)
1936 vb
.set_no_show_all(True)
1938 def show_tooltip(self
, contact
):
1939 pointer
= self
.tree
.get_pointer()
1940 props
= self
.tree
.get_path_at_pos(pointer
[0], pointer
[1])
1941 # check if the current pointer is at the same path
1942 # as it was before setting the timeout
1943 if props
and self
.tooltip
.id == props
[0]:
1944 # bounding rectangle of coordinates for the cell within the treeview
1945 rect
= self
.tree
.get_cell_area(props
[0], props
[1])
1947 # position of the treeview on the screen
1948 position
= self
.tree
.window
.get_origin()
1949 self
.tooltip
.show_tooltip(contact
, rect
.height
, position
[1] + \
1952 self
.tooltip
.hide_tooltip()
1955 def authorize(self
, widget
, jid
, account
):
1957 Authorize a contact (by re-sending auth menuitem)
1959 gajim
.connections
[account
].send_authorization(jid
)
1960 dialogs
.InformationDialog(_('Authorization has been sent'),
1961 _('Now "%s" will know your status.') %jid
)
1963 def req_sub(self
, widget
, jid
, txt
, account
, groups
=None, nickname
=None,
1966 Request subscription to a contact
1968 groups_list
= groups
or []
1969 gajim
.connections
[account
].request_subscription(jid
, txt
, nickname
,
1970 groups_list
, auto_auth
, gajim
.nicks
[account
])
1971 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
1974 attached_keys
= gajim
.config
.get_per('accounts', account
,
1975 'attached_gpg_keys').split()
1976 if jid
in attached_keys
:
1977 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
1978 contact
= gajim
.contacts
.create_contact(jid
=jid
, account
=account
,
1979 name
=nickname
, groups
=groups_list
, show
='requested', status
='',
1980 ask
='none', sub
='subscribe', keyID
=keyID
)
1981 gajim
.contacts
.add_contact(account
, contact
)
1983 if not _('Not in Roster') in contact
.get_shown_groups():
1984 dialogs
.InformationDialog(_('Subscription request has been '
1985 'sent'), _('If "%s" accepts this request you will know his '
1986 'or her status.') % jid
)
1988 self
.remove_contact(contact
.jid
, account
, force
=True)
1989 contact
.groups
= groups_list
1991 contact
.name
= nickname
1992 self
.add_contact(jid
, account
)
1994 def revoke_auth(self
, widget
, jid
, account
):
1996 Revoke a contact's authorization
1998 gajim
.connections
[account
].refuse_authorization(jid
)
1999 dialogs
.InformationDialog(_('Authorization has been removed'),
2000 _('Now "%s" will always see you as offline.') %jid
)
2002 def set_state(self
, account
, state
):
2003 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
2005 self
.model
[child_iterA
][0] = \
2006 gajim
.interface
.jabber_state_images
['16'][state
]
2007 if gajim
.interface
.systray_enabled
:
2008 gajim
.interface
.systray
.change_status(state
)
2010 def set_connecting_state(self
, account
):
2011 self
.set_state(account
, 'connecting')
2013 def send_status(self
, account
, status
, txt
, auto
=False, to
=None):
2014 if status
!= 'offline':
2016 if status
== gajim
.connections
[account
].get_status() and \
2017 txt
== gajim
.connections
[account
].status
:
2019 gajim
.config
.set_per('accounts', account
, 'last_status', status
)
2020 gajim
.config
.set_per('accounts', account
, 'last_status_msg',
2021 helpers
.to_one_line(txt
))
2022 if gajim
.connections
[account
].connected
< 2:
2023 self
.set_connecting_state(account
)
2025 keyid
= gajim
.config
.get_per('accounts', account
, 'keyid')
2026 if keyid
and not gajim
.connections
[account
].gpg
:
2027 dialogs
.WarningDialog(_('GPG is not usable'),
2028 _('You will be connected to %s without OpenPGP.') % \
2031 self
.send_status_continue(account
, status
, txt
, auto
, to
)
2033 def send_pep(self
, account
, pep_dict
):
2034 connection
= gajim
.connections
[account
]
2036 if 'activity' in pep_dict
:
2037 activity
= pep_dict
['activity']
2038 subactivity
= pep_dict
.get('subactivity', None)
2039 activity_text
= pep_dict
.get('activity_text', None)
2040 connection
.send_activity(activity
, subactivity
, activity_text
)
2042 connection
.retract_activity()
2044 if 'mood' in pep_dict
:
2045 mood
= pep_dict
['mood']
2046 mood_text
= pep_dict
.get('mood_text', None)
2047 connection
.send_mood(mood
, mood_text
)
2049 connection
.retract_mood()
2051 def delete_pep(self
, jid
, account
):
2052 if jid
== gajim
.get_jid_from_account(account
):
2053 gajim
.connections
[account
].pep
= {}
2054 self
.draw_account(account
)
2056 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
2059 self
.draw_all_pep_types(jid
, account
)
2060 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(jid
, account
)
2062 ctrl
.update_all_pep_types()
2064 def send_status_continue(self
, account
, status
, txt
, auto
, to
):
2065 if gajim
.account_is_connected(account
) and not to
:
2066 if status
== 'online' and gajim
.interface
.sleeper
.getState() != \
2067 common
.sleepy
.STATE_UNKNOWN
:
2068 gajim
.sleeper_state
[account
] = 'online'
2069 elif gajim
.sleeper_state
[account
] not in ('autoaway', 'autoxa') or \
2070 status
== 'offline':
2071 gajim
.sleeper_state
[account
] = 'off'
2074 gajim
.connections
[account
].send_custom_status(status
, txt
, to
)
2076 if status
in ('invisible', 'offline'):
2077 self
.delete_pep(gajim
.get_jid_from_account(account
), account
)
2078 was_invisible
= gajim
.connections
[account
].connected
== \
2079 gajim
.SHOW_LIST
.index('invisible')
2080 gajim
.connections
[account
].change_status(status
, txt
, auto
)
2082 if account
in gajim
.interface
.status_sent_to_users
:
2083 gajim
.interface
.status_sent_to_users
[account
] = {}
2084 if account
in gajim
.interface
.status_sent_to_groups
:
2085 gajim
.interface
.status_sent_to_groups
[account
] = {}
2086 for gc_control
in gajim
.interface
.msg_win_mgr
.get_controls(
2087 message_control
.TYPE_GC
) + \
2088 gajim
.interface
.minimized_controls
[account
].values():
2089 if gc_control
.account
== account
:
2090 if gajim
.gc_connected
[account
][gc_control
.room_jid
]:
2091 gajim
.connections
[account
].send_gc_status(
2092 gc_control
.nick
, gc_control
.room_jid
, status
, txt
)
2094 # for some reason, we are not connected to the room even
2095 # if tab is opened, send initial join_gc()
2096 gajim
.connections
[account
].join_gc(gc_control
.nick
,
2097 gc_control
.room_jid
, None)
2098 if was_invisible
and status
!= 'offline':
2099 # We come back from invisible, join bookmarks
2100 gajim
.interface
.auto_join_bookmarks(account
)
2103 def chg_contact_status(self
, contact
, show
, status
, account
):
2105 When a contact changes his or her status
2107 contact_instances
= gajim
.contacts
.get_contacts(account
, contact
.jid
)
2109 contact
.status
= status
2110 # name is to show in conversation window
2111 name
= contact
.get_shown_name()
2112 fjid
= contact
.get_full_jid()
2114 # The contact has several resources
2115 if len(contact_instances
) > 1:
2116 if contact
.resource
!= '':
2117 name
+= '/' + contact
.resource
2119 # Remove resource when going offline
2120 if show
in ('offline', 'error') and \
2121 not self
.contact_has_pending_roster_events(contact
, account
):
2122 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(fjid
, account
)
2125 ctrl
.parent_win
.redraw_tab(ctrl
)
2126 # keep the contact around, since it's
2127 # already attached to the control
2129 gajim
.contacts
.remove_contact(account
, contact
)
2131 elif contact
.jid
== gajim
.get_jid_from_account(account
) and \
2132 show
in ('offline', 'error'):
2133 if gajim
.config
.get('show_self_contact') != 'never':
2134 # SelfContact went offline. Remove him when last pending
2136 self
.remove_contact(contact
.jid
, account
, backend
=True)
2138 uf_show
= helpers
.get_uf_show(show
)
2140 # print status in chat window and update status/GPG image
2141 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(contact
.jid
, account
)
2142 if ctrl
and ctrl
.type_id
!= message_control
.TYPE_GC
:
2143 ctrl
.contact
= gajim
.contacts
.get_contact_with_highest_priority(
2144 account
, contact
.jid
)
2145 ctrl
.update_status_display(name
, uf_show
, status
)
2147 if contact
.resource
:
2148 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(fjid
, account
)
2150 ctrl
.update_status_display(name
, uf_show
, status
)
2152 # Delete pep if needed
2153 keep_pep
= any(c
.show
not in ('error', 'offline') for c
in
2155 if not keep_pep
and contact
.jid
!= gajim
.get_jid_from_account(account
) \
2156 and not contact
.is_groupchat():
2157 self
.delete_pep(contact
.jid
, account
)
2159 # Redraw everything and select the sender
2160 self
.adjust_and_draw_contact_context(contact
.jid
, account
)
2163 def on_status_changed(self
, account
, show
):
2165 The core tells us that our status has changed
2167 if account
not in gajim
.contacts
.get_accounts():
2169 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
2170 if gajim
.config
.get('show_self_contact') == 'always':
2171 self_resource
= gajim
.connections
[account
].server_resource
2172 self_contact
= gajim
.contacts
.get_contact(account
,
2173 gajim
.get_jid_from_account(account
), resource
=self_resource
)
2175 status
= gajim
.connections
[account
].status
2176 self
.chg_contact_status(self_contact
, show
, status
, account
)
2177 self
.set_account_status_icon(account
)
2178 if show
== 'offline':
2179 if self
.quit_on_next_offline
> -1:
2180 # we want to quit, we are waiting for all accounts to be offline
2181 self
.quit_on_next_offline
-= 1
2182 if self
.quit_on_next_offline
< 1:
2183 # all accounts offline, quit
2184 self
.quit_gtkgui_interface()
2186 # No need to redraw contacts if we're quitting
2188 self
.model
[child_iterA
][C_AVATAR_PIXBUF
] = None
2189 if account
in gajim
.con_types
:
2190 gajim
.con_types
[account
] = None
2191 for jid
in gajim
.contacts
.get_jid_list(account
):
2192 lcontact
= gajim
.contacts
.get_contacts(account
, jid
)
2193 ctrl
= gajim
.interface
.msg_win_mgr
.get_gc_control(jid
,
2195 for contact
in [c
for c
in lcontact
if (
2196 (c
.show
!= 'offline' or c
.is_transport()) and not ctrl
)]:
2197 self
.chg_contact_status(contact
, 'offline', '', account
)
2198 self
.set_actions_menu_needs_rebuild()
2199 self
.update_status_combobox()
2201 def get_status_message(self
, show
, on_response
, show_pep
=True,
2204 Get the status message by:
2206 1/ looking in default status message
2207 2/ asking to user if needed depending on ask_on(ff)line_status and
2209 show_pep can be False to hide pep things from status message or True
2211 empty_pep
= {'activity': '', 'subactivity': '', 'activity_text': '',
2212 'mood': '', 'mood_text': ''}
2213 if show
in gajim
.config
.get_per('defaultstatusmsg'):
2214 if gajim
.config
.get_per('defaultstatusmsg', show
, 'enabled'):
2215 msg
= gajim
.config
.get_per('defaultstatusmsg', show
, 'message')
2216 msg
= helpers
.from_one_line(msg
)
2217 on_response(msg
, empty_pep
)
2219 if not always_ask
and ((show
== 'online' and not gajim
.config
.get(
2220 'ask_online_status')) or (show
in ('offline', 'invisible') and not \
2221 gajim
.config
.get('ask_offline_status'))):
2222 on_response('', empty_pep
)
2225 dlg
= dialogs
.ChangeStatusMessageDialog(on_response
, show
, show_pep
)
2226 dlg
.dialog
.present() # show it on current workspace
2228 def change_status(self
, widget
, account
, status
):
2229 def change(account
, status
):
2230 def on_response(message
, pep_dict
):
2232 # user pressed Cancel to change status message dialog
2234 self
.send_status(account
, status
, message
)
2235 self
.send_pep(account
, pep_dict
)
2236 self
.get_status_message(status
, on_response
)
2238 if status
== 'invisible' and self
.connected_rooms(account
):
2239 dialogs
.ConfirmationDialog(
2240 _('You are participating in one or more group chats'),
2241 _('Changing your status to invisible will result in '
2242 'disconnection from those group chats. Are you sure you want '
2243 'to go invisible?'), on_response_ok
= (change
, account
, status
))
2245 change(account
, status
)
2247 def update_status_combobox(self
):
2248 # table to change index in connection.connected to index in combobox
2249 table
= {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
2250 'xa':3, 'dnd':4, 'invisible':5}
2252 # we check if there are more options in the combobox that it should
2253 # if yes, we remove the first ones
2254 while len(self
.status_combobox
.get_model()) > len(table
)+2:
2255 self
.status_combobox
.remove_text(0)
2257 show
= helpers
.get_global_show()
2258 # temporarily block signal in order not to send status that we show
2260 self
.combobox_callback_active
= False
2261 if helpers
.statuses_unified():
2262 self
.status_combobox
.set_active(table
[show
])
2264 uf_show
= helpers
.get_uf_show(show
)
2265 liststore
= self
.status_combobox
.get_model()
2266 liststore
.prepend(['SEPARATOR', None, '', True])
2267 status_combobox_text
= uf_show
+ ' (' + _("desync'ed") +')'
2268 liststore
.prepend([status_combobox_text
,
2269 gajim
.interface
.jabber_state_images
['16'][show
], show
, False])
2270 self
.status_combobox
.set_active(0)
2271 gajim
.interface
.change_awn_icon_status(show
)
2272 self
.combobox_callback_active
= True
2273 if gajim
.interface
.systray_enabled
:
2274 gajim
.interface
.systray
.change_status(show
)
2276 def get_show(self
, lcontact
):
2277 prio
= lcontact
[0].priority
2278 show
= lcontact
[0].show
2280 if u
.priority
> prio
:
2285 def on_message_window_delete(self
, win_mgr
, msg_win
):
2286 if gajim
.config
.get('one_message_window') == 'always_with_roster':
2287 self
.show_roster_vbox(True)
2288 gtkgui_helpers
.resize_window(self
.window
,
2289 gajim
.config
.get('roster_width'),
2290 gajim
.config
.get('roster_height'))
2292 def close_all_from_dict(self
, dic
):
2294 Close all the windows in the given dictionary
2296 for w
in dic
.values():
2297 if isinstance(w
, dict):
2298 self
.close_all_from_dict(w
)
2302 def close_all(self
, account
, force
=False):
2304 Close all the windows from an account. If force is True, do not ask
2305 confirmation before closing chat/gc windows
2307 if account
in gajim
.interface
.instances
:
2308 self
.close_all_from_dict(gajim
.interface
.instances
[account
])
2309 for ctrl
in gajim
.interface
.msg_win_mgr
.get_controls(acct
=account
):
2310 ctrl
.parent_win
.remove_tab(ctrl
, ctrl
.parent_win
.CLOSE_CLOSE_BUTTON
,
2313 def on_roster_window_delete_event(self
, widget
, event
):
2315 Main window X button was clicked
2317 if gajim
.interface
.systray_enabled
and not gajim
.config
.get(
2318 'quit_on_roster_x_button') and gajim
.config
.get('trayicon') != \
2320 self
.tooltip
.hide_tooltip()
2322 elif gajim
.config
.get('quit_on_roster_x_button'):
2323 self
.on_quit_request()
2327 gajim
.config
.set('quit_on_roster_x_button', True)
2328 self
.on_quit_request()
2329 dialogs
.ConfirmationDialogCheck(_('Really quit Gajim?'),
2330 _('Are you sure you want to quit Gajim?'),
2331 _('Always close Gajim'), on_response_ok
=on_ok
)
2332 return True # do NOT destroy the window
2334 def prepare_quit(self
):
2335 msgwin_width_adjust
= 0
2337 # in case show_roster_on_start is False and roster is never shown
2338 # window.window is None
2339 if self
.window
.window
is not None:
2340 if gajim
.config
.get('save-roster-position'):
2341 x
, y
= self
.window
.window
.get_root_origin()
2342 gajim
.config
.set('roster_x-position', x
)
2343 gajim
.config
.set('roster_y-position', y
)
2344 width
, height
= self
.window
.get_size()
2345 # For the width use the size of the vbox containing the tree and
2346 # status combo, this will cancel out any hpaned width
2347 width
= self
.xml
.get_object('roster_vbox2').allocation
.width
2348 gajim
.config
.set('roster_width', width
)
2349 gajim
.config
.set('roster_height', height
)
2350 if not self
.xml
.get_object('roster_vbox2').get_property('visible'):
2351 # The roster vbox is hidden, so the message window is larger
2352 # then we want to save (i.e. the window will grow every startup)
2354 msgwin_width_adjust
= -1 * width
2355 gajim
.config
.set('last_roster_visible',
2356 self
.window
.get_property('visible'))
2357 gajim
.interface
.msg_win_mgr
.shutdown(msgwin_width_adjust
)
2359 gajim
.config
.set('collapsed_rows', '\t'.join(self
.collapsed_rows
))
2360 gajim
.interface
.save_config()
2361 for account
in gajim
.connections
:
2362 gajim
.connections
[account
].quit(True)
2363 self
.close_all(account
)
2364 if gajim
.interface
.systray_enabled
:
2365 gajim
.interface
.hide_systray()
2367 def quit_gtkgui_interface(self
):
2369 When we quit the gtk interface - exit gtk
2374 def on_quit_request(self
, widget
=None):
2376 User wants to quit. Check if he should be warned about messages pending.
2377 Terminate all sessions and send offline to all connected account. We do
2378 NOT really quit gajim here
2380 accounts
= gajim
.connections
.keys()
2382 for acct
in accounts
:
2383 if gajim
.connections
[acct
].connected
:
2387 def on_continue3(message
, pep_dict
):
2388 self
.quit_on_next_offline
= 0
2389 accounts_to_disconnect
= []
2390 for acct
in accounts
:
2391 if gajim
.connections
[acct
].connected
> 1:
2392 self
.quit_on_next_offline
+= 1
2393 accounts_to_disconnect
.append(acct
)
2395 for acct
in accounts_to_disconnect
:
2396 self
.send_status(acct
, 'offline', message
)
2397 self
.send_pep(acct
, pep_dict
)
2399 if not self
.quit_on_next_offline
:
2400 self
.quit_gtkgui_interface()
2402 def on_continue2(message
, pep_dict
):
2403 # check if there is an active file transfer
2404 from common
.protocol
.bytestream
import (is_transfer_active
)
2405 files_props
= gajim
.interface
.instances
['file_transfers'].\
2407 transfer_active
= False
2408 for x
in files_props
:
2409 for y
in files_props
[x
]:
2410 if is_transfer_active(files_props
[x
][y
]):
2411 transfer_active
= True
2415 dialogs
.ConfirmationDialog(_('You have running file transfers'),
2416 _('If you quit now, the file(s) being transferred will '
2417 'be stopped. Do you still want to quit?'),
2418 on_response_ok
=(on_continue3
, message
, pep_dict
))
2420 on_continue3(message
, pep_dict
)
2422 def on_continue(message
, pep_dict
):
2424 # user pressed Cancel to change status message dialog
2426 # check if we have unread messages
2427 unread
= gajim
.events
.get_nb_events()
2428 if not gajim
.config
.get('notify_on_all_muc_messages'):
2429 unread_not_to_notify
= gajim
.events
.get_nb_events(
2431 unread
-= unread_not_to_notify
2433 # check if we have recent messages
2435 for win
in gajim
.interface
.msg_win_mgr
.windows():
2436 for ctrl
in win
.controls():
2437 fjid
= ctrl
.get_full_jid()
2438 if fjid
in gajim
.last_message_time
[ctrl
.account
]:
2439 if time
.time() - gajim
.last_message_time
[ctrl
.account
][
2446 if unread
or recent
:
2447 dialogs
.ConfirmationDialog(_('You have unread messages'),
2448 _('Messages will only be available for reading them later '
2449 'if you have history enabled and contact is in your '
2450 'roster.'), on_response_ok
=(on_continue2
,
2453 on_continue2(message
, pep_dict
)
2456 self
.get_status_message('offline', on_continue
, show_pep
=False)
2458 on_continue('', None)
2460 def _nec_presence_received(self
, obj
):
2461 account
= obj
.conn
.name
2464 if obj
.need_add_in_roster
:
2465 self
.add_contact(jid
, account
)
2467 jid_list
= gajim
.contacts
.get_jid_list(account
)
2468 if jid
in jid_list
or jid
== gajim
.get_jid_from_account(account
):
2469 if not gajim
.jid_is_transport(jid
) and len(obj
.contact_list
) == 1:
2470 if obj
.old_show
== 0 and obj
.new_show
> 1:
2471 gobject
.timeout_add_seconds(5, self
.remove_newly_added
, jid
,
2473 elif obj
.old_show
> 1 and obj
.new_show
== 0 and \
2474 obj
.conn
.connected
> 1:
2475 gobject
.timeout_add_seconds(5, self
.remove_to_be_removed
,
2479 self
.draw_contact(jid
, account
)
2481 if gajim
.jid_is_transport(jid
) and jid
in jid_list
:
2482 # It must be an agent
2483 # Update existing iter and group counting
2484 self
.draw_contact(jid
, account
)
2485 self
.draw_group(_('Transports'), account
)
2486 if obj
.new_show
> 1 and jid
in gajim
.transport_avatar
[account
]:
2487 # transport just signed in.
2489 for jid_
in gajim
.transport_avatar
[account
][jid
]:
2490 obj
.conn
.request_vcard(jid_
)
2493 self
.chg_contact_status(obj
.contact
, obj
.show
, obj
.status
, account
)
2495 def _nec_gc_presence_received(self
, obj
):
2496 account
= obj
.conn
.name
2497 if obj
.room_jid
in gajim
.interface
.minimized_controls
[account
]:
2498 gc_ctrl
= gajim
.interface
.minimized_controls
[account
][obj
.room_jid
]
2502 if obj
.nick
== gc_ctrl
.nick
:
2503 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
2506 contact
.show
= obj
.show
2507 self
.draw_contact(obj
.room_jid
, account
)
2508 self
.draw_group(_('Groupchats'), account
)
2510 def _nec_roster_received(self
, obj
):
2511 if obj
.received_from_server
:
2512 self
.fill_contacts_and_groups_dicts(obj
.roster
, obj
.conn
.name
)
2513 self
.add_account_contacts(obj
.conn
.name
)
2514 self
.fire_up_unread_messages_events(obj
.conn
.name
)
2516 gobject
.idle_add(self
.refilter_shown_roster_items
)
2518 def _nec_anonymous_auth(self
, obj
):
2520 This event is raised when our JID changed (most probably because we use
2521 anonymous account. We update contact and roster entry in this case
2523 self
.rename_self_contact(obj
.old_jid
, obj
.new_jid
, obj
.conn
.name
)
2525 def _nec_our_show(self
, obj
):
2526 model
= self
.status_combobox
.get_model()
2527 if obj
.show
== 'offline':
2528 # sensitivity for this menuitem
2529 if gajim
.get_number_of_connected_accounts() == 0:
2530 model
[self
.status_message_menuitem_iter
][3] = False
2532 # sensitivity for this menuitem
2533 model
[self
.status_message_menuitem_iter
][3] = True
2534 self
.on_status_changed(obj
.conn
.name
, obj
.show
)
2536 def _nec_connection_type(self
, obj
):
2537 self
.draw_account(obj
.conn
.name
)
2539 def _nec_agent_removed(self
, obj
):
2540 for jid
in obj
.jid_list
:
2541 self
.remove_contact(jid
, obj
.conn
.name
, backend
=True)
2543 def _nec_pep_received(self
, obj
):
2544 if obj
.jid
== common
.gajim
.get_jid_from_account(obj
.conn
.name
):
2545 self
.draw_account(obj
.conn
.name
)
2547 if obj
.pep_type
== 'nickname':
2548 self
.draw_contact(obj
.jid
, obj
.conn
.name
)
2550 self
.draw_pep(obj
.jid
, obj
.conn
.name
, obj
.pep_type
)
2552 def _nec_vcard_received(self
, obj
):
2554 # it's a muc occupant vcard
2556 self
.draw_avatar(obj
.jid
, obj
.conn
.name
)
2558 def _nec_gc_subject_received(self
, obj
):
2559 contact
= gajim
.contacts
.get_contact_with_highest_priority(
2560 obj
.conn
.name
, obj
.room_jid
)
2562 contact
.status
= obj
.subject
2563 self
.draw_contact(obj
.room_jid
, obj
.conn
.name
)
2565 def _nec_metacontacts_received(self
, obj
):
2566 self
.redraw_metacontacts(obj
.conn
.name
)
2568 def _nec_signed_in(self
, obj
):
2569 self
.set_actions_menu_needs_rebuild()
2570 self
.draw_account(obj
.conn
.name
)
2572 def _nec_decrypted_message_received(self
, obj
):
2573 if not obj
.msgtxt
: # empty message text
2575 if obj
.mtype
not in ('norml', 'chat'):
2577 if obj
.session
.control
:
2579 if obj
.mtype
== 'error':
2582 obj
.session
.control
.print_conversation(obj
.msgtxt
, typ
,
2583 tim
=obj
.timestamp
, encrypted
=obj
.encrypted
, subject
=obj
.subject
,
2584 xhtml
=obj
.xhtml
, displaymarking
=obj
.displaymarking
)
2586 gajim
.logger
.set_read_messages([obj
.msg_id
])
2588 if not obj
.session
.control
:
2589 contact
= gajim
.contacts
.get_contact(obj
.conn
.name
, obj
.jid
,
2590 obj
.resource_for_chat
)
2591 obj
.session
.control
= gajim
.interface
.new_chat(contact
,
2592 obj
.conn
.name
, resource
=obj
.resource_for_chat
,
2593 session
=obj
.session
)
2594 if len(gajim
.events
.get_events(obj
.conn
.name
, obj
.fjid
)):
2595 obj
.session
.control
.read_queue()
2597 if obj
.show_in_roster
:
2598 self
.draw_contact(obj
.jid
, obj
.conn
.name
)
2599 self
.show_title() # we show the * or [n]
2600 # Select the big brother contact in roster, it's visible because it
2602 family
= gajim
.contacts
.get_metacontacts_family(obj
.conn
.name
,
2605 nearby_family
, bb_jid
, bb_account
= \
2606 gajim
.contacts
.get_nearby_family_and_big_brother(family
,
2609 bb_jid
, bb_account
= obj
.jid
, obj
.conn
.name
2610 self
.select_contact(bb_jid
, bb_account
)
2612 ################################################################################
2613 ### Menu and GUI callbacks
2614 ### FIXME: order callbacks in itself...
2615 ################################################################################
2617 def on_actions_menuitem_activate(self
, widget
):
2620 def on_edit_menuitem_activate(self
, widget
):
2622 Need to call make_menu to build profile, avatar item
2626 def on_bookmark_menuitem_activate(self
, widget
, account
, bookmark
):
2627 gajim
.interface
.join_gc_room(account
, bookmark
['jid'], bookmark
['nick'],
2628 bookmark
['password'])
2630 def on_send_server_message_menuitem_activate(self
, widget
, account
):
2631 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2632 server
+= '/announce/online'
2633 dialogs
.SingleMessageWindow(account
, server
, 'send')
2635 def on_xml_console_menuitem_activate(self
, widget
, account
):
2636 if 'xml_console' in gajim
.interface
.instances
[account
]:
2637 gajim
.interface
.instances
[account
]['xml_console'].window
.present()
2639 gajim
.interface
.instances
[account
]['xml_console'] = \
2640 dialogs
.XMLConsoleWindow(account
)
2642 def on_archiving_preferences_menuitem_activate(self
, widget
, account
):
2643 if 'archiving_preferences' in gajim
.interface
.instances
[account
]:
2644 gajim
.interface
.instances
[account
]['archiving_preferences'].window
.\
2647 gajim
.interface
.instances
[account
]['archiving_preferences'] = \
2648 dialogs
.ArchivingPreferencesWindow(account
)
2650 def on_privacy_lists_menuitem_activate(self
, widget
, account
):
2651 if 'privacy_lists' in gajim
.interface
.instances
[account
]:
2652 gajim
.interface
.instances
[account
]['privacy_lists'].window
.present()
2654 gajim
.interface
.instances
[account
]['privacy_lists'] = \
2655 dialogs
.PrivacyListsWindow(account
)
2657 def on_set_motd_menuitem_activate(self
, widget
, account
):
2658 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2659 server
+= '/announce/motd'
2660 dialogs
.SingleMessageWindow(account
, server
, 'send')
2662 def on_update_motd_menuitem_activate(self
, widget
, account
):
2663 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2664 server
+= '/announce/motd/update'
2665 dialogs
.SingleMessageWindow(account
, server
, 'send')
2667 def on_delete_motd_menuitem_activate(self
, widget
, account
):
2668 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2669 server
+= '/announce/motd/delete'
2670 gajim
.connections
[account
].send_motd(server
)
2672 def on_history_manager_menuitem_activate(self
, widget
):
2674 if os
.path
.exists('history_manager.exe'): # user is running stable
2675 helpers
.exec_command('history_manager.exe')
2676 else: # user is running svn
2677 helpers
.exec_command('%s history_manager.py' % sys
.executable
)
2679 helpers
.exec_command('%s history_manager.py' % sys
.executable
)
2681 def on_info(self
, widget
, contact
, account
):
2683 Call vcard_information_window class to display contact's information
2685 if gajim
.connections
[account
].is_zeroconf
:
2686 self
.on_info_zeroconf(widget
, contact
, account
)
2689 info
= gajim
.interface
.instances
[account
]['infos']
2690 if contact
.jid
in info
:
2691 info
[contact
.jid
].window
.present()
2693 info
[contact
.jid
] = vcard
.VcardWindow(contact
, account
)
2695 def on_info_zeroconf(self
, widget
, contact
, account
):
2696 info
= gajim
.interface
.instances
[account
]['infos']
2697 if contact
.jid
in info
:
2698 info
[contact
.jid
].window
.present()
2700 contact
= gajim
.contacts
.get_first_contact_from_jid(account
,
2702 if contact
.show
in ('offline', 'error'):
2703 # don't show info on offline contacts
2705 info
[contact
.jid
] = vcard
.ZeroconfVcardWindow(contact
, account
)
2707 def on_roster_treeview_leave_notify_event(self
, widget
, event
):
2708 props
= widget
.get_path_at_pos(int(event
.x
), int(event
.y
))
2709 if self
.tooltip
.timeout
> 0:
2710 if not props
or self
.tooltip
.id == props
[0]:
2711 self
.tooltip
.hide_tooltip()
2713 def on_roster_treeview_motion_notify_event(self
, widget
, event
):
2714 model
= widget
.get_model()
2715 props
= widget
.get_path_at_pos(int(event
.x
), int(event
.y
))
2716 if self
.tooltip
.timeout
> 0:
2717 if not props
or self
.tooltip
.id != props
[0]:
2718 self
.tooltip
.hide_tooltip()
2723 titer
= model
.get_iter(row
)
2725 self
.tooltip
.hide_tooltip()
2727 if model
[titer
][C_TYPE
] in ('contact', 'self_contact'):
2728 # we're on a contact entry in the roster
2729 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
2730 jid
= model
[titer
][C_JID
].decode('utf-8')
2731 if self
.tooltip
.timeout
== 0 or self
.tooltip
.id != props
[0]:
2732 self
.tooltip
.id = row
2733 contacts
= gajim
.contacts
.get_contacts(account
, jid
)
2734 connected_contacts
= []
2736 if c
.show
not in ('offline', 'error'):
2737 connected_contacts
.append(c
)
2738 if not connected_contacts
:
2739 # no connected contacts, show the ofline one
2740 connected_contacts
= contacts
2741 self
.tooltip
.account
= account
2742 self
.tooltip
.timeout
= gobject
.timeout_add(500,
2743 self
.show_tooltip
, connected_contacts
)
2744 elif model
[titer
][C_TYPE
] == 'groupchat':
2745 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
2746 jid
= model
[titer
][C_JID
].decode('utf-8')
2747 if self
.tooltip
.timeout
== 0 or self
.tooltip
.id != props
[0]:
2748 self
.tooltip
.id = row
2749 contact
= gajim
.contacts
.get_contacts(account
, jid
)
2750 self
.tooltip
.account
= account
2751 self
.tooltip
.timeout
= gobject
.timeout_add(500,
2752 self
.show_tooltip
, contact
)
2753 elif model
[titer
][C_TYPE
] == 'account':
2754 # we're on an account entry in the roster
2755 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
2756 if account
== 'all':
2757 if self
.tooltip
.timeout
== 0 or self
.tooltip
.id != props
[0]:
2758 self
.tooltip
.id = row
2759 self
.tooltip
.account
= None
2760 self
.tooltip
.timeout
= gobject
.timeout_add(500,
2761 self
.show_tooltip
, [])
2763 jid
= gajim
.get_jid_from_account(account
)
2765 connection
= gajim
.connections
[account
]
2766 # get our current contact info
2768 nbr_on
, nbr_total
= gajim
.contacts
.get_nb_online_total_contacts(
2769 accounts
= [account
])
2770 account_name
= account
2771 if gajim
.account_is_connected(account
):
2772 account_name
+= ' (%s/%s)' % (repr(nbr_on
), repr(nbr_total
))
2773 contact
= gajim
.contacts
.create_self_contact(jid
=jid
,
2774 account
=account
, name
=account_name
,
2775 show
=connection
.get_status(), status
=connection
.status
,
2776 resource
=connection
.server_resource
,
2777 priority
=connection
.priority
)
2778 if gajim
.connections
[account
].gpg
:
2779 contact
.keyID
= gajim
.config
.get_per('accounts',
2780 connection
.name
, 'keyid')
2781 contacts
.append(contact
)
2782 # if we're online ...
2783 if connection
.connection
:
2784 roster
= connection
.connection
.getRoster()
2785 # in threadless connection when no roster stanza is sent,
2787 if roster
and roster
.getItem(jid
):
2788 resources
= roster
.getResources(jid
)
2789 # ...get the contact info for our other online resources
2790 for resource
in resources
:
2791 # Check if we already have this resource
2793 for contact_
in contacts
:
2794 if contact_
.resource
== resource
:
2799 show
= roster
.getShow(jid
+'/'+resource
)
2802 contact
= gajim
.contacts
.create_self_contact(
2803 jid
=jid
, account
=account
, show
=show
,
2804 status
=roster
.getStatus(jid
+ '/' + resource
),
2805 priority
=roster
.getPriority(
2806 jid
+ '/' + resource
), resource
=resource
)
2807 contacts
.append(contact
)
2808 if self
.tooltip
.timeout
== 0 or self
.tooltip
.id != props
[0]:
2809 self
.tooltip
.id = row
2810 self
.tooltip
.account
= None
2811 self
.tooltip
.timeout
= gobject
.timeout_add(500,
2812 self
.show_tooltip
, contacts
)
2814 def on_agent_logging(self
, widget
, jid
, state
, account
):
2816 When an agent is requested to log in or off
2818 gajim
.connections
[account
].send_agent_status(jid
, state
)
2820 def on_edit_agent(self
, widget
, contact
, account
):
2822 When we want to modify the agent registration
2824 gajim
.connections
[account
].request_register_agent_info(contact
.jid
)
2826 def on_remove_agent(self
, widget
, list_
):
2828 When an agent is requested to be removed. list_ is a list of (contact,
2831 for (contact
, account
) in list_
:
2832 if gajim
.config
.get_per('accounts', account
, 'hostname') == \
2834 # We remove the server contact
2835 # remove it from treeview
2836 gajim
.connections
[account
].unsubscribe(contact
.jid
)
2837 self
.remove_contact(contact
.jid
, account
, backend
=True)
2841 for (contact
, account
) in list_
:
2842 full_jid
= contact
.get_full_jid()
2843 gajim
.connections
[account
].unsubscribe_agent(full_jid
)
2844 # remove transport from treeview
2845 self
.remove_contact(contact
.jid
, account
, backend
=True)
2847 # Check if there are unread events from some contacts
2848 has_unread_events
= False
2849 for (contact
, account
) in list_
:
2850 for jid
in gajim
.events
.get_events(account
):
2851 if jid
.endswith(contact
.jid
):
2852 has_unread_events
= True
2854 if has_unread_events
:
2855 dialogs
.ErrorDialog(_('You have unread messages'),
2856 _('You must read them before removing this transport.'))
2859 pritext
= _('Transport "%s" will be removed') % list_
[0][0].jid
2860 sectext
= _('You will no longer be able to send and receive '
2861 'messages from contacts using this transport.')
2863 pritext
= _('Transports will be removed')
2865 for (contact
, account
) in list_
:
2866 jids
+= '\n ' + contact
.get_shown_name() + ','
2867 jids
= jids
[:-1] + '.'
2868 sectext
= _('You will no longer be able to send and receive '
2869 'messages to contacts from these transports: %s') % jids
2870 dialogs
.ConfirmationDialog(pritext
, sectext
,
2871 on_response_ok
= (remove
, list_
))
2873 def on_block(self
, widget
, list_
, group
=None):
2875 When clicked on the 'block' button in context menu. list_ is a list of
2878 def on_continue(msg
, pep_dict
):
2880 # user pressed Cancel to change status message dialog
2884 for (contact
, account
) in list_
:
2885 if account
not in accounts
:
2886 if not gajim
.connections
[account
].\
2887 privacy_rules_supported
:
2889 accounts
.append(account
)
2890 self
.send_status(account
, 'offline', msg
, to
=contact
.jid
)
2891 new_rule
= {'order': u
'1', 'type': u
'jid',
2892 'action': u
'deny', 'value' : contact
.jid
,
2893 'child': [u
'message', u
'iq', u
'presence-out']}
2894 gajim
.connections
[account
].blocked_list
.append(new_rule
)
2895 # needed for draw_contact:
2896 gajim
.connections
[account
].blocked_contacts
.append(
2898 self
.draw_contact(contact
.jid
, account
)
2900 for (contact
, account
) in list_
:
2901 if account
not in accounts
:
2902 if not gajim
.connections
[account
].\
2903 privacy_rules_supported
:
2905 accounts
.append(account
)
2906 # needed for draw_group:
2907 gajim
.connections
[account
].blocked_groups
.append(group
)
2908 self
.draw_group(group
, account
)
2909 self
.send_status(account
, 'offline', msg
, to
=contact
.jid
)
2910 self
.draw_contact(contact
.jid
, account
)
2911 new_rule
= {'order': u
'1', 'type': u
'group', 'action': u
'deny',
2912 'value' : group
, 'child': [u
'message', u
'iq',
2914 # account is the same for all when we block a group
2915 gajim
.connections
[list_
[0][1]].blocked_list
.append(new_rule
)
2916 for account
in accounts
:
2917 connection
= gajim
.connections
[account
]
2918 connection
.set_privacy_list('block', connection
.blocked_list
)
2919 if len(connection
.blocked_list
) == 1:
2920 connection
.set_active_list('block')
2921 connection
.set_default_list('block')
2922 connection
.get_privacy_list('block')
2924 def _block_it(is_checked
=None):
2925 if is_checked
is not None: # dialog has been shown
2926 if is_checked
: # user does not want to be asked again
2927 gajim
.config
.set('confirm_block', 'no')
2929 gajim
.config
.set('confirm_block', 'yes')
2930 self
.get_status_message('offline', on_continue
, show_pep
=False)
2932 confirm_block
= gajim
.config
.get('confirm_block')
2933 if confirm_block
== 'no':
2936 pritext
= _('You are about to block a contact. Are you sure you want'
2938 sectext
= _('This contact will see you offline and you will not '
2939 'receive messages he will send you.')
2940 dialogs
.ConfirmationDialogCheck(pritext
, sectext
,
2941 _('_Do not ask me again'), on_response_ok
=_block_it
)
2943 def on_unblock(self
, widget
, list_
, group
=None):
2945 When clicked on the 'unblock' button in context menu.
2949 for (contact
, account
) in list_
:
2950 if account
not in accounts
:
2951 if gajim
.connections
[account
].privacy_rules_supported
:
2952 accounts
.append(account
)
2953 gajim
.connections
[account
].new_blocked_list
= []
2954 gajim
.connections
[account
].to_unblock
= []
2955 gajim
.connections
[account
].to_unblock
.append(
2958 gajim
.connections
[account
].to_unblock
.append(contact
.jid
)
2959 # needed for draw_contact:
2960 if contact
.jid
in gajim
.connections
[account
].blocked_contacts
:
2961 gajim
.connections
[account
].blocked_contacts
.remove(
2963 self
.draw_contact(contact
.jid
, account
)
2964 for account
in accounts
:
2965 for rule
in gajim
.connections
[account
].blocked_list
:
2966 if rule
['action'] != 'deny' or rule
['type'] != 'jid' \
2967 or rule
['value'] not in \
2968 gajim
.connections
[account
].to_unblock
:
2969 gajim
.connections
[account
].new_blocked_list
.append(rule
)
2971 for (contact
, account
) in list_
:
2972 if account
not in accounts
:
2973 if gajim
.connections
[account
].privacy_rules_supported
:
2974 accounts
.append(account
)
2975 # needed for draw_group:
2976 if group
in gajim
.connections
[account
].blocked_groups
:
2977 gajim
.connections
[account
].blocked_groups
.remove(
2979 self
.draw_group(group
, account
)
2980 gajim
.connections
[account
].new_blocked_list
= []
2981 for rule
in gajim
.connections
[account
].blocked_list
:
2982 if rule
['action'] != 'deny' or \
2983 rule
['type'] != 'group' or rule
['value'] != group
:
2984 gajim
.connections
[account
].new_blocked_list
.\
2986 self
.draw_contact(contact
.jid
, account
)
2987 for account
in accounts
:
2988 gajim
.connections
[account
].set_privacy_list('block',
2989 gajim
.connections
[account
].new_blocked_list
)
2990 gajim
.connections
[account
].get_privacy_list('block')
2991 if len(gajim
.connections
[account
].new_blocked_list
) == 0:
2992 gajim
.connections
[account
].blocked_list
= []
2993 gajim
.connections
[account
].blocked_contacts
= []
2994 gajim
.connections
[account
].blocked_groups
= []
2995 gajim
.connections
[account
].set_default_list('')
2996 gajim
.connections
[account
].set_active_list('')
2997 gajim
.connections
[account
].del_privacy_list('block')
2998 if 'privacy_list_block' in gajim
.interface
.instances
[account
]:
2999 del gajim
.interface
.instances
[account
]['privacy_list_block']
3000 for (contact
, account
) in list_
:
3001 if not self
.regroup
:
3002 show
= gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
]
3003 else: # accounts merged
3004 show
= helpers
.get_global_show()
3005 if show
== 'invisible':
3006 # Don't send our presence if we're invisible
3008 if account
not in accounts
:
3009 accounts
.append(account
)
3010 if gajim
.connections
[account
].privacy_rules_supported
:
3011 self
.send_status(account
, show
,
3012 gajim
.connections
[account
].status
, to
=contact
.jid
)
3014 self
.send_status(account
, show
,
3015 gajim
.connections
[account
].status
, to
=contact
.jid
)
3017 def on_rename(self
, widget
, row_type
, jid
, account
):
3018 # this function is called either by F2 or by Rename menuitem
3019 if 'rename' in gajim
.interface
.instances
:
3020 gajim
.interface
.instances
['rename'].dialog
.present()
3023 # account is offline, don't allow to rename
3024 if gajim
.connections
[account
].connected
< 2:
3026 if row_type
in ('contact', 'agent'):
3028 title
= _('Rename Contact')
3029 message
= _('Enter a new nickname for contact %s') % jid
3030 old_text
= gajim
.contacts
.get_contact_with_highest_priority(account
,
3032 elif row_type
== 'group':
3033 if jid
in helpers
.special_groups
+ (_('General'),):
3036 title
= _('Rename Group')
3037 message
= _('Enter a new name for group %s') % \
3038 gobject
.markup_escape_text(jid
)
3040 def on_renamed(new_text
, account
, row_type
, jid
, old_text
):
3041 if 'rename' in gajim
.interface
.instances
:
3042 del gajim
.interface
.instances
['rename']
3043 if row_type
in ('contact', 'agent'):
3044 if old_text
== new_text
:
3046 contacts
= gajim
.contacts
.get_contacts(account
, jid
)
3047 for contact
in contacts
:
3048 contact
.name
= new_text
3049 gajim
.connections
[account
].update_contact(jid
, new_text
, \
3051 self
.draw_contact(jid
, account
)
3052 # Update opened chats
3053 for ctrl
in gajim
.interface
.msg_win_mgr
.get_controls(jid
,
3056 win
= gajim
.interface
.msg_win_mgr
.get_window(jid
, account
)
3057 win
.redraw_tab(ctrl
)
3059 elif row_type
== 'group':
3060 # in C_JID column, we hold the group name (which is not escaped)
3061 self
.rename_group(old_text
, new_text
, account
)
3064 if 'rename' in gajim
.interface
.instances
:
3065 del gajim
.interface
.instances
['rename']
3067 gajim
.interface
.instances
['rename'] = dialogs
.InputDialog(title
,
3068 message
, old_text
, False, (on_renamed
, account
, row_type
, jid
,
3069 old_text
), on_canceled
)
3071 def on_remove_group_item_activated(self
, widget
, group
, account
):
3073 for contact
in gajim
.contacts
.get_contacts_from_group(account
,
3076 self
.remove_contact_from_groups(contact
.jid
, account
,
3079 gajim
.connections
[account
].unsubscribe(contact
.jid
)
3080 self
.remove_contact(contact
.jid
, account
, backend
=True)
3082 dialogs
.ConfirmationDialogCheck(_('Remove Group'),
3083 _('Do you want to remove group %s from the roster?') % group
,
3084 _('Also remove all contacts in this group from your roster'),
3085 on_response_ok
=on_ok
)
3087 def on_assign_pgp_key(self
, widget
, contact
, account
):
3088 attached_keys
= gajim
.config
.get_per('accounts', account
,
3089 'attached_gpg_keys').split()
3092 for i
in xrange(len(attached_keys
)/2):
3093 keys
[attached_keys
[2*i
]] = attached_keys
[2*i
+1]
3094 if attached_keys
[2*i
] == contact
.jid
:
3095 keyID
= attached_keys
[2*i
+1]
3096 public_keys
= gajim
.connections
[account
].ask_gpg_keys()
3097 public_keys
[_('None')] = _('None')
3099 def on_key_selected(keyID
):
3102 if keyID
[0] == _('None'):
3103 if contact
.jid
in keys
:
3104 del keys
[contact
.jid
]
3108 keys
[contact
.jid
] = keyID
3110 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(contact
.jid
, account
)
3116 keys_str
+= jid
+ ' ' + keys
[jid
] + ' '
3117 gajim
.config
.set_per('accounts', account
, 'attached_gpg_keys',
3119 for u
in gajim
.contacts
.get_contacts(account
, contact
.jid
):
3120 u
.keyID
= helpers
.prepare_and_validate_gpg_keyID(account
,
3123 dialogs
.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
3124 _('Select a key to apply to the contact'), public_keys
,
3125 on_key_selected
, selected
=keyID
)
3127 def on_set_custom_avatar_activate(self
, widget
, contact
, account
):
3128 def on_ok(widget
, path_to_file
):
3129 filesize
= os
.path
.getsize(path_to_file
) # in bytes
3130 invalid_file
= False
3132 if os
.path
.isfile(path_to_file
):
3133 stat
= os
.stat(path_to_file
)
3136 msg
= _('File is empty')
3139 msg
= _('File does not exist')
3141 dialogs
.ErrorDialog(_('Could not load image'), msg
)
3144 pixbuf
= gtk
.gdk
.pixbuf_new_from_file(path_to_file
)
3145 if filesize
> 16384: # 16 kb
3146 # get the image at 'tooltip size'
3147 # and hope that user did not specify in ACE crazy size
3148 pixbuf
= gtkgui_helpers
.get_scaled_pixbuf(pixbuf
, 'tooltip')
3149 except gobject
.GError
, msg
: # unknown format
3150 # msg should be string, not object instance
3152 dialogs
.ErrorDialog(_('Could not load image'), msg
)
3154 gajim
.interface
.save_avatar_files(contact
.jid
, pixbuf
, local
=True)
3156 self
.update_avatar_in_gui(contact
.jid
, account
)
3158 def on_clear(widget
):
3161 gajim
.interface
.remove_avatar_files(contact
.jid
, local
=True)
3162 self
.update_avatar_in_gui(contact
.jid
, account
)
3164 dlg
= dialogs
.AvatarChooserDialog(on_response_ok
=on_ok
,
3165 on_response_clear
=on_clear
)
3167 def on_edit_groups(self
, widget
, list_
):
3168 dialogs
.EditGroupsDialog(list_
)
3170 def on_history(self
, widget
, contact
, account
):
3172 When history menuitem is activated: call log window
3174 if 'logs' in gajim
.interface
.instances
:
3175 gajim
.interface
.instances
['logs'].window
.present()
3176 gajim
.interface
.instances
['logs'].open_history(contact
.jid
, account
)
3178 gajim
.interface
.instances
['logs'] = history_window
.\
3179 HistoryWindow(contact
.jid
, account
)
3181 def on_disconnect(self
, widget
, jid
, account
):
3183 When disconnect menuitem is activated: disconect from room
3185 if jid
in gajim
.interface
.minimized_controls
[account
]:
3186 ctrl
= gajim
.interface
.minimized_controls
[account
][jid
]
3188 ctrl
.got_disconnected()
3189 self
.remove_groupchat(jid
, account
)
3191 def on_reconnect(self
, widget
, jid
, account
):
3193 When reconnect menuitem is activated: join the room
3195 if jid
in gajim
.interface
.minimized_controls
[account
]:
3196 ctrl
= gajim
.interface
.minimized_controls
[account
][jid
]
3197 gajim
.interface
.join_gc_room(account
, jid
, ctrl
.nick
,
3198 gajim
.gc_passwords
.get(jid
, ''))
3200 def on_send_single_message_menuitem_activate(self
, widget
, account
,
3203 dialogs
.SingleMessageWindow(account
, action
='send')
3204 elif isinstance(contact
, list):
3205 dialogs
.SingleMessageWindow(account
, contact
, 'send')
3208 if contact
.jid
== gajim
.get_jid_from_account(account
):
3209 jid
+= '/' + contact
.resource
3210 dialogs
.SingleMessageWindow(account
, jid
, 'send')
3212 def on_send_file_menuitem_activate(self
, widget
, contact
, account
,
3214 gajim
.interface
.instances
['file_transfers'].show_file_send_request(
3217 def on_add_special_notification_menuitem_activate(self
, widget
, jid
):
3218 dialogs
.AddSpecialNotificationDialog(jid
)
3220 def on_invite_to_new_room(self
, widget
, list_
, resource
=None):
3222 Resource parameter MUST NOT be used if more than one contact in list
3226 for (contact
, account
) in list_
:
3227 if contact
.jid
not in jid_list
:
3228 if resource
: # we MUST have one contact only in list_
3229 fjid
= contact
.jid
+ '/' + resource
3230 jid_list
.append(fjid
)
3232 jid_list
.append(contact
.jid
)
3233 if account
not in account_list
:
3234 account_list
.append(account
)
3235 # transform None in 'jabber'
3236 type_
= gajim
.get_transport_name_from_jid(jid_list
[0]) or 'jabber'
3237 for account
in account_list
:
3238 if gajim
.connections
[account
].muc_jid
[type_
]:
3239 # create the room on this muc server
3240 if 'join_gc' in gajim
.interface
.instances
[account
]:
3241 gajim
.interface
.instances
[account
]['join_gc'].window
.\
3244 gajim
.interface
.instances
[account
]['join_gc'] = \
3245 dialogs
.JoinGroupchatWindow(account
,
3246 gajim
.connections
[account
].muc_jid
[type_
],
3247 automatic
= {'invities': jid_list
})
3248 except GajimGeneralException
:
3252 def on_invite_to_room(self
, widget
, list_
, room_jid
, room_account
,
3255 Resource parameter MUST NOT be used if more than one contact in list
3259 contact_jid
= contact
.jid
3260 if resource
: # we MUST have one contact only in list_
3261 contact_jid
+= '/' + resource
3262 gajim
.connections
[room_account
].send_invite(room_jid
, contact_jid
)
3264 def on_all_groupchat_maximized(self
, widget
, group_list
):
3265 for (contact
, account
) in group_list
:
3266 self
.on_groupchat_maximized(widget
, contact
.jid
, account
)
3268 def on_groupchat_maximized(self
, widget
, jid
, account
):
3270 When a groupchat is maximized
3272 if not jid
in gajim
.interface
.minimized_controls
[account
]:
3274 gc_control
= gajim
.interface
.msg_win_mgr
.get_gc_control(jid
,
3277 mw
= gajim
.interface
.msg_win_mgr
.get_window(jid
, account
)
3278 mw
.set_active_tab(gc_control
)
3279 mw
.window
.window
.focus(gtk
.get_current_event_time())
3281 ctrl
= gajim
.interface
.minimized_controls
[account
][jid
]
3282 mw
= gajim
.interface
.msg_win_mgr
.get_window(jid
, account
)
3284 mw
= gajim
.interface
.msg_win_mgr
.create_window(ctrl
.contact
,
3285 ctrl
.account
, ctrl
.type_id
)
3286 ctrl
.parent_win
= mw
3288 mw
.set_active_tab(ctrl
)
3289 mw
.window
.window
.focus(gtk
.get_current_event_time())
3290 self
.remove_groupchat(jid
, account
)
3292 def on_edit_account(self
, widget
, account
):
3293 if 'accounts' in gajim
.interface
.instances
:
3294 gajim
.interface
.instances
['accounts'].window
.present()
3296 gajim
.interface
.instances
['accounts'] = config
.AccountsWindow()
3297 gajim
.interface
.instances
['accounts'].select_account(account
)
3299 def on_zeroconf_properties(self
, widget
, account
):
3300 if 'accounts' in gajim
.interface
.instances
:
3301 gajim
.interface
.instances
['accounts'].window
.present()
3303 gajim
.interface
.instances
['accounts'] = config
.AccountsWindow()
3304 gajim
.interface
.instances
['accounts'].select_account(account
)
3306 def on_open_gmail_inbox(self
, widget
, account
):
3307 url
= gajim
.connections
[account
].gmail_url
3309 helpers
.launch_browser_mailer('url', url
)
3311 def on_change_status_message_activate(self
, widget
, account
):
3312 show
= gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
]
3313 def on_response(message
, pep_dict
):
3314 if message
is None: # None is if user pressed Cancel
3316 self
.send_status(account
, show
, message
)
3317 self
.send_pep(account
, pep_dict
)
3318 dialogs
.ChangeStatusMessageDialog(on_response
, show
)
3320 def on_add_to_roster(self
, widget
, contact
, account
):
3321 dialogs
.AddNewContactWindow(account
, contact
.jid
, contact
.name
)
3324 def on_roster_treeview_scroll_event(self
, widget
, event
):
3325 self
.tooltip
.hide_tooltip()
3327 def on_roster_treeview_key_press_event(self
, widget
, event
):
3329 When a key is pressed in the treeviews
3331 self
.tooltip
.hide_tooltip()
3332 if event
.keyval
== gtk
.keysyms
.Escape
:
3333 self
.tree
.get_selection().unselect_all()
3334 elif event
.keyval
== gtk
.keysyms
.F2
:
3335 treeselection
= self
.tree
.get_selection()
3336 model
, list_of_paths
= treeselection
.get_selected_rows()
3337 if len(list_of_paths
) != 1:
3339 path
= list_of_paths
[0]
3340 type_
= model
[path
][C_TYPE
]
3341 if type_
in ('contact', 'group', 'agent'):
3342 jid
= model
[path
][C_JID
].decode('utf-8')
3343 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3344 self
.on_rename(widget
, type_
, jid
, account
)
3346 elif event
.keyval
== gtk
.keysyms
.Delete
:
3347 treeselection
= self
.tree
.get_selection()
3348 model
, list_of_paths
= treeselection
.get_selected_rows()
3349 if not len(list_of_paths
):
3351 type_
= model
[list_of_paths
[0]][C_TYPE
]
3352 account
= model
[list_of_paths
[0]][C_ACCOUNT
].decode('utf-8')
3353 if type_
in ('account', 'group', 'self_contact') or \
3354 account
== gajim
.ZEROCONF_ACC_NAME
:
3357 for path
in list_of_paths
:
3358 if model
[path
][C_TYPE
] != type_
:
3360 jid
= model
[path
][C_JID
].decode('utf-8')
3361 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3362 contact
= gajim
.contacts
.get_contact_with_highest_priority(
3364 list_
.append((contact
, account
))
3365 if type_
== 'contact':
3366 self
.on_req_usub(widget
, list_
)
3367 elif type_
== 'agent':
3368 self
.on_remove_agent(widget
, list_
)
3370 def on_roster_treeview_button_release_event(self
, widget
, event
):
3372 path
= self
.tree
.get_path_at_pos(int(event
.x
), int(event
.y
))[0]
3376 if event
.button
== 1: # Left click
3377 if gajim
.single_click
and not event
.state
& gtk
.gdk
.SHIFT_MASK
and \
3378 not event
.state
& gtk
.gdk
.CONTROL_MASK
:
3379 # Check if button has been pressed on the same row
3380 if self
.clicked_path
== path
:
3381 self
.on_row_activated(widget
, path
)
3382 self
.clicked_path
= None
3384 def on_roster_treeview_button_press_event(self
, widget
, event
):
3385 # hide tooltip, no matter the button is pressed
3386 self
.tooltip
.hide_tooltip()
3388 pos
= self
.tree
.get_path_at_pos(int(event
.x
), int(event
.y
))
3389 path
, x
= pos
[0], pos
[2]
3391 self
.tree
.get_selection().unselect_all()
3394 if event
.button
== 3: # Right click
3396 model
, list_of_paths
= self
.tree
.get_selection().\
3400 if path
not in list_of_paths
:
3401 self
.tree
.get_selection().unselect_all()
3402 self
.tree
.get_selection().select_path(path
)
3403 return self
.show_treeview_menu(event
)
3405 elif event
.button
== 2: # Middle click
3407 model
, list_of_paths
= self
.tree
.get_selection().\
3411 if list_of_paths
!= [path
]:
3412 self
.tree
.get_selection().unselect_all()
3413 self
.tree
.get_selection().select_path(path
)
3414 type_
= model
[path
][C_TYPE
]
3415 if type_
in ('agent', 'contact', 'self_contact', 'groupchat'):
3416 self
.on_row_activated(widget
, path
)
3417 elif type_
== 'account':
3418 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3419 if account
!= 'all':
3420 show
= gajim
.connections
[account
].connected
3421 if show
> 1: # We are connected
3422 self
.on_change_status_message_activate(widget
, account
)
3424 show
= helpers
.get_global_show()
3425 if show
== 'offline':
3427 def on_response(message
, pep_dict
):
3430 for acct
in gajim
.connections
:
3431 if not gajim
.config
.get_per('accounts', acct
,
3432 'sync_with_global_status'):
3434 current_show
= gajim
.SHOW_LIST
[gajim
.connections
[acct
].\
3436 self
.send_status(acct
, current_show
, message
)
3437 self
.send_pep(acct
, pep_dict
)
3438 dialogs
.ChangeStatusMessageDialog(on_response
, show
)
3441 elif event
.button
== 1: # Left click
3442 model
= self
.modelfilter
3443 type_
= model
[path
][C_TYPE
]
3444 # x_min is the x start position of status icon column
3445 if gajim
.config
.get('avatar_position_in_roster') == 'left':
3446 x_min
= gajim
.config
.get('roster_avatar_width')
3449 if gajim
.single_click
and not event
.state
& gtk
.gdk
.SHIFT_MASK
and \
3450 not event
.state
& gtk
.gdk
.CONTROL_MASK
:
3451 # Don't handle double click if we press icon of a metacontact
3452 titer
= model
.get_iter(path
)
3453 if x
> x_min
and x
< x_min
+ 27 and type_
== 'contact' and \
3454 model
.iter_has_child(titer
):
3455 if (self
.tree
.row_expanded(path
)):
3456 self
.tree
.collapse_row(path
)
3458 self
.tree
.expand_row(path
, False)
3460 # We just save on which row we press button, and open chat
3461 # window on button release to be able to do DND without opening
3463 self
.clicked_path
= path
3466 if type_
== 'group' and x
< 27:
3467 # first cell in 1st column (the arrow SINGLE clicked)
3468 if (self
.tree
.row_expanded(path
)):
3469 self
.tree
.collapse_row(path
)
3471 self
.expand_group_row(path
)
3473 elif type_
== 'contact' and x
> x_min
and x
< x_min
+ 27:
3474 if (self
.tree
.row_expanded(path
)):
3475 self
.tree
.collapse_row(path
)
3477 self
.tree
.expand_row(path
, False)
3479 def expand_group_row(self
, path
):
3480 self
.tree
.expand_row(path
, False)
3481 iter = self
.modelfilter
.get_iter(path
)
3482 child_iter
= self
.modelfilter
.iter_children(iter)
3484 type_
= self
.modelfilter
[child_iter
][C_TYPE
]
3485 account
= self
.modelfilter
[child_iter
][C_ACCOUNT
]
3486 group
= self
.modelfilter
[child_iter
][C_JID
]
3487 if type_
== 'group' and account
+ group
not in self
.collapsed_rows
:
3488 self
.expand_group_row(self
.modelfilter
.get_path(child_iter
))
3489 child_iter
= self
.modelfilter
.iter_next(child_iter
)
3491 def on_req_usub(self
, widget
, list_
):
3493 Remove a contact. list_ is a list of (contact, account) tuples
3495 def on_ok(is_checked
, list_
):
3498 contact
= list_
[0][0]
3499 if contact
.sub
!= 'to' and is_checked
:
3501 for (contact
, account
) in list_
:
3502 if _('Not in Roster') not in contact
.get_shown_groups():
3503 gajim
.connections
[account
].unsubscribe(contact
.jid
,
3505 self
.remove_contact(contact
.jid
, account
, backend
=True)
3506 if not remove_auth
and contact
.sub
== 'both':
3509 contact
.sub
= 'from'
3510 # we can't see him, but have to set it manually in contact
3511 contact
.show
= 'offline'
3512 gajim
.contacts
.add_contact(account
, contact
)
3513 self
.add_contact(contact
.jid
, account
)
3518 contact
= list_
[0][0]
3519 pritext
= _('Contact "%s" will be removed from your roster') % \
3520 contact
.get_shown_name()
3521 sectext
= _('You are about to remove "%(name)s" (%(jid)s) from '
3522 'your roster.\n') % {'name': contact
.get_shown_name(),
3524 if contact
.sub
== 'to':
3525 dialogs
.ConfirmationDialog(pritext
, sectext
+ \
3526 _('By removing this contact you also remove authorization '
3527 'resulting in him or her always seeing you as offline.'),
3528 on_response_ok
=(on_ok2
, list_
))
3529 elif _('Not in Roster') in contact
.get_shown_groups():
3530 # Contact is not in roster
3531 dialogs
.ConfirmationDialog(pritext
, sectext
+ \
3532 _('Do you want to continue?'), on_response_ok
=(on_ok2
,
3535 dialogs
.ConfirmationDialogCheck(pritext
, sectext
+ \
3536 _('By removing this contact you also by default remove '
3537 'authorization resulting in him or her always seeing you as'
3539 _('I want this contact to know my status after removal'),
3540 on_response_ok
=(on_ok
, list_
))
3542 # several contact to remove at the same time
3543 pritext
= _('Contacts will be removed from your roster')
3545 for (contact
, account
) in list_
:
3546 jids
+= '\n ' + contact
.get_shown_name() + ' (%s)' % \
3548 sectext
= _('By removing these contacts:%s\nyou also remove '
3549 'authorization resulting in them always seeing you as '
3551 dialogs
.ConfirmationDialog(pritext
, sectext
,
3552 on_response_ok
=(on_ok2
, list_
))
3554 def on_send_custom_status(self
, widget
, contact_list
, show
, group
=None):
3558 # contact_list has only one element except if group != None
3559 def on_response(message
, pep_dict
):
3560 if message
is None: # None if user pressed Cancel
3563 for (contact
, account
) in contact_list
:
3564 if account
not in account_list
:
3565 account_list
.append(account
)
3566 # 1. update status_sent_to_[groups|users] list
3568 for account
in account_list
:
3569 if account
not in gajim
.interface
.status_sent_to_groups
:
3570 gajim
.interface
.status_sent_to_groups
[account
] = {}
3571 gajim
.interface
.status_sent_to_groups
[account
][group
] = show
3573 for (contact
, account
) in contact_list
:
3574 if account
not in gajim
.interface
.status_sent_to_users
:
3575 gajim
.interface
.status_sent_to_users
[account
] = {}
3576 gajim
.interface
.status_sent_to_users
[account
][contact
.jid
] \
3579 # 2. update privacy lists if main status is invisible
3580 for account
in account_list
:
3581 if gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
] == \
3583 gajim
.connections
[account
].set_invisible_rule()
3585 # 3. send directed presence
3586 for (contact
, account
) in contact_list
:
3587 our_jid
= gajim
.get_jid_from_account(account
)
3590 jid
+= '/' + contact
.resource
3591 self
.send_status(account
, show
, message
, to
=jid
)
3593 def send_it(is_checked
=None):
3594 if is_checked
is not None: # dialog has been shown
3595 if is_checked
: # user does not want to be asked again
3596 gajim
.config
.set('confirm_custom_status', 'no')
3598 gajim
.config
.set('confirm_custom_status', 'yes')
3599 self
.get_status_message(show
, on_response
, show_pep
=False,
3602 confirm_custom_status
= gajim
.config
.get('confirm_custom_status')
3603 if confirm_custom_status
== 'no':
3606 pritext
= _('You are about to send a custom status. Are you sure you '
3607 'want to continue?')
3608 sectext
= _('This contact will temporarily see you as %(status)s, '
3609 'but only until you change your status. Then he or she will see '
3610 'your global status.') % {'status': show
}
3611 dialogs
.ConfirmationDialogCheck(pritext
, sectext
,
3612 _('_Do not ask me again'), on_response_ok
=send_it
)
3614 def on_status_combobox_changed(self
, widget
):
3616 When we change our status via the combobox
3618 model
= self
.status_combobox
.get_model()
3619 active
= self
.status_combobox
.get_active()
3620 if active
== -1: # no active item
3622 if not self
.combobox_callback_active
:
3623 self
.previous_status_combobox_active
= active
3625 accounts
= gajim
.connections
.keys()
3626 if len(accounts
) == 0:
3627 dialogs
.ErrorDialog(_('No account available'),
3628 _('You must create an account before you can chat with other '
3630 self
.update_status_combobox()
3632 status
= model
[active
][2].decode('utf-8')
3633 # status "desync'ed" or not
3634 statuses_unified
= helpers
.statuses_unified()
3635 if (active
== 7 and statuses_unified
) or (active
== 9 and \
3636 not statuses_unified
):
3637 # 'Change status message' selected:
3638 # do not change show, just show change status dialog
3639 status
= model
[self
.previous_status_combobox_active
][2].decode(
3641 def on_response(message
, pep_dict
):
3642 if message
is not None: # None if user pressed Cancel
3643 for account
in accounts
:
3644 if not gajim
.config
.get_per('accounts', account
,
3645 'sync_with_global_status'):
3647 current_show
= gajim
.SHOW_LIST
[
3648 gajim
.connections
[account
].connected
]
3649 self
.send_status(account
, current_show
, message
)
3650 self
.send_pep(account
, pep_dict
)
3651 self
.combobox_callback_active
= False
3652 self
.status_combobox
.set_active(
3653 self
.previous_status_combobox_active
)
3654 self
.combobox_callback_active
= True
3655 dialogs
.ChangeStatusMessageDialog(on_response
, status
)
3657 # we are about to change show, so save this new show so in case
3658 # after user chooses "Change status message" menuitem
3659 # we can return to this show
3660 self
.previous_status_combobox_active
= active
3661 connected_accounts
= gajim
.get_number_of_connected_accounts()
3663 def on_continue(message
, pep_dict
):
3665 # user pressed Cancel to change status message dialog
3666 self
.update_status_combobox()
3668 global_sync_accounts
= []
3669 for acct
in accounts
:
3670 if gajim
.config
.get_per('accounts', acct
,
3671 'sync_with_global_status'):
3672 global_sync_accounts
.append(acct
)
3673 global_sync_connected_accounts
= \
3674 gajim
.get_number_of_connected_accounts(global_sync_accounts
)
3675 for account
in accounts
:
3676 if not gajim
.config
.get_per('accounts', account
,
3677 'sync_with_global_status'):
3679 # we are connected (so we wanna change show and status)
3680 # or no account is connected and we want to connect with new
3683 if not global_sync_connected_accounts
> 0 or \
3684 gajim
.connections
[account
].connected
> 0:
3685 self
.send_status(account
, status
, message
)
3686 self
.send_pep(account
, pep_dict
)
3687 self
.update_status_combobox()
3689 if status
== 'invisible':
3691 for account
in accounts
:
3692 if connected_accounts
< 1 or gajim
.account_is_connected(
3694 if not gajim
.config
.get_per('accounts', account
,
3695 'sync_with_global_status'):
3697 # We're going to change our status to invisible
3698 if self
.connected_rooms(account
):
3703 self
.get_status_message(status
, on_continue
, show_pep
=False)
3706 self
.update_status_combobox()
3708 dialogs
.ConfirmationDialog(
3709 _('You are participating in one or more group chats'),
3710 _('Changing your status to invisible will result in '
3711 'disconnection from those group chats. Are you sure you '
3712 'want to go invisible?'), on_reponse_ok
=on_ok
,
3713 on_response_cancel
=on_cancel
)
3716 self
.get_status_message(status
, on_continue
)
3718 def on_preferences_menuitem_activate(self
, widget
):
3719 if 'preferences' in gajim
.interface
.instances
:
3720 gajim
.interface
.instances
['preferences'].window
.present()
3722 gajim
.interface
.instances
['preferences'] = config
.PreferencesWindow(
3725 def on_plugins_menuitem_activate(self
, widget
):
3726 if gajim
.interface
.instances
.has_key('plugins'):
3727 gajim
.interface
.instances
['plugins'].window
.present()
3729 gajim
.interface
.instances
['plugins'] = plugins
.gui
.PluginsWindow()
3731 def on_publish_tune_toggled(self
, widget
, account
):
3732 active
= widget
.get_active()
3733 gajim
.config
.set_per('accounts', account
, 'publish_tune', active
)
3735 gajim
.interface
.enable_music_listener()
3737 gajim
.connections
[account
].retract_tune()
3738 # disable music listener only if no other account uses it
3739 for acc
in gajim
.connections
:
3740 if gajim
.config
.get_per('accounts', acc
, 'publish_tune'):
3743 gajim
.interface
.disable_music_listener()
3745 helpers
.update_optional_features(account
)
3747 def on_publish_location_toggled(self
, widget
, account
):
3748 active
= widget
.get_active()
3749 gajim
.config
.set_per('accounts', account
, 'publish_location', active
)
3751 location_listener
.enable()
3753 gajim
.connections
[account
].retract_location()
3754 # disable music listener only if no other account uses it
3755 for acc
in gajim
.connections
:
3756 if gajim
.config
.get_per('accounts', acc
, 'publish_location'):
3759 location_listener
.disable()
3761 helpers
.update_optional_features(account
)
3763 def on_pep_services_menuitem_activate(self
, widget
, account
):
3764 if 'pep_services' in gajim
.interface
.instances
[account
]:
3765 gajim
.interface
.instances
[account
]['pep_services'].window
.present()
3767 gajim
.interface
.instances
[account
]['pep_services'] = \
3768 config
.ManagePEPServicesWindow(account
)
3770 def on_add_new_contact(self
, widget
, account
):
3771 dialogs
.AddNewContactWindow(account
)
3773 def on_join_gc_activate(self
, widget
, account
):
3775 When the join gc menuitem is clicked, show the join gc window
3777 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
3778 if gajim
.connections
[account
].connected
== invisible_show
:
3779 dialogs
.ErrorDialog(_('You cannot join a group chat while you are '
3782 if 'join_gc' in gajim
.interface
.instances
[account
]:
3783 gajim
.interface
.instances
[account
]['join_gc'].window
.present()
3786 gajim
.interface
.instances
[account
]['join_gc'] = \
3787 dialogs
.JoinGroupchatWindow(account
)
3788 except GajimGeneralException
:
3791 def on_new_chat_menuitem_activate(self
, widget
, account
):
3792 dialogs
.NewChatDialog(account
)
3794 def on_contents_menuitem_activate(self
, widget
):
3795 helpers
.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
3797 def on_faq_menuitem_activate(self
, widget
):
3798 helpers
.launch_browser_mailer('url',
3799 'http://trac.gajim.org/wiki/GajimFaq')
3801 def on_features_menuitem_activate(self
, widget
):
3802 features_window
.FeaturesWindow()
3804 def on_about_menuitem_activate(self
, widget
):
3805 dialogs
.AboutDialog()
3807 def on_accounts_menuitem_activate(self
, widget
):
3808 if 'accounts' in gajim
.interface
.instances
:
3809 gajim
.interface
.instances
['accounts'].window
.present()
3811 gajim
.interface
.instances
['accounts'] = config
.AccountsWindow()
3813 def on_file_transfers_menuitem_activate(self
, widget
):
3814 if gajim
.interface
.instances
['file_transfers'].window
.get_property(
3816 gajim
.interface
.instances
['file_transfers'].window
.present()
3818 gajim
.interface
.instances
['file_transfers'].window
.show_all()
3820 def on_history_menuitem_activate(self
, widget
):
3821 if 'logs' in gajim
.interface
.instances
:
3822 gajim
.interface
.instances
['logs'].window
.present()
3824 gajim
.interface
.instances
['logs'] = history_window
.\
3827 def on_show_transports_menuitem_activate(self
, widget
):
3828 gajim
.config
.set('show_transports_group', widget
.get_active())
3829 self
.refilter_shown_roster_items()
3831 def on_manage_bookmarks_menuitem_activate(self
, widget
):
3832 config
.ManageBookmarksWindow()
3834 def on_profile_avatar_menuitem_activate(self
, widget
, account
):
3835 gajim
.interface
.edit_own_details(account
)
3837 def on_execute_command(self
, widget
, contact
, account
, resource
=None):
3839 Execute command. Full JID needed; if it is other contact, resource is
3840 necessary. Widget is unnecessary, only to be able to make this a
3844 if resource
is not None:
3845 jid
= jid
+ u
'/' + resource
3846 adhoc_commands
.CommandWindow(account
, jid
)
3848 def on_roster_window_focus_in_event(self
, widget
, event
):
3849 # roster received focus, so if we had urgency REMOVE IT
3850 # NOTE: we do not have to read the message to remove urgency
3851 # so this functions does that
3852 gtkgui_helpers
.set_unset_urgency_hint(widget
, False)
3854 # if a contact row is selected, update colors (eg. for status msg)
3855 # because gtk engines may differ in bg when window is selected
3857 if len(self
._last
_selected
_contact
):
3858 for (jid
, account
) in self
._last
_selected
_contact
:
3859 self
.draw_contact(jid
, account
, selected
=True, focus
=True)
3861 def on_roster_window_focus_out_event(self
, widget
, event
):
3862 # if a contact row is selected, update colors (eg. for status msg)
3863 # because gtk engines may differ in bg when window is selected
3865 if len(self
._last
_selected
_contact
):
3866 for (jid
, account
) in self
._last
_selected
_contact
:
3867 self
.draw_contact(jid
, account
, selected
=True, focus
=False)
3869 def on_roster_window_key_press_event(self
, widget
, event
):
3870 if event
.keyval
== gtk
.keysyms
.Escape
:
3871 if gajim
.interface
.msg_win_mgr
.mode
== \
3872 MessageWindowMgr
.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER
and \
3873 gajim
.interface
.msg_win_mgr
.one_window_opened():
3874 # let message window close the tab
3876 list_of_paths
= self
.tree
.get_selection().get_selected_rows()[1]
3877 if not len(list_of_paths
) and gajim
.interface
.systray_enabled
and \
3878 not gajim
.config
.get('quit_on_roster_x_button'):
3879 self
.tooltip
.hide_tooltip()
3881 elif event
.state
& gtk
.gdk
.CONTROL_MASK
and event
.keyval
== \
3883 treeselection
= self
.tree
.get_selection()
3884 model
, list_of_paths
= treeselection
.get_selected_rows()
3885 for path
in list_of_paths
:
3886 type_
= model
[path
][C_TYPE
]
3887 if type_
in ('contact', 'agent'):
3888 jid
= model
[path
][C_JID
].decode('utf-8')
3889 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3890 contact
= gajim
.contacts
.get_first_contact_from_jid(account
,
3892 self
.on_info(widget
, contact
, account
)
3893 elif event
.state
& gtk
.gdk
.CONTROL_MASK
and event
.keyval
== \
3895 treeselection
= self
.tree
.get_selection()
3896 model
, list_of_paths
= treeselection
.get_selected_rows()
3897 if len(list_of_paths
) != 1:
3899 path
= list_of_paths
[0]
3900 type_
= model
[path
][C_TYPE
]
3901 if type_
in ('contact', 'agent'):
3902 jid
= model
[path
][C_JID
].decode('utf-8')
3903 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3904 contact
= gajim
.contacts
.get_first_contact_from_jid(account
,
3906 self
.on_history(widget
, contact
, account
)
3908 def on_roster_window_popup_menu(self
, widget
):
3909 event
= gtk
.gdk
.Event(gtk
.gdk
.KEY_PRESS
)
3910 self
.show_treeview_menu(event
)
3912 def on_row_activated(self
, widget
, path
):
3914 When an iter is activated (double-click or single click if gnome is set
3917 model
= self
.modelfilter
3918 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3919 type_
= model
[path
][C_TYPE
]
3920 if type_
in ('group', 'account'):
3921 if self
.tree
.row_expanded(path
):
3922 self
.tree
.collapse_row(path
)
3924 self
.tree
.expand_row(path
, False)
3926 jid
= model
[path
][C_JID
].decode('utf-8')
3928 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
3929 titer
= model
.get_iter(path
)
3930 if contact
.is_groupchat():
3931 first_ev
= gajim
.events
.get_first_event(account
, jid
)
3932 if first_ev
and self
.open_event(account
, jid
, first_ev
):
3933 # We are invited to a GC
3934 # open event cares about connecting to it
3935 self
.remove_groupchat(jid
, account
)
3937 self
.on_groupchat_maximized(None, jid
, account
)
3941 first_ev
= gajim
.events
.get_first_event(account
, jid
)
3943 # look in other resources
3944 for c
in gajim
.contacts
.get_contacts(account
, jid
):
3945 fjid
= c
.get_full_jid()
3946 first_ev
= gajim
.events
.get_first_event(account
, fjid
)
3948 resource
= c
.resource
3950 if not first_ev
and model
.iter_has_child(titer
):
3951 child_iter
= model
.iter_children(titer
)
3952 while not first_ev
and child_iter
:
3953 child_jid
= model
[child_iter
][C_JID
].decode('utf-8')
3954 first_ev
= gajim
.events
.get_first_event(account
, child_jid
)
3958 child_iter
= model
.iter_next(child_iter
)
3961 if first_ev
.type_
in ('chat', 'normal'):
3962 session
= first_ev
.parameters
[8]
3965 fjid
+= '/' + resource
3966 if self
.open_event(account
, fjid
, first_ev
):
3969 contact
= gajim
.contacts
.get_contact(account
, jid
, resource
)
3970 if not contact
or isinstance(contact
, list):
3971 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
3973 if jid
== gajim
.get_jid_from_account(account
):
3974 resource
= contact
.resource
3976 gajim
.interface
.on_open_chat_window(None, contact
, account
, \
3977 resource
=resource
, session
=session
)
3979 def on_roster_treeview_row_activated(self
, widget
, path
, col
=0):
3981 When an iter is double clicked: open the first event window
3983 if not gajim
.single_click
:
3984 self
.on_row_activated(widget
, path
)
3986 def on_roster_treeview_row_expanded(self
, widget
, titer
, path
):
3988 When a row is expanded change the icon of the arrow
3990 self
._toggeling
_row
= True
3991 model
= widget
.get_model()
3992 child_model
= model
.get_model()
3993 child_iter
= model
.convert_iter_to_child_iter(titer
)
3995 if self
.regroup
: # merged accounts
3996 accounts
= gajim
.connections
.keys()
3998 accounts
= [model
[titer
][C_ACCOUNT
].decode('utf-8')]
4000 type_
= model
[titer
][C_TYPE
]
4001 if type_
== 'group':
4002 group
= model
[titer
][C_JID
].decode('utf-8')
4003 child_model
[child_iter
][C_IMG
] = \
4004 gajim
.interface
.jabber_state_images
['16']['opened']
4005 for account
in accounts
:
4006 if group
in gajim
.groups
[account
]: # This account has this group
4007 gajim
.groups
[account
][group
]['expand'] = True
4008 if account
+ group
in self
.collapsed_rows
:
4009 self
.collapsed_rows
.remove(account
+ group
)
4010 for contact
in gajim
.contacts
.iter_contacts(account
):
4012 if group
in contact
.groups
and \
4013 gajim
.contacts
.is_big_brother(account
, jid
, accounts
) and \
4014 account
+ group
+ jid
not in self
.collapsed_rows
:
4015 titers
= self
._get
_contact
_iter
(jid
, account
)
4016 for titer
in titers
:
4017 path
= model
.get_path(titer
)
4018 self
.tree
.expand_row(path
, False)
4019 elif type_
== 'account':
4020 account
= accounts
[0] # There is only one cause we don't use merge
4021 if account
in self
.collapsed_rows
:
4022 self
.collapsed_rows
.remove(account
)
4023 self
.draw_account(account
)
4024 # When we expand, groups are collapsed. Restore expand state
4025 for group
in gajim
.groups
[account
]:
4026 if gajim
.groups
[account
][group
]['expand']:
4027 titer
= self
._get
_group
_iter
(group
, account
)
4029 path
= model
.get_path(titer
)
4030 self
.tree
.expand_row(path
, False)
4031 elif type_
== 'contact':
4032 # Metacontact got toggled, update icon
4033 jid
= model
[titer
][C_JID
].decode('utf-8')
4034 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4035 contact
= gajim
.contacts
.get_contact(account
, jid
)
4036 for group
in contact
.groups
:
4037 if account
+ group
+ jid
in self
.collapsed_rows
:
4038 self
.collapsed_rows
.remove(account
+ group
+ jid
)
4039 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
4041 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)[0]
4042 # Redraw all brothers to show pending events
4043 for data
in nearby_family
:
4044 self
.draw_contact(data
['jid'], data
['account'])
4046 self
._toggeling
_row
= False
4048 def on_roster_treeview_row_collapsed(self
, widget
, titer
, path
):
4050 When a row is collapsed change the icon of the arrow
4052 self
._toggeling
_row
= True
4053 model
= widget
.get_model()
4054 child_model
= model
.get_model()
4055 child_iter
= model
.convert_iter_to_child_iter(titer
)
4057 if self
.regroup
: # merged accounts
4058 accounts
= gajim
.connections
.keys()
4060 accounts
= [model
[titer
][C_ACCOUNT
].decode('utf-8')]
4062 type_
= model
[titer
][C_TYPE
]
4063 if type_
== 'group':
4064 child_model
[child_iter
][C_IMG
] = gajim
.interface
.\
4065 jabber_state_images
['16']['closed']
4066 group
= model
[titer
][C_JID
].decode('utf-8')
4067 for account
in accounts
:
4068 if group
in gajim
.groups
[account
]: # This account has this group
4069 gajim
.groups
[account
][group
]['expand'] = False
4070 if account
+ group
not in self
.collapsed_rows
:
4071 self
.collapsed_rows
.append(account
+ group
)
4072 elif type_
== 'account':
4073 account
= accounts
[0] # There is only one cause we don't use merge
4074 if account
not in self
.collapsed_rows
:
4075 self
.collapsed_rows
.append(account
)
4076 self
.draw_account(account
)
4077 elif type_
== 'contact':
4078 # Metacontact got toggled, update icon
4079 jid
= model
[titer
][C_JID
].decode('utf-8')
4080 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4081 contact
= gajim
.contacts
.get_contact(account
, jid
)
4082 for group
in contact
.groups
:
4083 if account
+ group
+ jid
not in self
.collapsed_rows
:
4084 self
.collapsed_rows
.append(account
+ group
+ jid
)
4085 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
4087 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)[0]
4088 # Redraw all brothers to show pending events
4089 for data
in nearby_family
:
4090 self
.draw_contact(data
['jid'], data
['account'])
4092 self
._toggeling
_row
= False
4094 def on_modelfilter_row_has_child_toggled(self
, model
, path
, titer
):
4096 Called when a row has gotten the first or lost its last child row
4098 Expand Parent if necessary.
4100 if self
._toggeling
_row
:
4101 # Signal is emitted when we write to our model
4104 type_
= model
[titer
][C_TYPE
]
4105 account
= model
[titer
][C_ACCOUNT
]
4109 account
= account
.decode('utf-8')
4111 if type_
== 'contact':
4112 child_iter
= model
.convert_iter_to_child_iter(titer
)
4113 if self
.model
.iter_has_child(child_iter
):
4114 # we are a bigbrother metacontact
4115 # redraw us to show/hide expand icon
4117 # Prevent endless loops
4118 jid
= model
[titer
][C_JID
].decode('utf-8')
4119 gobject
.idle_add(self
.draw_contact
, jid
, account
)
4120 elif type_
== 'group':
4121 group
= model
[titer
][C_JID
].decode('utf-8')
4122 self
._adjust
_group
_expand
_collapse
_state
(group
, account
)
4123 elif type_
== 'account':
4124 self
._adjust
_account
_expand
_collapse
_state
(account
)
4126 # Selection can change when the model is filtered
4127 # Only write to the model when filtering is finished!
4129 # FIXME: When we are filtering our custom colors are somehow lost
4131 # def on_treeview_selection_changed(self, selection):
4132 # '''Called when selection in TreeView has changed.
4134 # Redraw unselected rows to make status message readable
4135 # on all possible backgrounds.
4137 # model, list_of_paths = selection.get_selected_rows()
4138 # if len(self._last_selected_contact):
4139 # # update unselected rows
4140 # for (jid, account) in self._last_selected_contact:
4141 # gobject.idle_add(self.draw_contact, jid,
4143 # self._last_selected_contact = []
4144 # if len(list_of_paths) == 0:
4146 # for path in list_of_paths:
4148 # if row[C_TYPE] != 'contact':
4149 # self._last_selected_contact = []
4151 # jid = row[C_JID].decode('utf-8')
4152 # account = row[C_ACCOUNT].decode('utf-8')
4153 # self._last_selected_contact.append((jid, account))
4154 # gobject.idle_add(self.draw_contact, jid, account, True)
4156 def on_service_disco_menuitem_activate(self
, widget
, account
):
4157 server_jid
= gajim
.config
.get_per('accounts', account
, 'hostname')
4158 if server_jid
in gajim
.interface
.instances
[account
]['disco']:
4159 gajim
.interface
.instances
[account
]['disco'][server_jid
].\
4163 # Object will add itself to the window dict
4164 disco
.ServiceDiscoveryWindow(account
, address_entry
=True)
4165 except GajimGeneralException
:
4168 def on_show_offline_contacts_menuitem_activate(self
, widget
):
4170 When show offline option is changed: redraw the treeview
4172 gajim
.config
.set('showoffline', not gajim
.config
.get('showoffline'))
4173 self
.refilter_shown_roster_items()
4174 w
= self
.xml
.get_object('show_only_active_contacts_menuitem')
4175 if gajim
.config
.get('showoffline'):
4176 # We need to filter twice to show groups with no contacts inside
4177 # in the correct expand state
4178 self
.refilter_shown_roster_items()
4179 w
.set_sensitive(False)
4181 w
.set_sensitive(True)
4183 def on_show_only_active_contacts_menuitem_activate(self
, widget
):
4185 When show only active contact option is changed: redraw the treeview
4187 gajim
.config
.set('show_only_chat_and_online', not gajim
.config
.get(
4188 'show_only_chat_and_online'))
4189 self
.refilter_shown_roster_items()
4190 w
= self
.xml
.get_object('show_offline_contacts_menuitem')
4191 if gajim
.config
.get('show_only_chat_and_online'):
4192 # We need to filter twice to show groups with no contacts inside
4193 # in the correct expand state
4194 self
.refilter_shown_roster_items()
4195 w
.set_sensitive(False)
4197 w
.set_sensitive(True)
4199 def on_view_menu_activate(self
, widget
):
4200 # Hide the show roster menu if we are not in the right windowing mode.
4201 if self
.hpaned
.get_child2() is not None:
4202 self
.xml
.get_object('show_roster_menuitem').show()
4204 self
.xml
.get_object('show_roster_menuitem').hide()
4206 def on_show_roster_menuitem_toggled(self
, widget
):
4207 # when num controls is 0 this menuitem is hidden, but still need to
4208 # disable keybinding
4209 if self
.hpaned
.get_child2() is not None:
4210 self
.show_roster_vbox(widget
.get_active())
4212 def on_rfilter_entry_changed(self
, widget
):
4213 """ When we update the content of the filter """
4214 self
.rfilter_string
= widget
.get_text().lower()
4215 if self
.rfilter_string
== '':
4216 self
.rfilter_enabled
= False
4218 self
.rfilter_enabled
= True
4219 self
.refilter_shown_roster_items()
4221 def on_rfilter_entry_icon_press(self
, widget
, icon
, event
):
4222 """ Disable the roster filtering by clicking the icon in the textEntry """
4223 self
.xml
.get_object('show_rfilter_menuitem').set_active(False)
4224 self
.rfilter_enabled
= False
4225 self
.refilter_shown_roster_items()
4227 def on_show_rfilter_menuitem_toggled(self
, widget
):
4228 """ Show the roster filter entry """
4229 self
.rfilter_enabled
= widget
.get_active()
4230 self
.rfilter_entry
.set_visible(self
.rfilter_enabled
)
4231 self
.rfilter_entry
.set_editable(self
.rfilter_enabled
)
4232 if self
.rfilter_enabled
:
4233 self
.rfilter_entry
.set_text('')
4234 self
.rfilter_entry
.grab_focus()
4236 def on_roster_hpaned_notify(self
, pane
, gparamspec
):
4238 Keep changing the width of the roster
4239 (when a gtk.Paned widget handle is dragged)
4241 if gparamspec
.name
== 'position':
4242 roster_width
= pane
.get_child1().allocation
.width
4243 gajim
.config
.set('roster_width', roster_width
)
4245 ################################################################################
4246 ### Drag and Drop handling
4247 ################################################################################
4249 def drag_data_get_data(self
, treeview
, context
, selection
, target_id
,
4251 model
, list_of_paths
= self
.tree
.get_selection().get_selected_rows()
4252 if len(list_of_paths
) != 1:
4254 path
= list_of_paths
[0]
4257 data
= model
[path
][C_JID
]
4258 selection
.set(selection
.target
, 8, data
)
4260 def drag_begin(self
, treeview
, context
):
4261 self
.dragging
= True
4263 def drag_end(self
, treeview
, context
):
4264 self
.dragging
= False
4266 def on_drop_rosterx(self
, widget
, account_source
, c_source
, account_dest
,
4267 c_dest
, was_big_brother
, context
, etime
):
4268 gajim
.connections
[account_dest
].send_contacts([c_source
], c_dest
.jid
)
4270 def on_drop_in_contact(self
, widget
, account_source
, c_source
, account_dest
,
4271 c_dest
, was_big_brother
, context
, etime
):
4273 if not gajim
.connections
[account_source
].private_storage_supported
or \
4274 not gajim
.connections
[account_dest
].private_storage_supported
:
4275 dialogs
.WarningDialog(_('Metacontacts storage not supported by '
4277 _('Your server does not support storing metacontacts '
4278 'information. So those information will not be saved on next '
4281 def merge_contacts(is_checked
=None):
4283 if is_checked
is not None: # dialog has been shown
4284 if is_checked
: # user does not want to be asked again
4285 gajim
.config
.set('confirm_metacontacts', 'no')
4287 gajim
.config
.set('confirm_metacontacts', 'yes')
4289 # We might have dropped on a metacontact.
4290 # Remove it and readd later with updated family info
4291 dest_family
= gajim
.contacts
.get_metacontacts_family(account_dest
,
4294 self
._remove
_metacontact
_family
(dest_family
, account_dest
)
4295 source_family
= gajim
.contacts
.get_metacontacts_family(
4296 account_source
, c_source
.jid
)
4297 if dest_family
== source_family
:
4298 n
= contacts
= len(dest_family
)
4299 for tag
in source_family
:
4300 if tag
['jid'] == c_source
.jid
:
4301 tag
['order'] = contacts
4307 self
._remove
_entity
(c_dest
, account_dest
)
4309 old_family
= gajim
.contacts
.get_metacontacts_family(account_source
,
4311 old_groups
= c_source
.groups
4313 # Remove old source contact(s)
4315 # We have got little brothers. Readd them all
4316 self
._remove
_metacontact
_family
(old_family
, account_source
)
4318 # We are only a litle brother. Simply remove us from our big
4320 if self
._get
_contact
_iter
(c_source
.jid
, account_source
):
4321 # When we have been in the group before.
4322 # Do not try to remove us again
4323 self
._remove
_entity
(c_source
, account_source
)
4326 own_data
['jid'] = c_source
.jid
4327 own_data
['account'] = account_source
4328 # Don't touch the rest of the family
4329 old_family
= [own_data
]
4331 # Apply new tag and update contact
4332 for data
in old_family
:
4333 if account_source
!= data
['account'] and not self
.regroup
:
4336 _account
= data
['account']
4338 _contact
= gajim
.contacts
.get_first_contact_from_jid(_account
,
4341 # One of the metacontacts may be not connected.
4344 _contact
.groups
= c_dest
.groups
[:]
4345 gajim
.contacts
.add_metacontact(account_dest
, c_dest
.jid
,
4346 _account
, _contact
.jid
, contacts
)
4347 gajim
.connections
[account_source
].update_contact(_contact
.jid
,
4348 _contact
.name
, _contact
.groups
)
4350 # Re-add all and update GUI
4351 new_family
= gajim
.contacts
.get_metacontacts_family(account_source
,
4353 brothers
= self
._add
_metacontact
_family
(new_family
, account_source
)
4355 for c
, acc
in brothers
:
4356 self
.draw_completely(c
.jid
, acc
)
4358 old_groups
.extend(c_dest
.groups
)
4359 for g
in old_groups
:
4360 self
.draw_group(g
, account_source
)
4362 self
.draw_account(account_source
)
4363 context
.finish(True, True, etime
)
4365 confirm_metacontacts
= gajim
.config
.get('confirm_metacontacts')
4366 if confirm_metacontacts
== 'no':
4369 pritext
= _('You are about to create a metacontact. Are you sure you '
4370 'want to continue?')
4371 sectext
= _('Metacontacts are a way to regroup several contacts in one '
4372 'line. Generally it is used when the same person has several '
4373 'Jabber accounts or transport accounts.')
4374 dlg
= dialogs
.ConfirmationDialogCheck(pritext
, sectext
,
4375 _('_Do not ask me again'), on_response_ok
=merge_contacts
)
4376 if not confirm_metacontacts
: # First time we see this window
4377 dlg
.checkbutton
.set_active(True)
4379 def on_drop_in_group(self
, widget
, account
, c_source
, grp_dest
,
4380 is_big_brother
, context
, etime
, grp_source
= None):
4382 # add whole metacontact to new group
4383 self
.add_contact_to_groups(c_source
.jid
, account
, [grp_dest
, ])
4384 # remove afterwards so the contact is not moved to General in the
4386 if grp_dest
!= grp_source
:
4387 self
.remove_contact_from_groups(c_source
.jid
, account
,
4390 # Normal contact or little brother
4391 family
= gajim
.contacts
.get_metacontacts_family(account
,
4395 # Remove whole family. Remove us from the family.
4396 # Then re-add other family members.
4397 self
._remove
_metacontact
_family
(family
, account
)
4398 gajim
.contacts
.remove_metacontact(account
, c_source
.jid
)
4400 if account
!= data
['account'] and not self
.regroup
:
4402 if data
['jid'] == c_source
.jid
and\
4403 data
['account'] == account
:
4405 self
.add_contact(data
['jid'], data
['account'])
4408 self
.add_contact_to_groups(c_source
.jid
, account
, [grp_dest
, ])
4412 self
.add_contact_to_groups(c_source
.jid
, account
, [grp_dest
, ])
4413 # remove afterwards so the contact is not moved to General in
4415 if grp_dest
!= grp_source
:
4416 self
.remove_contact_from_groups(c_source
.jid
, account
,
4419 if context
.action
in (gtk
.gdk
.ACTION_MOVE
, gtk
.gdk
.ACTION_COPY
):
4420 context
.finish(True, True, etime
)
4422 def drag_drop(self
, treeview
, context
, x
, y
, timestamp
):
4423 target_list
= treeview
.drag_dest_get_target_list()
4424 target
= treeview
.drag_dest_find_target(context
, target_list
)
4425 treeview
.drag_get_data(context
, target
)
4426 context
.finish(False, True)
4429 def move_group(self
, old_name
, new_name
, account
):
4430 for group
in gajim
.groups
[account
].keys():
4431 if group
.startswith(old_name
):
4432 self
.rename_group(group
, group
.replace(old_name
, new_name
),
4435 def drag_data_received_data(self
, treeview
, context
, x
, y
, selection
, info
,
4437 treeview
.stop_emission('drag_data_received')
4438 drop_info
= treeview
.get_dest_row_at_pos(x
, y
)
4441 if not selection
.data
:
4442 return # prevents tb when several entrys are dragged
4443 model
= treeview
.get_model()
4444 data
= selection
.data
4445 path_dest
, position
= drop_info
4447 if position
== gtk
.TREE_VIEW_DROP_BEFORE
and len(path_dest
) == 2 \
4448 and path_dest
[1] == 0: # dropped before the first group
4450 if position
== gtk
.TREE_VIEW_DROP_BEFORE
and len(path_dest
) == 2:
4451 # dropped before a group: we drop it in the previous group every
4453 path_dest
= (path_dest
[0], path_dest
[1]-1)
4454 # destination: the row something got dropped on
4455 iter_dest
= model
.get_iter(path_dest
)
4456 type_dest
= model
[iter_dest
][C_TYPE
].decode('utf-8')
4457 jid_dest
= model
[iter_dest
][C_JID
].decode('utf-8')
4458 account_dest
= model
[iter_dest
][C_ACCOUNT
].decode('utf-8')
4460 # drop on account row in merged mode, we cannot know the desired account
4461 if account_dest
== 'all':
4463 # nothing can be done, if destination account is offline
4464 if gajim
.connections
[account_dest
].connected
< 2:
4467 # A file got dropped on the roster
4468 if info
== self
.TARGET_TYPE_URI_LIST
:
4469 if len(path_dest
) < 3:
4471 if type_dest
!= 'contact':
4473 c_dest
= gajim
.contacts
.get_contact_with_highest_priority(
4474 account_dest
, jid_dest
)
4475 if not c_dest
.supports(NS_FILE
):
4478 uri_splitted
= uri
.split() # we may have more than one file dropped
4480 # This is always the last element in windows
4481 uri_splitted
.remove('\0')
4484 nb_uri
= len(uri_splitted
)
4487 for a_uri
in uri_splitted
:
4488 path
= helpers
.get_file_path_from_dnd_dropped_uri(a_uri
)
4489 if not os
.path
.isfile(path
):
4490 bad_uris
.append(a_uri
)
4492 dialogs
.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris
))
4494 def _on_send_files(account
, jid
, uris
):
4495 c
= gajim
.contacts
.get_contact_with_highest_priority(account
,
4498 path
= helpers
.get_file_path_from_dnd_dropped_uri(uri
)
4499 if os
.path
.isfile(path
): # is it file?
4500 gajim
.interface
.instances
['file_transfers'].send_file(
4502 # Popup dialog to confirm sending
4503 prim_text
= 'Send file?'
4504 sec_text
= i18n
.ngettext('Do you want to send this file to %s:',
4505 'Do you want to send these files to %s:', nb_uri
) %\
4506 c_dest
.get_shown_name()
4507 for uri
in uri_splitted
:
4508 path
= helpers
.get_file_path_from_dnd_dropped_uri(uri
)
4509 sec_text
+= '\n' + os
.path
.basename(path
)
4510 dialog
= dialogs
.NonModalConfirmationDialog(prim_text
, sec_text
,
4511 on_response_ok
=(_on_send_files
, account_dest
, jid_dest
,
4516 # a roster entry was dragged and dropped somewhere in the roster
4518 # source: the row that was dragged
4519 path_source
= treeview
.get_selection().get_selected_rows()[1][0]
4520 iter_source
= model
.get_iter(path_source
)
4521 type_source
= model
[iter_source
][C_TYPE
]
4522 account_source
= model
[iter_source
][C_ACCOUNT
].decode('utf-8')
4524 if gajim
.config
.get_per('accounts', account_source
, 'is_zeroconf'):
4527 if type_dest
== 'self_contact':
4528 # drop on self contact row
4531 if type_dest
== 'groupchat':
4532 # drop on a minimized groupchat
4533 # TODO: Invite to groupchat if type_dest = contact
4536 if type_source
== 'group':
4537 if account_source
!= account_dest
:
4538 # drop on another account
4540 grp_source
= model
[iter_source
][C_JID
].decode('utf-8')
4541 delimiter
= gajim
.connections
[account_source
].nested_group_delimiter
4542 grp_source_list
= grp_source
.split(delimiter
)
4544 if type_dest
== 'account':
4545 new_grp
= grp_source_list
[-1]
4546 elif type_dest
== 'group':
4547 new_grp
= model
[iter_dest
][C_JID
].decode('utf-8') + delimiter
+\
4550 self
.move_group(grp_source
, new_grp
, account_source
)
4552 # Only normal contacts and group can be dragged
4553 if type_source
!= 'contact':
4556 # A contact was dropped
4557 if gajim
.config
.get_per('accounts', account_dest
, 'is_zeroconf'):
4558 # drop on zeroconf account, adding not possible
4561 if type_dest
== 'account' and account_source
== account_dest
:
4562 # drop on the account it was dragged from
4565 # Get valid source group, jid and contact
4567 while model
[it
][C_TYPE
] == 'contact':
4568 it
= model
.iter_parent(it
)
4569 grp_source
= model
[it
][C_JID
].decode('utf-8')
4570 if grp_source
in helpers
.special_groups
and \
4571 grp_source
not in ('Not in Roster', 'Observers'):
4572 # a transport or a minimized groupchat was dragged
4573 # we can add it to other accounts but not move it to another group,
4576 jid_source
= data
.decode('utf-8')
4577 c_source
= gajim
.contacts
.get_contact_with_highest_priority(
4578 account_source
, jid_source
)
4580 # Get destination group
4582 if type_dest
== 'group':
4583 grp_dest
= model
[iter_dest
][C_JID
].decode('utf-8')
4584 elif type_dest
in ('contact', 'agent'):
4586 while model
[it
][C_TYPE
] != 'group':
4587 it
= model
.iter_parent(it
)
4588 grp_dest
= model
[it
][C_JID
].decode('utf-8')
4589 if grp_dest
in helpers
.special_groups
:
4592 if jid_source
== jid_dest
:
4593 if grp_source
== grp_dest
and account_source
== account_dest
:
4597 # contact drop somewhere in or on a foreign account
4598 if (type_dest
== 'account' or not self
.regroup
) and \
4599 account_source
!= account_dest
:
4600 # add to account in specified group
4601 dialogs
.AddNewContactWindow(account
=account_dest
, jid
=jid_source
,
4602 user_nick
=c_source
.name
, group
=grp_dest
)
4605 # we may not add contacts from special_groups
4606 if grp_source
in helpers
.special_groups
:
4609 # Is the contact we drag a meta contact?
4610 accounts
= (self
.regroup
and gajim
.contacts
.get_accounts()) or \
4612 is_big_brother
= gajim
.contacts
.is_big_brother(account_source
,
4613 jid_source
, accounts
)
4615 drop_in_middle_of_meta
= False
4616 if type_dest
== 'contact':
4617 if position
== gtk
.TREE_VIEW_DROP_BEFORE
and len(path_dest
) == 4:
4618 drop_in_middle_of_meta
= True
4619 if position
== gtk
.TREE_VIEW_DROP_AFTER
and (len(path_dest
) == 4 or\
4620 self
.modelfilter
.iter_has_child(iter_dest
)):
4621 drop_in_middle_of_meta
= True
4622 # Contact drop on group row or between two contacts that are
4624 if (type_dest
== 'group' or position
in (gtk
.TREE_VIEW_DROP_BEFORE
,
4625 gtk
.TREE_VIEW_DROP_AFTER
)) and not drop_in_middle_of_meta
:
4626 self
.on_drop_in_group(None, account_source
, c_source
, grp_dest
,
4627 is_big_brother
, context
, etime
, grp_source
)
4630 # Contact drop on another contact, make meta contacts
4631 if position
== gtk
.TREE_VIEW_DROP_INTO_OR_AFTER
or \
4632 position
== gtk
.TREE_VIEW_DROP_INTO_OR_BEFORE
or drop_in_middle_of_meta
:
4633 c_dest
= gajim
.contacts
.get_contact_with_highest_priority(
4634 account_dest
, jid_dest
)
4636 # c_dest is None if jid_dest doesn't belong to account
4639 item
= gtk
.MenuItem(_('Send %s to %s') % (c_source
.get_shown_name(),
4640 c_dest
.get_shown_name()))
4641 item
.connect('activate', self
.on_drop_rosterx
, account_source
,
4642 c_source
, account_dest
, c_dest
, is_big_brother
, context
, etime
)
4645 dest_family
= gajim
.contacts
.get_metacontacts_family(account_dest
,
4647 source_family
= gajim
.contacts
.get_metacontacts_family(
4648 account_source
, c_source
.jid
)
4649 if dest_family
== source_family
:
4650 item
= gtk
.MenuItem(_('Make %s first contact') % (
4651 c_source
.get_shown_name()))
4653 item
= gtk
.MenuItem(_('Make %s and %s metacontacts') % (
4654 c_source
.get_shown_name(), c_dest
.get_shown_name()))
4656 item
.connect('activate', self
.on_drop_in_contact
, account_source
,
4657 c_source
, account_dest
, c_dest
, is_big_brother
, context
, etime
)
4661 menu
.attach_to_widget(self
.tree
, None)
4662 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
4664 menu
.popup(None, None, None, 1, etime
)
4666 ################################################################################
4667 ### Everything about images and icons....
4668 ### Cleanup assigned to Jim++ :-)
4669 ################################################################################
4671 def get_appropriate_state_images(self
, jid
, size
='16', icon_name
='online'):
4673 Check jid and return the appropriate state images dict for the demanded
4674 size. icon_name is taken into account when jid is from transport:
4675 transport iconset doesn't contain all icons, so we fall back to jabber
4678 transport
= gajim
.get_transport_name_from_jid(jid
)
4679 if transport
and size
in self
.transports_state_images
:
4680 if transport
not in self
.transports_state_images
[size
]:
4681 # we don't have iconset for this transport loaded yet. Let's do
4683 self
.make_transport_state_images(transport
)
4684 if transport
in self
.transports_state_images
[size
] and \
4685 icon_name
in self
.transports_state_images
[size
][transport
]:
4686 return self
.transports_state_images
[size
][transport
]
4687 return gajim
.interface
.jabber_state_images
[size
]
4689 def make_transport_state_images(self
, transport
):
4691 Initialize opened and closed 'transport' iconset dict
4693 if gajim
.config
.get('use_transports_iconsets'):
4694 folder
= os
.path
.join(helpers
.get_transport_path(transport
),
4696 pixo
, pixc
= gtkgui_helpers
.load_icons_meta()
4697 self
.transports_state_images
['opened'][transport
] = \
4698 gtkgui_helpers
.load_iconset(folder
, pixo
, transport
=True)
4699 self
.transports_state_images
['closed'][transport
] = \
4700 gtkgui_helpers
.load_iconset(folder
, pixc
, transport
=True)
4701 folder
= os
.path
.join(helpers
.get_transport_path(transport
),
4703 self
.transports_state_images
['32'][transport
] = \
4704 gtkgui_helpers
.load_iconset(folder
, transport
=True)
4705 folder
= os
.path
.join(helpers
.get_transport_path(transport
),
4707 self
.transports_state_images
['16'][transport
] = \
4708 gtkgui_helpers
.load_iconset(folder
, transport
=True)
4710 def update_jabber_state_images(self
):
4712 self
.setup_and_draw_roster()
4713 # Update the status combobox
4714 model
= self
.status_combobox
.get_model()
4715 titer
= model
.get_iter_root()
4717 if model
[titer
][2] != '':
4718 # If it's not change status message iter
4719 # eg. if it has show parameter not ''
4720 model
[titer
][1] = gajim
.interface
.jabber_state_images
['16'][
4722 titer
= model
.iter_next(titer
)
4723 # Update the systray
4724 if gajim
.interface
.systray_enabled
:
4725 gajim
.interface
.systray
.set_img()
4727 for win
in gajim
.interface
.msg_win_mgr
.windows():
4728 for ctrl
in win
.controls():
4730 win
.redraw_tab(ctrl
)
4732 self
.update_status_combobox()
4734 def set_account_status_icon(self
, account
):
4735 status
= gajim
.connections
[account
].connected
4736 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
4739 if not self
.regroup
:
4740 show
= gajim
.SHOW_LIST
[status
]
4741 else: # accounts merged
4742 show
= helpers
.get_global_show()
4743 self
.model
[child_iterA
][C_IMG
] = gajim
.interface
.jabber_state_images
[
4746 ################################################################################
4747 ### Style and theme related methods
4748 ################################################################################
4750 def show_title(self
):
4751 change_title_allowed
= gajim
.config
.get('change_roster_title')
4752 if not change_title_allowed
:
4755 if gajim
.config
.get('one_message_window') == 'always_with_roster':
4756 # always_with_roster mode defers to the MessageWindow
4757 if not gajim
.interface
.msg_win_mgr
.one_window_opened():
4758 # No MessageWindow to defer to
4759 self
.window
.set_title('Gajim')
4764 for account
in gajim
.connections
:
4765 # Count events in roster title only if we don't auto open them
4766 if not helpers
.allow_popup_window(account
):
4767 nb_unread
+= gajim
.events
.get_nb_events(['chat', 'normal',
4768 'file-request', 'file-error', 'file-completed',
4769 'file-request-error', 'file-send-error', 'file-stopped',
4770 'printed_chat'], account
)
4772 start
= '[' + str(nb_unread
) + '] '
4773 elif nb_unread
== 1:
4776 self
.window
.set_title(start
+ 'Gajim')
4778 gtkgui_helpers
.set_unset_urgency_hint(self
.window
, nb_unread
)
4780 def _change_style(self
, model
, path
, titer
, option
):
4781 if option
is None or model
[titer
][C_TYPE
] == option
:
4782 # We changed style for this type of row
4783 model
[titer
][C_NAME
] = model
[titer
][C_NAME
]
4785 def change_roster_style(self
, option
):
4786 self
.model
.foreach(self
._change
_style
, option
)
4787 for win
in gajim
.interface
.msg_win_mgr
.windows():
4788 win
.repaint_themed_widgets()
4790 def repaint_themed_widgets(self
):
4792 Notify windows that contain themed widgets to repaint them
4794 for win
in gajim
.interface
.msg_win_mgr
.windows():
4795 win
.repaint_themed_widgets()
4796 for account
in gajim
.connections
:
4797 for addr
in gajim
.interface
.instances
[account
]['disco']:
4798 gajim
.interface
.instances
[account
]['disco'][addr
].paint_banner()
4799 for ctrl
in gajim
.interface
.minimized_controls
[account
].values():
4800 ctrl
.repaint_themed_widgets()
4802 def update_avatar_in_gui(self
, jid
, account
):
4804 self
.draw_avatar(jid
, account
)
4805 # Update chat window
4807 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(jid
, account
)
4811 def set_renderer_color(self
, renderer
, style
, set_background
=True):
4813 Set style for treeview cell, using PRELIGHT system color
4816 bgcolor
= self
.tree
.style
.bg
[style
]
4817 renderer
.set_property('cell-background-gdk', bgcolor
)
4819 fgcolor
= self
.tree
.style
.fg
[style
]
4820 renderer
.set_property('foreground-gdk', fgcolor
)
4822 def _iconCellDataFunc(self
, column
, renderer
, model
, titer
, data
=None):
4824 When a row is added, set properties for icon renderer
4826 type_
= model
[titer
][C_TYPE
]
4827 if type_
== 'account':
4828 self
._set
_account
_row
_background
_color
(renderer
)
4829 renderer
.set_property('xalign', 0)
4830 elif type_
== 'group':
4831 self
._set
_group
_row
_background
_color
(renderer
)
4832 parent_iter
= model
.iter_parent(titer
)
4833 if model
[parent_iter
][C_TYPE
] == 'group':
4834 renderer
.set_property('xalign', 0.4)
4836 renderer
.set_property('xalign', 0.2)
4838 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4839 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4840 # This can append when at the moment we add the row
4842 jid
= model
[titer
][C_JID
].decode('utf-8')
4843 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4844 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4845 parent_iter
= model
.iter_parent(titer
)
4846 if model
[parent_iter
][C_TYPE
] == 'contact':
4847 renderer
.set_property('xalign', 1)
4849 renderer
.set_property('xalign', 0.6)
4850 renderer
.set_property('width', 26)
4852 def _nameCellDataFunc(self
, column
, renderer
, model
, titer
, data
=None):
4854 When a row is added, set properties for name renderer
4856 theme
= gajim
.config
.get('roster_theme')
4857 type_
= model
[titer
][C_TYPE
]
4858 if type_
== 'account':
4859 color
= gajim
.config
.get_per('themes', theme
, 'accounttextcolor')
4861 renderer
.set_property('foreground', color
)
4863 self
.set_renderer_color(renderer
, gtk
.STATE_ACTIVE
, False)
4864 renderer
.set_property('font',
4865 gtkgui_helpers
.get_theme_font_for_option(theme
, 'accountfont'))
4866 renderer
.set_property('xpad', 0)
4867 renderer
.set_property('width', 3)
4868 self
._set
_account
_row
_background
_color
(renderer
)
4869 elif type_
== 'group':
4870 color
= gajim
.config
.get_per('themes', theme
, 'grouptextcolor')
4872 renderer
.set_property('foreground', color
)
4874 self
.set_renderer_color(renderer
, gtk
.STATE_PRELIGHT
, False)
4875 renderer
.set_property('font',
4876 gtkgui_helpers
.get_theme_font_for_option(theme
, 'groupfont'))
4877 parent_iter
= model
.iter_parent(titer
)
4878 if model
[parent_iter
][C_TYPE
] == 'group':
4879 renderer
.set_property('xpad', 8)
4881 renderer
.set_property('xpad', 4)
4882 self
._set
_group
_row
_background
_color
(renderer
)
4884 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4885 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4886 # This can append when at the moment we add the row
4888 jid
= model
[titer
][C_JID
].decode('utf-8')
4889 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4891 if type_
== 'groupchat':
4892 ctrl
= gajim
.interface
.minimized_controls
[account
].get(jid
,
4894 if ctrl
and ctrl
.attention_flag
:
4895 color
= gajim
.config
.get_per('themes', theme
,
4896 'state_muc_directed_msg_color')
4897 renderer
.set_property('foreground', 'red')
4899 color
= gajim
.config
.get_per('themes', theme
,
4902 renderer
.set_property('foreground', color
)
4904 renderer
.set_property('foreground', None)
4905 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4906 renderer
.set_property('font',
4907 gtkgui_helpers
.get_theme_font_for_option(theme
, 'contactfont'))
4908 parent_iter
= model
.iter_parent(titer
)
4909 if model
[parent_iter
][C_TYPE
] == 'contact':
4910 renderer
.set_property('xpad', 16)
4912 renderer
.set_property('xpad', 12)
4914 def _fill_pep_pixbuf_renderer(self
, column
, renderer
, model
, titer
,
4917 When a row is added, draw the respective pep icon
4919 type_
= model
[titer
][C_TYPE
]
4921 # allocate space for the icon only if needed
4922 if not model
[titer
][data
]:
4923 renderer
.set_property('visible', False)
4925 renderer
.set_property('visible', True)
4927 if type_
== 'account':
4928 self
._set
_account
_row
_background
_color
(renderer
)
4929 renderer
.set_property('xalign', 1)
4931 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4932 # This can append at the moment we add the row
4934 jid
= model
[titer
][C_JID
].decode('utf-8')
4935 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4936 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4938 def _fill_avatar_pixbuf_renderer(self
, column
, renderer
, model
, titer
,
4941 When a row is added, set properties for avatar renderer
4943 type_
= model
[titer
][C_TYPE
]
4944 if type_
in ('group', 'account'):
4945 renderer
.set_property('visible', False)
4948 # allocate space for the icon only if needed
4949 if model
[titer
][C_AVATAR_PIXBUF
] or \
4950 gajim
.config
.get('avatar_position_in_roster') == 'left':
4951 renderer
.set_property('visible', True)
4953 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4954 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4955 # This can append at the moment we add the row
4957 jid
= model
[titer
][C_JID
].decode('utf-8')
4958 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4959 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4961 renderer
.set_property('visible', False)
4963 if gajim
.config
.get('avatar_position_in_roster') == 'left':
4964 renderer
.set_property('width', gajim
.config
.get(
4965 'roster_avatar_width'))
4966 renderer
.set_property('xalign', 0.5)
4968 renderer
.set_property('xalign', 1) # align pixbuf to the right
4970 def _fill_padlock_pixbuf_renderer(self
, column
, renderer
, model
, titer
,
4973 When a row is added, set properties for padlock renderer
4975 type_
= model
[titer
][C_TYPE
]
4976 # allocate space for the icon only if needed
4977 if type_
== 'account' and model
[titer
][C_PADLOCK_PIXBUF
]:
4978 renderer
.set_property('visible', True)
4979 self
._set
_account
_row
_background
_color
(renderer
)
4980 renderer
.set_property('xalign', 1) # align pixbuf to the right
4982 renderer
.set_property('visible', False)
4984 def _set_account_row_background_color(self
, renderer
):
4985 theme
= gajim
.config
.get('roster_theme')
4986 color
= gajim
.config
.get_per('themes', theme
, 'accountbgcolor')
4988 renderer
.set_property('cell-background', color
)
4990 self
.set_renderer_color(renderer
, gtk
.STATE_ACTIVE
)
4992 def _set_contact_row_background_color(self
, renderer
, jid
, account
):
4993 theme
= gajim
.config
.get('roster_theme')
4994 if jid
in gajim
.newly_added
[account
]:
4995 renderer
.set_property('cell-background', gajim
.config
.get(
4996 'just_connected_bg_color'))
4997 elif jid
in gajim
.to_be_removed
[account
]:
4998 renderer
.set_property('cell-background', gajim
.config
.get(
4999 'just_disconnected_bg_color'))
5001 color
= gajim
.config
.get_per('themes', theme
, 'contactbgcolor')
5002 renderer
.set_property('cell-background', color
if color
else None)
5004 def _set_group_row_background_color(self
, renderer
):
5005 theme
= gajim
.config
.get('roster_theme')
5006 color
= gajim
.config
.get_per('themes', theme
, 'groupbgcolor')
5008 renderer
.set_property('cell-background', color
)
5010 self
.set_renderer_color(renderer
, gtk
.STATE_PRELIGHT
)
5012 ################################################################################
5013 ### Everything about building menus
5014 ### FIXME: We really need to make it simpler! 1465 lines are a few to much....
5015 ################################################################################
5017 def make_menu(self
, force
=False):
5019 Create the main window's menus
5021 if not force
and not self
.actions_menu_needs_rebuild
:
5023 new_chat_menuitem
= self
.xml
.get_object('new_chat_menuitem')
5024 single_message_menuitem
= self
.xml
.get_object(
5025 'send_single_message_menuitem')
5026 join_gc_menuitem
= self
.xml
.get_object('join_gc_menuitem')
5027 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5029 join_gc_menuitem
.set_image(muc_icon
)
5030 add_new_contact_menuitem
= self
.xml
.get_object(
5031 'add_new_contact_menuitem')
5032 service_disco_menuitem
= self
.xml
.get_object('service_disco_menuitem')
5033 advanced_menuitem
= self
.xml
.get_object('advanced_menuitem')
5034 profile_avatar_menuitem
= self
.xml
.get_object('profile_avatar_menuitem')
5036 # destroy old advanced menus
5037 for m
in self
.advanced_menus
:
5040 # make it sensitive. it is insensitive only if no accounts are
5042 advanced_menuitem
.set_sensitive(True)
5044 if self
.add_new_contact_handler_id
:
5045 add_new_contact_menuitem
.handler_disconnect(
5046 self
.add_new_contact_handler_id
)
5047 self
.add_new_contact_handler_id
= None
5049 if self
.service_disco_handler_id
:
5050 service_disco_menuitem
.handler_disconnect(
5051 self
.service_disco_handler_id
)
5052 self
.service_disco_handler_id
= None
5054 if self
.single_message_menuitem_handler_id
:
5055 single_message_menuitem
.handler_disconnect(
5056 self
.single_message_menuitem_handler_id
)
5057 self
.single_message_menuitem_handler_id
= None
5059 if self
.profile_avatar_menuitem_handler_id
:
5060 profile_avatar_menuitem
.handler_disconnect(
5061 self
.profile_avatar_menuitem_handler_id
)
5062 self
.profile_avatar_menuitem_handler_id
= None
5064 # remove the existing submenus
5065 add_new_contact_menuitem
.remove_submenu()
5066 service_disco_menuitem
.remove_submenu()
5067 join_gc_menuitem
.remove_submenu()
5068 single_message_menuitem
.remove_submenu()
5069 advanced_menuitem
.remove_submenu()
5070 profile_avatar_menuitem
.remove_submenu()
5072 gc_sub_menu
= gtk
.Menu() # gc is always a submenu
5073 join_gc_menuitem
.set_submenu(gc_sub_menu
)
5075 connected_accounts
= gajim
.get_number_of_connected_accounts()
5077 connected_accounts_with_private_storage
= 0
5079 # items that get shown whether an account is zeroconf or not
5080 accounts_list
= sorted(gajim
.contacts
.get_accounts())
5081 if connected_accounts
> 2 or \
5082 (connected_accounts
> 1 and not gajim
.zeroconf_is_connected()):
5083 # 2 or more "real" (no zeroconf) accounts? make submenus
5084 new_chat_sub_menu
= gtk
.Menu()
5086 for account
in accounts_list
:
5087 if gajim
.connections
[account
].connected
<= 1 or \
5088 gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5089 # if offline or connecting or zeroconf
5093 new_chat_item
= gtk
.MenuItem(_('using account %s') % account
,
5095 new_chat_sub_menu
.append(new_chat_item
)
5096 new_chat_item
.connect('activate',
5097 self
.on_new_chat_menuitem_activate
, account
)
5099 new_chat_menuitem
.set_submenu(new_chat_sub_menu
)
5100 new_chat_sub_menu
.show_all()
5102 # menu items that don't apply to zeroconf connections
5103 if connected_accounts
== 1 or (connected_accounts
== 2 and \
5104 gajim
.zeroconf_is_connected()):
5105 # only one 'real' (non-zeroconf) account is connected, don't need
5108 for account
in accounts_list
:
5109 if gajim
.account_is_connected(account
) and \
5110 not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5112 if gajim
.connections
[account
].private_storage_supported
:
5113 connected_accounts_with_private_storage
+= 1
5114 self
.add_bookmarks_list(gc_sub_menu
, account
)
5115 gc_sub_menu
.show_all()
5117 if not self
.add_new_contact_handler_id
:
5118 self
.add_new_contact_handler_id
= \
5119 add_new_contact_menuitem
.connect(
5120 'activate', self
.on_add_new_contact
, account
)
5122 if not self
.service_disco_handler_id
:
5123 self
.service_disco_handler_id
= service_disco_menuitem
.\
5125 self
.on_service_disco_menuitem_activate
, account
)
5128 if not self
.single_message_menuitem_handler_id
:
5129 self
.single_message_menuitem_handler_id
= \
5130 single_message_menuitem
.connect('activate', \
5131 self
.on_send_single_message_menuitem_activate
, account
)
5133 break # No other account connected
5135 # 2 or more 'real' accounts are connected, make submenus
5136 single_message_sub_menu
= gtk
.Menu()
5137 add_sub_menu
= gtk
.Menu()
5138 disco_sub_menu
= gtk
.Menu()
5140 for account
in accounts_list
:
5141 if gajim
.connections
[account
].connected
<= 1 or \
5142 gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5143 # skip account if it's offline or connecting or is zeroconf
5147 single_message_item
= gtk
.MenuItem(_('using account %s') % \
5149 single_message_sub_menu
.append(single_message_item
)
5150 single_message_item
.connect('activate',
5151 self
.on_send_single_message_menuitem_activate
, account
)
5154 if gajim
.connections
[account
].private_storage_supported
:
5155 connected_accounts_with_private_storage
+= 1
5156 gc_item
= gtk
.MenuItem(_('using account %s') % account
, False)
5157 gc_sub_menu
.append(gc_item
)
5158 gc_menuitem_menu
= gtk
.Menu()
5159 self
.add_bookmarks_list(gc_menuitem_menu
, account
)
5160 gc_item
.set_submenu(gc_menuitem_menu
)
5163 add_item
= gtk
.MenuItem(_('to %s account') % account
, False)
5164 add_sub_menu
.append(add_item
)
5165 add_item
.connect('activate', self
.on_add_new_contact
, account
)
5168 disco_item
= gtk
.MenuItem(_('using %s account') % account
,
5170 disco_sub_menu
.append(disco_item
)
5171 disco_item
.connect('activate',
5172 self
.on_service_disco_menuitem_activate
, account
)
5174 single_message_menuitem
.set_submenu(single_message_sub_menu
)
5175 single_message_sub_menu
.show_all()
5176 gc_sub_menu
.show_all()
5177 add_new_contact_menuitem
.set_submenu(add_sub_menu
)
5178 add_sub_menu
.show_all()
5179 service_disco_menuitem
.set_submenu(disco_sub_menu
)
5180 disco_sub_menu
.show_all()
5182 if connected_accounts
== 0:
5183 # no connected accounts, make the menuitems insensitive
5184 for item
in (new_chat_menuitem
, join_gc_menuitem
,
5185 add_new_contact_menuitem
, service_disco_menuitem
,
5186 single_message_menuitem
):
5187 item
.set_sensitive(False)
5188 else: # we have one or more connected accounts
5189 for item
in (new_chat_menuitem
, join_gc_menuitem
,
5190 add_new_contact_menuitem
, service_disco_menuitem
,
5191 single_message_menuitem
):
5192 item
.set_sensitive(True)
5193 # disable some fields if only local account is there
5194 if connected_accounts
== 1:
5195 for account
in gajim
.connections
:
5196 if gajim
.account_is_connected(account
) and \
5197 gajim
.connections
[account
].is_zeroconf
:
5198 for item
in (new_chat_menuitem
, join_gc_menuitem
,
5199 add_new_contact_menuitem
, service_disco_menuitem
,
5200 single_message_menuitem
):
5201 item
.set_sensitive(False)
5203 # Manage GC bookmarks
5204 newitem
= gtk
.SeparatorMenuItem() # separator
5205 gc_sub_menu
.append(newitem
)
5207 newitem
= gtk
.ImageMenuItem(_('_Manage Bookmarks...'))
5208 img
= gtk
.image_new_from_stock(gtk
.STOCK_PREFERENCES
,
5210 newitem
.set_image(img
)
5211 newitem
.connect('activate', self
.on_manage_bookmarks_menuitem_activate
)
5212 gc_sub_menu
.append(newitem
)
5213 gc_sub_menu
.show_all()
5214 if connected_accounts_with_private_storage
== 0:
5215 newitem
.set_sensitive(False)
5217 connected_accounts_with_vcard
= []
5218 for account
in gajim
.connections
:
5219 if gajim
.account_is_connected(account
) and \
5220 gajim
.connections
[account
].vcard_supported
:
5221 connected_accounts_with_vcard
.append(account
)
5222 if len(connected_accounts_with_vcard
) > 1:
5223 # 2 or more accounts? make submenus
5224 profile_avatar_sub_menu
= gtk
.Menu()
5225 for account
in connected_accounts_with_vcard
:
5227 profile_avatar_item
= gtk
.MenuItem(_('of account %s') % account
,
5229 profile_avatar_sub_menu
.append(profile_avatar_item
)
5230 profile_avatar_item
.connect('activate',
5231 self
.on_profile_avatar_menuitem_activate
, account
)
5232 profile_avatar_menuitem
.set_submenu(profile_avatar_sub_menu
)
5233 profile_avatar_sub_menu
.show_all()
5234 elif len(connected_accounts_with_vcard
) == 1:
5235 # user has only one account
5236 account
= connected_accounts_with_vcard
[0]
5238 if not self
.profile_avatar_menuitem_handler_id
:
5239 self
.profile_avatar_menuitem_handler_id
= \
5240 profile_avatar_menuitem
.connect('activate',
5241 self
.on_profile_avatar_menuitem_activate
, account
)
5243 if len(connected_accounts_with_vcard
) == 0:
5244 profile_avatar_menuitem
.set_sensitive(False)
5246 profile_avatar_menuitem
.set_sensitive(True)
5249 if len(gajim
.connections
) == 0: # user has no accounts
5250 advanced_menuitem
.set_sensitive(False)
5251 elif len(gajim
.connections
) == 1: # we have one acccount
5252 account
= gajim
.connections
.keys()[0]
5253 advanced_menuitem_menu
= \
5254 self
.get_and_connect_advanced_menuitem_menu(account
)
5255 self
.advanced_menus
.append(advanced_menuitem_menu
)
5257 self
.add_history_manager_menuitem(advanced_menuitem_menu
)
5259 advanced_menuitem
.set_submenu(advanced_menuitem_menu
)
5260 advanced_menuitem_menu
.show_all()
5261 else: # user has *more* than one account : build advanced submenus
5262 advanced_sub_menu
= gtk
.Menu()
5263 accounts
= [] # Put accounts in a list to sort them
5264 for account
in gajim
.connections
:
5265 accounts
.append(account
)
5267 for account
in accounts
:
5268 advanced_item
= gtk
.MenuItem(_('for account %s') % account
,
5270 advanced_sub_menu
.append(advanced_item
)
5271 advanced_menuitem_menu
= \
5272 self
.get_and_connect_advanced_menuitem_menu(account
)
5273 self
.advanced_menus
.append(advanced_menuitem_menu
)
5274 advanced_item
.set_submenu(advanced_menuitem_menu
)
5276 self
.add_history_manager_menuitem(advanced_sub_menu
)
5278 advanced_menuitem
.set_submenu(advanced_sub_menu
)
5279 advanced_sub_menu
.show_all()
5281 self
.actions_menu_needs_rebuild
= False
5283 def build_account_menu(self
, account
):
5284 # we have to create our own set of icons for the menu
5285 # using self.jabber_status_images is poopoo
5286 iconset
= gajim
.config
.get('iconset')
5287 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5288 state_images
= gtkgui_helpers
.load_iconset(path
)
5290 if not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5291 xml
= gtkgui_helpers
.get_gtk_builder('account_context_menu.ui')
5292 account_context_menu
= xml
.get_object('account_context_menu')
5294 status_menuitem
= xml
.get_object('status_menuitem')
5295 start_chat_menuitem
= xml
.get_object('start_chat_menuitem')
5296 join_group_chat_menuitem
= xml
.get_object(
5297 'join_group_chat_menuitem')
5298 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5300 join_group_chat_menuitem
.set_image(muc_icon
)
5301 open_gmail_inbox_menuitem
= xml
.get_object(
5302 'open_gmail_inbox_menuitem')
5303 add_contact_menuitem
= xml
.get_object('add_contact_menuitem')
5304 service_discovery_menuitem
= xml
.get_object(
5305 'service_discovery_menuitem')
5306 execute_command_menuitem
= xml
.get_object(
5307 'execute_command_menuitem')
5308 edit_account_menuitem
= xml
.get_object('edit_account_menuitem')
5309 sub_menu
= gtk
.Menu()
5310 status_menuitem
.set_submenu(sub_menu
)
5312 for show
in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
5313 uf_show
= helpers
.get_uf_show(show
, use_mnemonic
=True)
5314 item
= gtk
.ImageMenuItem(uf_show
)
5315 icon
= state_images
[show
]
5316 item
.set_image(icon
)
5317 sub_menu
.append(item
)
5318 con
= gajim
.connections
[account
]
5319 if show
== 'invisible' and con
.connected
> 1 and \
5320 not con
.privacy_rules_supported
:
5321 item
.set_sensitive(False)
5323 item
.connect('activate', self
.change_status
, account
, show
)
5325 item
= gtk
.SeparatorMenuItem()
5326 sub_menu
.append(item
)
5328 item
= gtk
.ImageMenuItem(_('_Change Status Message'))
5329 gtkgui_helpers
.add_image_to_menuitem(item
, 'gajim-kbd_input')
5330 sub_menu
.append(item
)
5331 item
.connect('activate', self
.on_change_status_message_activate
,
5333 if gajim
.connections
[account
].connected
< 2:
5334 item
.set_sensitive(False)
5336 item
= gtk
.SeparatorMenuItem()
5337 sub_menu
.append(item
)
5339 uf_show
= helpers
.get_uf_show('offline', use_mnemonic
=True)
5340 item
= gtk
.ImageMenuItem(uf_show
)
5341 icon
= state_images
['offline']
5342 item
.set_image(icon
)
5343 sub_menu
.append(item
)
5344 item
.connect('activate', self
.change_status
, account
, 'offline')
5346 pep_menuitem
= xml
.get_object('pep_menuitem')
5347 if gajim
.connections
[account
].pep_supported
:
5348 pep_submenu
= gtk
.Menu()
5349 pep_menuitem
.set_submenu(pep_submenu
)
5350 def add_item(label
, opt_name
, func
):
5351 item
= gtk
.CheckMenuItem(label
)
5352 pep_submenu
.append(item
)
5353 if not dbus_support
.supported
:
5354 item
.set_sensitive(False)
5356 activ
= gajim
.config
.get_per('accounts', account
,
5358 item
.set_active(activ
)
5359 item
.connect('toggled', func
, account
)
5361 add_item(_('Publish Tune'), 'publish_tune',
5362 self
.on_publish_tune_toggled
)
5363 add_item(_('Publish Location'), 'publish_location',
5364 self
.on_publish_location_toggled
)
5366 pep_config
= gtk
.ImageMenuItem(_('Configure Services...'))
5367 item
= gtk
.SeparatorMenuItem()
5368 pep_submenu
.append(item
)
5369 pep_config
.set_sensitive(True)
5370 pep_submenu
.append(pep_config
)
5371 pep_config
.connect('activate',
5372 self
.on_pep_services_menuitem_activate
, account
)
5373 img
= gtk
.image_new_from_stock(gtk
.STOCK_PREFERENCES
,
5375 pep_config
.set_image(img
)
5378 pep_menuitem
.set_sensitive(False)
5380 if not gajim
.connections
[account
].gmail_url
:
5381 open_gmail_inbox_menuitem
.set_no_show_all(True)
5382 open_gmail_inbox_menuitem
.hide()
5384 open_gmail_inbox_menuitem
.connect('activate',
5385 self
.on_open_gmail_inbox
, account
)
5387 edit_account_menuitem
.connect('activate', self
.on_edit_account
,
5389 add_contact_menuitem
.connect('activate', self
.on_add_new_contact
,
5391 service_discovery_menuitem
.connect('activate',
5392 self
.on_service_disco_menuitem_activate
, account
)
5393 hostname
= gajim
.config
.get_per('accounts', account
, 'hostname')
5394 contact
= gajim
.contacts
.create_contact(jid
=hostname
,
5395 account
=account
) # Fake contact
5396 execute_command_menuitem
.connect('activate',
5397 self
.on_execute_command
, contact
, account
)
5399 start_chat_menuitem
.connect('activate',
5400 self
.on_new_chat_menuitem_activate
, account
)
5402 gc_sub_menu
= gtk
.Menu() # gc is always a submenu
5403 join_group_chat_menuitem
.set_submenu(gc_sub_menu
)
5404 self
.add_bookmarks_list(gc_sub_menu
, account
)
5406 # make some items insensitive if account is offline
5407 if gajim
.connections
[account
].connected
< 2:
5408 for widget
in (add_contact_menuitem
, service_discovery_menuitem
,
5409 join_group_chat_menuitem
, execute_command_menuitem
,
5410 pep_menuitem
, start_chat_menuitem
):
5411 widget
.set_sensitive(False)
5413 xml
= gtkgui_helpers
.get_gtk_builder('zeroconf_context_menu.ui')
5414 account_context_menu
= xml
.get_object('zeroconf_context_menu')
5416 status_menuitem
= xml
.get_object('status_menuitem')
5417 zeroconf_properties_menuitem
= xml
.get_object(
5418 'zeroconf_properties_menuitem')
5419 sub_menu
= gtk
.Menu()
5420 status_menuitem
.set_submenu(sub_menu
)
5422 for show
in ('online', 'away', 'dnd', 'invisible'):
5423 uf_show
= helpers
.get_uf_show(show
, use_mnemonic
=True)
5424 item
= gtk
.ImageMenuItem(uf_show
)
5425 icon
= state_images
[show
]
5426 item
.set_image(icon
)
5427 sub_menu
.append(item
)
5428 item
.connect('activate', self
.change_status
, account
, show
)
5430 item
= gtk
.SeparatorMenuItem()
5431 sub_menu
.append(item
)
5433 item
= gtk
.ImageMenuItem(_('_Change Status Message'))
5434 gtkgui_helpers
.add_image_to_menuitem(item
, 'gajim-kbd_input')
5435 sub_menu
.append(item
)
5436 item
.connect('activate', self
.on_change_status_message_activate
,
5438 if gajim
.connections
[account
].connected
< 2:
5439 item
.set_sensitive(False)
5441 uf_show
= helpers
.get_uf_show('offline', use_mnemonic
=True)
5442 item
= gtk
.ImageMenuItem(uf_show
)
5443 icon
= state_images
['offline']
5444 item
.set_image(icon
)
5445 sub_menu
.append(item
)
5446 item
.connect('activate', self
.change_status
, account
, 'offline')
5448 zeroconf_properties_menuitem
.connect('activate',
5449 self
.on_zeroconf_properties
, account
)
5451 return account_context_menu
5453 def make_account_menu(self
, event
, titer
):
5455 Make account's popup menu
5457 model
= self
.modelfilter
5458 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5460 if account
!= 'all': # not in merged mode
5461 menu
= self
.build_account_menu(account
)
5464 iconset
= gajim
.config
.get('iconset')
5465 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5466 accounts
= [] # Put accounts in a list to sort them
5467 for account
in gajim
.connections
:
5468 accounts
.append(account
)
5470 for account
in accounts
:
5471 state_images
= gtkgui_helpers
.load_iconset(path
)
5472 item
= gtk
.ImageMenuItem(account
)
5473 show
= gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
]
5474 icon
= state_images
[show
]
5475 item
.set_image(icon
)
5476 account_menu
= self
.build_account_menu(account
)
5477 item
.set_submenu(account_menu
)
5480 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5482 menu
.attach_to_widget(self
.tree
, None)
5483 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5485 menu
.popup(None, None, None, event_button
, event
.time
)
5487 def make_group_menu(self
, event
, titer
):
5489 Make group's popup menu
5491 model
= self
.modelfilter
5492 path
= model
.get_path(titer
)
5493 group
= model
[titer
][C_JID
].decode('utf-8')
5494 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5496 list_
= [] # list of (jid, account) tuples
5497 list_online
= [] # list of (jid, account) tuples
5499 group
= model
[titer
][C_JID
]
5500 for jid
in gajim
.contacts
.get_jid_list(account
):
5501 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
5503 if group
in contact
.get_shown_groups():
5504 if contact
.show
not in ('offline', 'error'):
5505 list_online
.append((contact
, account
))
5506 list_
.append((contact
, account
))
5509 # Make special context menu if group is Groupchats
5510 if group
== _('Groupchats'):
5511 maximize_menuitem
= gtk
.ImageMenuItem(_('_Maximize All'))
5512 icon
= gtk
.image_new_from_stock(gtk
.STOCK_GOTO_TOP
,
5514 maximize_menuitem
.set_image(icon
)
5515 maximize_menuitem
.connect('activate',
5516 self
.on_all_groupchat_maximized
, list_
)
5517 menu
.append(maximize_menuitem
)
5519 # Send Group Message
5520 send_group_message_item
= gtk
.ImageMenuItem(
5521 _('Send Group M_essage'))
5522 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
5523 send_group_message_item
.set_image(icon
)
5525 send_group_message_submenu
= gtk
.Menu()
5526 send_group_message_item
.set_submenu(send_group_message_submenu
)
5527 menu
.append(send_group_message_item
)
5529 group_message_to_all_item
= gtk
.MenuItem(_('To all users'))
5530 send_group_message_submenu
.append(group_message_to_all_item
)
5532 group_message_to_all_online_item
= gtk
.MenuItem(
5533 _('To all online users'))
5534 send_group_message_submenu
.append(group_message_to_all_online_item
)
5536 group_message_to_all_online_item
.connect('activate',
5537 self
.on_send_single_message_menuitem_activate
, account
,
5539 group_message_to_all_item
.connect('activate',
5540 self
.on_send_single_message_menuitem_activate
, account
, list_
)
5543 invite_menuitem
= gtk
.ImageMenuItem(_('In_vite to'))
5544 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5546 invite_menuitem
.set_image(muc_icon
)
5548 gui_menu_builder
.build_invite_submenu(invite_menuitem
, list_online
)
5549 menu
.append(invite_menuitem
)
5551 # Send Custom Status
5552 send_custom_status_menuitem
= gtk
.ImageMenuItem(
5553 _('Send Cus_tom Status'))
5554 # add a special img for this menuitem
5555 if helpers
.group_is_blocked(account
, group
):
5556 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
5558 send_custom_status_menuitem
.set_sensitive(False)
5560 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NETWORK
,
5562 send_custom_status_menuitem
.set_image(icon
)
5563 status_menuitems
= gtk
.Menu()
5564 send_custom_status_menuitem
.set_submenu(status_menuitems
)
5565 iconset
= gajim
.config
.get('iconset')
5566 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5567 for s
in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5568 # icon MUST be different instance for every item
5569 state_images
= gtkgui_helpers
.load_iconset(path
)
5570 status_menuitem
= gtk
.ImageMenuItem(helpers
.get_uf_show(s
))
5571 status_menuitem
.connect('activate', self
.on_send_custom_status
,
5573 icon
= state_images
[s
]
5574 status_menuitem
.set_image(icon
)
5575 status_menuitems
.append(status_menuitem
)
5576 menu
.append(send_custom_status_menuitem
)
5578 # there is no singlemessage and custom status for zeroconf
5579 if gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5580 send_custom_status_menuitem
.set_sensitive(False)
5581 send_group_message_item
.set_sensitive(False)
5583 if not group
in helpers
.special_groups
:
5584 item
= gtk
.SeparatorMenuItem() # separator
5588 rename_item
= gtk
.ImageMenuItem(_('Re_name'))
5589 # add a special img for rename menuitem
5590 gtkgui_helpers
.add_image_to_menuitem(rename_item
, 'gajim-kbd_input')
5591 menu
.append(rename_item
)
5592 rename_item
.connect('activate', self
.on_rename
, 'group', group
,
5598 for g_account
in gajim
.connections
:
5599 if helpers
.group_is_blocked(g_account
, group
):
5602 if helpers
.group_is_blocked(account
, group
):
5605 if is_blocked
and gajim
.connections
[account
].\
5606 privacy_rules_supported
:
5607 unblock_menuitem
= gtk
.ImageMenuItem(_('_Unblock'))
5608 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
,
5610 unblock_menuitem
.set_image(icon
)
5611 unblock_menuitem
.connect('activate', self
.on_unblock
, list_
,
5613 menu
.append(unblock_menuitem
)
5615 block_menuitem
= gtk
.ImageMenuItem(_('_Block'))
5616 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
,
5618 block_menuitem
.set_image(icon
)
5619 block_menuitem
.connect('activate', self
.on_block
, list_
, group
)
5620 menu
.append(block_menuitem
)
5621 if not gajim
.connections
[account
].privacy_rules_supported
:
5622 block_menuitem
.set_sensitive(False)
5625 remove_item
= gtk
.ImageMenuItem(_('_Remove'))
5626 icon
= gtk
.image_new_from_stock(gtk
.STOCK_REMOVE
,
5628 remove_item
.set_image(icon
)
5629 menu
.append(remove_item
)
5630 remove_item
.connect('activate', self
.on_remove_group_item_activated
,
5633 # unsensitive if account is not connected
5634 if gajim
.connections
[account
].connected
< 2:
5635 rename_item
.set_sensitive(False)
5637 # General group cannot be changed
5638 if group
== _('General'):
5639 rename_item
.set_sensitive(False)
5640 remove_item
.set_sensitive(False)
5642 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5644 menu
.attach_to_widget(self
.tree
, None)
5645 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5647 menu
.popup(None, None, None, event_button
, event
.time
)
5649 def make_contact_menu(self
, event
, titer
):
5651 Make contact's popup menu
5653 model
= self
.modelfilter
5654 jid
= model
[titer
][C_JID
].decode('utf-8')
5655 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5656 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
5657 menu
= gui_menu_builder
.get_contact_menu(contact
, account
)
5658 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5659 menu
.attach_to_widget(self
.tree
, None)
5660 menu
.popup(None, None, None, event_button
, event
.time
)
5662 def make_multiple_contact_menu(self
, event
, iters
):
5664 Make group's popup menu
5666 model
= self
.modelfilter
5667 list_
= [] # list of (jid, account) tuples
5668 one_account_offline
= False
5670 privacy_rules_supported
= True
5672 jid
= model
[titer
][C_JID
].decode('utf-8')
5673 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5674 if gajim
.connections
[account
].connected
< 2:
5675 one_account_offline
= True
5676 if not gajim
.connections
[account
].privacy_rules_supported
:
5677 privacy_rules_supported
= False
5678 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
5680 if helpers
.jid_is_blocked(account
, jid
):
5682 list_
.append((contact
, account
))
5686 for (contact
, current_account
) in list_
:
5687 # check that we use the same account for every sender
5688 if account
is not None and account
!= current_account
:
5691 account
= current_account
5692 if account
is not None:
5693 send_group_message_item
= gtk
.ImageMenuItem(
5694 _('Send Group M_essage'))
5695 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
5696 send_group_message_item
.set_image(icon
)
5697 menu
.append(send_group_message_item
)
5698 send_group_message_item
.connect('activate',
5699 self
.on_send_single_message_menuitem_activate
, account
, list_
)
5701 # Invite to Groupchat
5702 invite_item
= gtk
.ImageMenuItem(_('In_vite to'))
5703 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5705 invite_item
.set_image(muc_icon
)
5707 gui_menu_builder
.build_invite_submenu(invite_item
, list_
)
5708 menu
.append(invite_item
)
5710 item
= gtk
.SeparatorMenuItem() # separator
5713 # Manage Transport submenu
5714 item
= gtk
.ImageMenuItem(_('_Manage Contacts'))
5715 icon
= gtk
.image_new_from_stock(gtk
.STOCK_PROPERTIES
,
5717 item
.set_image(icon
)
5718 manage_contacts_submenu
= gtk
.Menu()
5719 item
.set_submenu(manage_contacts_submenu
)
5723 edit_groups_item
= gtk
.ImageMenuItem(_('Edit _Groups'))
5724 icon
= gtk
.image_new_from_stock(gtk
.STOCK_EDIT
, gtk
.ICON_SIZE_MENU
)
5725 edit_groups_item
.set_image(icon
)
5726 manage_contacts_submenu
.append(edit_groups_item
)
5727 edit_groups_item
.connect('activate', self
.on_edit_groups
, list_
)
5729 item
= gtk
.SeparatorMenuItem() # separator
5730 manage_contacts_submenu
.append(item
)
5733 if is_blocked
and privacy_rules_supported
:
5734 unblock_menuitem
= gtk
.ImageMenuItem(_('_Unblock'))
5735 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
, gtk
.ICON_SIZE_MENU
)
5736 unblock_menuitem
.set_image(icon
)
5737 unblock_menuitem
.connect('activate', self
.on_unblock
, list_
)
5738 manage_contacts_submenu
.append(unblock_menuitem
)
5740 block_menuitem
= gtk
.ImageMenuItem(_('_Block'))
5741 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
, gtk
.ICON_SIZE_MENU
)
5742 block_menuitem
.set_image(icon
)
5743 block_menuitem
.connect('activate', self
.on_block
, list_
)
5744 manage_contacts_submenu
.append(block_menuitem
)
5746 if not privacy_rules_supported
:
5747 block_menuitem
.set_sensitive(False)
5750 remove_item
= gtk
.ImageMenuItem(_('_Remove'))
5751 icon
= gtk
.image_new_from_stock(gtk
.STOCK_REMOVE
, gtk
.ICON_SIZE_MENU
)
5752 remove_item
.set_image(icon
)
5753 manage_contacts_submenu
.append(remove_item
)
5754 remove_item
.connect('activate', self
.on_req_usub
, list_
)
5755 # unsensitive remove if one account is not connected
5756 if one_account_offline
:
5757 remove_item
.set_sensitive(False)
5759 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5761 menu
.attach_to_widget(self
.tree
, None)
5762 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5764 menu
.popup(None, None, None, event_button
, event
.time
)
5766 def make_transport_menu(self
, event
, titer
):
5768 Make transport's popup menu
5770 model
= self
.modelfilter
5771 jid
= model
[titer
][C_JID
].decode('utf-8')
5772 path
= model
.get_path(titer
)
5773 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5774 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
5777 # Send single message
5778 item
= gtk
.ImageMenuItem(_('Send Single Message'))
5779 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
5780 item
.set_image(icon
)
5781 item
.connect('activate',
5782 self
.on_send_single_message_menuitem_activate
, account
, contact
)
5786 if helpers
.jid_is_blocked(account
, jid
):
5789 # Send Custom Status
5790 send_custom_status_menuitem
= gtk
.ImageMenuItem(
5791 _('Send Cus_tom Status'))
5792 # add a special img for this menuitem
5794 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
5796 send_custom_status_menuitem
.set_sensitive(False)
5798 if account
in gajim
.interface
.status_sent_to_users
and \
5799 jid
in gajim
.interface
.status_sent_to_users
[account
]:
5800 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
5801 gajim
.interface
.status_sent_to_users
[account
][jid
]))
5803 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NETWORK
,
5805 send_custom_status_menuitem
.set_image(icon
)
5806 status_menuitems
= gtk
.Menu()
5807 send_custom_status_menuitem
.set_submenu(status_menuitems
)
5808 iconset
= gajim
.config
.get('iconset')
5809 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5810 for s
in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5811 # icon MUST be different instance for every item
5812 state_images
= gtkgui_helpers
.load_iconset(path
)
5813 status_menuitem
= gtk
.ImageMenuItem(helpers
.get_uf_show(s
))
5814 status_menuitem
.connect('activate', self
.on_send_custom_status
,
5815 [(contact
, account
)], s
)
5816 icon
= state_images
[s
]
5817 status_menuitem
.set_image(icon
)
5818 status_menuitems
.append(status_menuitem
)
5819 menu
.append(send_custom_status_menuitem
)
5821 item
= gtk
.SeparatorMenuItem() # separator
5825 item
= gtk
.ImageMenuItem(_('Execute Command...'))
5826 icon
= gtk
.image_new_from_stock(gtk
.STOCK_EXECUTE
, gtk
.ICON_SIZE_MENU
)
5827 item
.set_image(icon
)
5829 item
.connect('activate', self
.on_execute_command
, contact
, account
,
5831 if gajim
.account_is_disconnected(account
):
5832 item
.set_sensitive(False)
5834 # Manage Transport submenu
5835 item
= gtk
.ImageMenuItem(_('_Manage Transport'))
5836 icon
= gtk
.image_new_from_stock(gtk
.STOCK_PROPERTIES
,
5838 item
.set_image(icon
)
5839 manage_transport_submenu
= gtk
.Menu()
5840 item
.set_submenu(manage_transport_submenu
)
5844 item
= gtk
.ImageMenuItem(_('_Modify Transport'))
5845 icon
= gtk
.image_new_from_stock(gtk
.STOCK_PREFERENCES
,
5847 item
.set_image(icon
)
5848 manage_transport_submenu
.append(item
)
5849 item
.connect('activate', self
.on_edit_agent
, contact
, account
)
5850 if gajim
.account_is_disconnected(account
):
5851 item
.set_sensitive(False)
5854 item
= gtk
.ImageMenuItem(_('_Rename'))
5855 # add a special img for rename menuitem
5856 gtkgui_helpers
.add_image_to_menuitem(item
, 'gajim-kbd_input')
5857 manage_transport_submenu
.append(item
)
5858 item
.connect('activate', self
.on_rename
, 'agent', jid
, account
)
5859 if gajim
.account_is_disconnected(account
):
5860 item
.set_sensitive(False)
5862 item
= gtk
.SeparatorMenuItem() # separator
5863 manage_transport_submenu
.append(item
)
5867 item
= gtk
.ImageMenuItem(_('_Unblock'))
5868 item
.connect('activate', self
.on_unblock
, [(contact
, account
)])
5870 item
= gtk
.ImageMenuItem(_('_Block'))
5871 item
.connect('activate', self
.on_block
, [(contact
, account
)])
5873 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
, gtk
.ICON_SIZE_MENU
)
5874 item
.set_image(icon
)
5875 manage_transport_submenu
.append(item
)
5876 if gajim
.account_is_disconnected(account
):
5877 item
.set_sensitive(False)
5880 item
= gtk
.ImageMenuItem(_('_Remove'))
5881 icon
= gtk
.image_new_from_stock(gtk
.STOCK_REMOVE
, gtk
.ICON_SIZE_MENU
)
5882 item
.set_image(icon
)
5883 manage_transport_submenu
.append(item
)
5884 item
.connect('activate', self
.on_remove_agent
, [(contact
, account
)])
5885 if gajim
.account_is_disconnected(account
):
5886 item
.set_sensitive(False)
5888 item
= gtk
.SeparatorMenuItem() # separator
5892 information_menuitem
= gtk
.ImageMenuItem(_('_Information'))
5893 icon
= gtk
.image_new_from_stock(gtk
.STOCK_INFO
, gtk
.ICON_SIZE_MENU
)
5894 information_menuitem
.set_image(icon
)
5895 menu
.append(information_menuitem
)
5896 information_menuitem
.connect('activate', self
.on_info
, contact
, account
)
5898 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5900 menu
.attach_to_widget(self
.tree
, None)
5901 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5903 menu
.popup(None, None, None, event_button
, event
.time
)
5905 def make_groupchat_menu(self
, event
, titer
):
5906 model
= self
.modelfilter
5908 jid
= model
[titer
][C_JID
].decode('utf-8')
5909 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5910 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
5913 if jid
in gajim
.interface
.minimized_controls
[account
]:
5914 maximize_menuitem
= gtk
.ImageMenuItem(_('_Maximize'))
5915 icon
= gtk
.image_new_from_stock(gtk
.STOCK_GOTO_TOP
,
5917 maximize_menuitem
.set_image(icon
)
5918 maximize_menuitem
.connect('activate', self
.on_groupchat_maximized
, \
5920 menu
.append(maximize_menuitem
)
5922 if not gajim
.gc_connected
[account
].get(jid
, False):
5923 connect_menuitem
= gtk
.ImageMenuItem(_('_Reconnect'))
5924 connect_icon
= gtk
.image_new_from_stock(gtk
.STOCK_CONNECT
, \
5926 connect_menuitem
.set_image(connect_icon
)
5927 connect_menuitem
.connect('activate', self
.on_reconnect
, jid
,
5929 menu
.append(connect_menuitem
)
5930 disconnect_menuitem
= gtk
.ImageMenuItem(_('_Disconnect'))
5931 disconnect_icon
= gtk
.image_new_from_stock(gtk
.STOCK_DISCONNECT
, \
5933 disconnect_menuitem
.set_image(disconnect_icon
)
5934 disconnect_menuitem
.connect('activate', self
.on_disconnect
, jid
,
5936 menu
.append(disconnect_menuitem
)
5938 item
= gtk
.SeparatorMenuItem() # separator
5941 history_menuitem
= gtk
.ImageMenuItem(_('_History'))
5942 history_icon
= gtk
.image_new_from_stock(gtk
.STOCK_JUSTIFY_FILL
, \
5944 history_menuitem
.set_image(history_icon
)
5945 history_menuitem
.connect('activate', self
.on_history
, contact
, account
)
5946 menu
.append(history_menuitem
)
5948 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5950 menu
.attach_to_widget(self
.tree
, None)
5951 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5953 menu
.popup(None, None, None, event_button
, event
.time
)
5955 def get_and_connect_advanced_menuitem_menu(self
, account
):
5957 Add FOR ACCOUNT options
5959 xml
= gtkgui_helpers
.get_gtk_builder('advanced_menuitem_menu.ui')
5960 advanced_menuitem_menu
= xml
.get_object('advanced_menuitem_menu')
5962 xml_console_menuitem
= xml
.get_object('xml_console_menuitem')
5963 archiving_preferences_menuitem
= xml
.get_object(
5964 'archiving_preferences_menuitem')
5965 privacy_lists_menuitem
= xml
.get_object('privacy_lists_menuitem')
5966 administrator_menuitem
= xml
.get_object('administrator_menuitem')
5967 send_server_message_menuitem
= xml
.get_object(
5968 'send_server_message_menuitem')
5969 set_motd_menuitem
= xml
.get_object('set_motd_menuitem')
5970 update_motd_menuitem
= xml
.get_object('update_motd_menuitem')
5971 delete_motd_menuitem
= xml
.get_object('delete_motd_menuitem')
5973 xml_console_menuitem
.connect('activate',
5974 self
.on_xml_console_menuitem_activate
, account
)
5976 if gajim
.connections
[account
]:
5977 if gajim
.connections
[account
].privacy_rules_supported
:
5978 privacy_lists_menuitem
.connect('activate',
5979 self
.on_privacy_lists_menuitem_activate
, account
)
5981 privacy_lists_menuitem
.set_sensitive(False)
5982 if gajim
.connections
[account
].archive_pref_supported
:
5983 archiving_preferences_menuitem
.connect('activate',
5984 self
.on_archiving_preferences_menuitem_activate
, account
)
5986 archiving_preferences_menuitem
.set_sensitive(False)
5988 if gajim
.connections
[account
].is_zeroconf
:
5989 administrator_menuitem
.set_sensitive(False)
5990 send_server_message_menuitem
.set_sensitive(False)
5991 set_motd_menuitem
.set_sensitive(False)
5992 update_motd_menuitem
.set_sensitive(False)
5993 delete_motd_menuitem
.set_sensitive(False)
5995 send_server_message_menuitem
.connect('activate',
5996 self
.on_send_server_message_menuitem_activate
, account
)
5998 set_motd_menuitem
.connect('activate',
5999 self
.on_set_motd_menuitem_activate
, account
)
6001 update_motd_menuitem
.connect('activate',
6002 self
.on_update_motd_menuitem_activate
, account
)
6004 delete_motd_menuitem
.connect('activate',
6005 self
.on_delete_motd_menuitem_activate
, account
)
6007 advanced_menuitem_menu
.show_all()
6009 return advanced_menuitem_menu
6011 def add_history_manager_menuitem(self
, menu
):
6013 Add a seperator and History Manager menuitem BELOW for account menuitems
6015 item
= gtk
.SeparatorMenuItem() # separator
6019 item
= gtk
.ImageMenuItem(_('History Manager'))
6020 icon
= gtk
.image_new_from_stock(gtk
.STOCK_JUSTIFY_FILL
,
6022 item
.set_image(icon
)
6024 item
.connect('activate', self
.on_history_manager_menuitem_activate
)
6026 def add_bookmarks_list(self
, gc_sub_menu
, account
):
6028 Show join new group chat item and bookmarks list for an account
6030 item
= gtk
.ImageMenuItem(_('_Join New Group Chat'))
6031 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
6032 item
.set_image(icon
)
6033 item
.connect('activate', self
.on_join_gc_activate
, account
)
6035 gc_sub_menu
.append(item
)
6037 # User has at least one bookmark.
6038 if gajim
.connections
[account
].bookmarks
:
6039 item
= gtk
.SeparatorMenuItem()
6040 gc_sub_menu
.append(item
)
6042 for bookmark
in gajim
.connections
[account
].bookmarks
:
6043 # Do not use underline.
6044 item
= gtk
.MenuItem(bookmark
['name'], False)
6045 item
.connect('activate', self
.on_bookmark_menuitem_activate
,
6047 gc_sub_menu
.append(item
)
6049 def set_actions_menu_needs_rebuild(self
):
6050 self
.actions_menu_needs_rebuild
= True
6051 # Just handle new_chat_menuitem to have ctrl+N working even if we don't
6053 new_chat_menuitem
= self
.xml
.get_object('new_chat_menuitem')
6054 ag
= gtk
.accel_groups_from_object(self
.window
)[0]
6056 if self
.new_chat_menuitem_handler_id
:
6057 new_chat_menuitem
.handler_disconnect(
6058 self
.new_chat_menuitem_handler_id
)
6059 self
.new_chat_menuitem_handler_id
= None
6061 new_chat_menuitem
.remove_submenu()
6063 connected_accounts
= gajim
.get_number_of_connected_accounts()
6064 if connected_accounts
== 1 or (connected_accounts
== 2 and \
6065 gajim
.zeroconf_is_connected()):
6066 # only one 'real' (non-zeroconf) account is connected, don't need
6068 accounts_list
= sorted(gajim
.contacts
.get_accounts())
6069 for account
in accounts_list
:
6070 if gajim
.account_is_connected(account
) and \
6071 not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
6072 if not self
.new_chat_menuitem_handler_id
:
6073 self
.new_chat_menuitem_handler_id
= new_chat_menuitem
.\
6075 self
.on_new_chat_menuitem_activate
, account
)
6077 def show_appropriate_context_menu(self
, event
, iters
):
6078 # iters must be all of the same type
6079 model
= self
.modelfilter
6080 type_
= model
[iters
[0]][C_TYPE
]
6081 for titer
in iters
[1:]:
6082 if model
[titer
][C_TYPE
] != type_
:
6084 if type_
== 'group' and len(iters
) == 1:
6085 self
.make_group_menu(event
, iters
[0])
6086 if type_
== 'groupchat' and len(iters
) == 1:
6087 self
.make_groupchat_menu(event
, iters
[0])
6088 elif type_
== 'agent' and len(iters
) == 1:
6089 self
.make_transport_menu(event
, iters
[0])
6090 elif type_
in ('contact', 'self_contact') and len(iters
) == 1:
6091 self
.make_contact_menu(event
, iters
[0])
6092 elif type_
== 'contact':
6093 self
.make_multiple_contact_menu(event
, iters
)
6094 elif type_
== 'account' and len(iters
) == 1:
6095 self
.make_account_menu(event
, iters
[0])
6097 def show_treeview_menu(self
, event
):
6099 model
, list_of_paths
= self
.tree
.get_selection().get_selected_rows()
6101 self
.tree
.get_selection().unselect_all()
6103 if not len(list_of_paths
):
6104 # no row is selected
6106 if len(list_of_paths
) > 1:
6108 for path
in list_of_paths
:
6109 iters
.append(model
.get_iter(path
))
6111 path
= list_of_paths
[0]
6112 iters
= [model
.get_iter(path
)]
6113 self
.show_appropriate_context_menu(event
, iters
)
6117 def on_ctrl_j(self
, accel_group
, acceleratable
, keyval
, modifier
):
6119 Bring up the conference join dialog, when CTRL+J accelerator is being
6122 # find a connected account:
6123 for account
in gajim
.connections
:
6124 if gajim
.account_is_connected(account
):
6126 self
.on_join_gc_activate(None, account
)
6129 def fill_column(self
, col
):
6130 for rend
in self
.renderers_list
:
6131 col
.pack_start(rend
[1], expand
=rend
[2])
6132 col
.add_attribute(rend
[1], rend
[3], rend
[4])
6133 col
.set_cell_data_func(rend
[1], rend
[5], rend
[6])
6134 # set renderers propertys
6135 for renderer
in self
.renderers_propertys
.keys():
6136 renderer
.set_property(self
.renderers_propertys
[renderer
][0],
6137 self
.renderers_propertys
[renderer
][1])
6139 ################################################################################
6141 ################################################################################
6144 self
.filtering
= False
6145 # Number of renderers plugins added
6146 self
.nb_ext_renderers
= 0
6147 # [icon, name, type, jid, account, editable, mood_pixbuf,
6148 # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
6150 self
.columns
= [gtk
.Image
, str, str, str, str,
6151 gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
,
6152 gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
]
6153 self
.xml
= gtkgui_helpers
.get_gtk_builder('roster_window.ui')
6154 self
.window
= self
.xml
.get_object('roster_window')
6155 self
.hpaned
= self
.xml
.get_object('roster_hpaned')
6156 gajim
.interface
.msg_win_mgr
= MessageWindowMgr(self
.window
, self
.hpaned
)
6157 gajim
.interface
.msg_win_mgr
.connect('window-delete',
6158 self
.on_message_window_delete
)
6159 self
.advanced_menus
= [] # We keep them to destroy them
6160 if gajim
.config
.get('roster_window_skip_taskbar'):
6161 self
.window
.set_property('skip-taskbar-hint', True)
6162 self
.tree
= self
.xml
.get_object('roster_treeview')
6163 sel
= self
.tree
.get_selection()
6164 sel
.set_mode(gtk
.SELECTION_MULTIPLE
)
6165 # sel.connect('changed',
6166 # self.on_treeview_selection_changed)
6170 self
._iters
['MERGED'] = {'account': None, 'groups': {}}
6171 # holds a list of (jid, account) tupples
6172 self
._last
_selected
_contact
= []
6173 self
.transports_state_images
= {'16': {}, '32': {}, 'opened': {},
6176 self
.last_save_dir
= None
6177 self
.editing_path
= None # path of row with cell in edit mode
6178 self
.add_new_contact_handler_id
= False
6179 self
.service_disco_handler_id
= False
6180 self
.new_chat_menuitem_handler_id
= False
6181 self
.single_message_menuitem_handler_id
= False
6182 self
.profile_avatar_menuitem_handler_id
= False
6183 #FIXME: When list_accel_closures will be wrapped in pygtk
6184 # no need of this variable
6185 self
.have_new_chat_accel
= False # Is the "Ctrl+N" shown ?
6186 self
.set_actions_menu_needs_rebuild()
6187 self
.regroup
= gajim
.config
.get('mergeaccounts')
6188 self
.clicked_path
= None # Used remember on wich row we clicked
6189 if len(gajim
.connections
) < 2:
6190 # Do not merge accounts if only one exists
6191 self
.regroup
= False
6192 gtkgui_helpers
.resize_window(self
.window
,
6193 gajim
.config
.get('roster_width'),
6194 gajim
.config
.get('roster_height'))
6195 if gajim
.config
.get('save-roster-position'):
6196 gtkgui_helpers
.move_window(self
.window
,
6197 gajim
.config
.get('roster_x-position'),
6198 gajim
.config
.get('roster_y-position'))
6200 self
.popups_notification_height
= 0
6201 self
.popup_notification_windows
= []
6203 # Remove contact from roster when last event opened
6204 # { (contact, account): { backend: boolean }
6205 self
.contacts_to_be_removed
= {}
6206 gajim
.events
.event_removed_subscribe(self
.on_event_removed
)
6208 # when this value become 0 we quit main application. If it's more than 0
6209 # it means we are waiting for this number of accounts to disconnect
6211 self
.quit_on_next_offline
= -1
6213 # uf_show, img, show, sensitive
6214 liststore
= gtk
.ListStore(str, gtk
.Image
, str, bool)
6215 self
.status_combobox
= self
.xml
.get_object('status_combobox')
6217 cell
= cell_renderer_image
.CellRendererImage(0, 1)
6218 self
.status_combobox
.pack_start(cell
, False)
6220 # img to show is in in 2nd column of liststore
6221 self
.status_combobox
.add_attribute(cell
, 'image', 1)
6222 # if it will be sensitive or not it is in the fourth column
6223 # all items in the 'row' must have sensitive to False
6224 # if we want False (so we add it for img_cell too)
6225 self
.status_combobox
.add_attribute(cell
, 'sensitive', 3)
6227 cell
= gtk
.CellRendererText()
6228 cell
.set_property('xpad', 5) # padding for status text
6229 self
.status_combobox
.pack_start(cell
, True)
6230 # text to show is in in first column of liststore
6231 self
.status_combobox
.add_attribute(cell
, 'text', 0)
6232 # if it will be sensitive or not it is in the fourth column
6233 self
.status_combobox
.add_attribute(cell
, 'sensitive', 3)
6235 self
.status_combobox
.set_row_separator_func(self
._iter
_is
_separator
)
6237 for show
in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
6238 uf_show
= helpers
.get_uf_show(show
)
6239 liststore
.append([uf_show
,
6240 gajim
.interface
.jabber_state_images
['16'][show
], show
, True])
6241 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6242 liststore
.append(['SEPARATOR', None, '', True])
6244 path
= gtkgui_helpers
.get_icon_path('gajim-kbd_input')
6246 img
.set_from_file(path
)
6247 # sensitivity to False because by default we're offline
6248 self
.status_message_menuitem_iter
= liststore
.append(
6249 [_('Change Status Message...'), img
, '', False])
6250 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6251 liststore
.append(['SEPARATOR', None, '', True])
6253 uf_show
= helpers
.get_uf_show('offline')
6254 liststore
.append([uf_show
, gajim
.interface
.jabber_state_images
['16'][
6255 'offline'], 'offline', True])
6257 status_combobox_items
= ['online', 'chat', 'away', 'xa', 'dnd',
6258 'invisible', 'separator1', 'change_status_msg', 'separator2',
6260 self
.status_combobox
.set_model(liststore
)
6262 # default to offline
6263 number_of_menuitem
= status_combobox_items
.index('offline')
6264 self
.status_combobox
.set_active(number_of_menuitem
)
6266 # holds index to previously selected item so if
6267 # "change status message..." is selected we can fallback to previously
6268 # selected item and not stay with that item selected
6269 self
.previous_status_combobox_active
= number_of_menuitem
6271 showOffline
= gajim
.config
.get('showoffline')
6272 showOnlyChatAndOnline
= gajim
.config
.get('show_only_chat_and_online')
6274 w
= self
.xml
.get_object('show_offline_contacts_menuitem')
6275 w
.set_active(showOffline
)
6276 if showOnlyChatAndOnline
:
6277 w
.set_sensitive(False)
6279 w
= self
.xml
.get_object('show_only_active_contacts_menuitem')
6280 w
.set_active(showOnlyChatAndOnline
)
6282 w
.set_sensitive(False)
6284 show_transports_group
= gajim
.config
.get('show_transports_group')
6285 self
.xml
.get_object('show_transports_menuitem').set_active(
6286 show_transports_group
)
6288 self
.xml
.get_object('show_roster_menuitem').set_active(True)
6291 col
= gtk
.TreeViewColumn()
6292 # list of renderers with attributes / properties in the form:
6293 # (name, renderer_object, expand?, attribute_name, attribute_value,
6294 # cell_data_func, func_arg)
6295 self
.renderers_list
= []
6296 self
.renderers_propertys
={}
6297 self
._pep
_type
_to
_model
_column
= {'mood': C_MOOD_PIXBUF
,
6298 'activity': C_ACTIVITY_PIXBUF
, 'tune': C_TUNE_PIXBUF
,
6299 'location': C_LOCATION_PIXBUF
}
6301 renderer_text
= gtk
.CellRendererText()
6302 self
.renderers_propertys
[renderer_text
] = ('ellipsize',
6303 pango
.ELLIPSIZE_END
)
6305 def add_avatar_renderer():
6306 self
.renderers_list
.append(('avatar', gtk
.CellRendererPixbuf(),
6307 False, 'pixbuf', C_AVATAR_PIXBUF
,
6308 self
._fill
_avatar
_pixbuf
_renderer
, None))
6310 if gajim
.config
.get('avatar_position_in_roster') == 'left':
6311 add_avatar_renderer()
6313 self
.renderers_list
+= (
6314 ('icon', cell_renderer_image
.CellRendererImage(0, 0), False,
6315 'image', C_IMG
, self
._iconCellDataFunc
, None),
6317 ('name', renderer_text
, True,
6318 'markup', C_NAME
, self
._nameCellDataFunc
, None),
6320 ('mood', gtk
.CellRendererPixbuf(), False,
6321 'pixbuf', C_MOOD_PIXBUF
,
6322 self
._fill
_pep
_pixbuf
_renderer
, C_MOOD_PIXBUF
),
6324 ('activity', gtk
.CellRendererPixbuf(), False,
6325 'pixbuf', C_ACTIVITY_PIXBUF
,
6326 self
._fill
_pep
_pixbuf
_renderer
, C_ACTIVITY_PIXBUF
),
6328 ('tune', gtk
.CellRendererPixbuf(), False,
6329 'pixbuf', C_TUNE_PIXBUF
,
6330 self
._fill
_pep
_pixbuf
_renderer
, C_TUNE_PIXBUF
),
6332 ('location', gtk
.CellRendererPixbuf(), False,
6333 'pixbuf', C_LOCATION_PIXBUF
,
6334 self
._fill
_pep
_pixbuf
_renderer
, C_LOCATION_PIXBUF
))
6336 if gajim
.config
.get('avatar_position_in_roster') == 'right':
6337 add_avatar_renderer()
6339 self
.renderers_list
.append(('padlock', gtk
.CellRendererPixbuf(), False,
6340 'pixbuf', C_PADLOCK_PIXBUF
,
6341 self
._fill
_padlock
_pixbuf
_renderer
, None))
6343 # fill and append column
6344 self
.fill_column(col
)
6345 self
.tree
.append_column(col
)
6347 # do not show gtk arrows workaround
6348 col
= gtk
.TreeViewColumn()
6349 render_pixbuf
= gtk
.CellRendererPixbuf()
6350 col
.pack_start(render_pixbuf
, expand
=False)
6351 self
.tree
.append_column(col
)
6352 col
.set_visible(False)
6353 self
.tree
.set_expander_column(col
)
6355 # set search function
6356 self
.tree
.set_search_equal_func(self
._search
_roster
_func
)
6359 self
.TARGET_TYPE_URI_LIST
= 80
6360 TARGETS
= [('MY_TREE_MODEL_ROW',
6361 gtk
.TARGET_SAME_APP | gtk
.TARGET_SAME_WIDGET
, 0)]
6362 TARGETS2
= [('MY_TREE_MODEL_ROW', gtk
.TARGET_SAME_WIDGET
, 0),
6363 ('text/uri-list', 0, self
.TARGET_TYPE_URI_LIST
)]
6364 self
.tree
.enable_model_drag_source(gtk
.gdk
.BUTTON1_MASK
, TARGETS
,
6365 gtk
.gdk
.ACTION_DEFAULT | gtk
.gdk
.ACTION_MOVE | gtk
.gdk
.ACTION_COPY
)
6366 self
.tree
.enable_model_drag_dest(TARGETS2
, gtk
.gdk
.ACTION_DEFAULT
)
6367 self
.tree
.connect('drag_begin', self
.drag_begin
)
6368 self
.tree
.connect('drag_end', self
.drag_end
)
6369 self
.tree
.connect('drag_drop', self
.drag_drop
)
6370 self
.tree
.connect('drag_data_get', self
.drag_data_get_data
)
6371 self
.tree
.connect('drag_data_received', self
.drag_data_received_data
)
6372 self
.dragging
= False
6373 self
.xml
.connect_signals(self
)
6374 self
.combobox_callback_active
= True
6376 self
.collapsed_rows
= gajim
.config
.get('collapsed_rows').split('\t')
6377 self
.tooltip
= tooltips
.RosterTooltip()
6378 # Workaroung: For strange reasons signal is behaving like row-changed
6379 self
._toggeling
_row
= False
6380 self
.setup_and_draw_roster()
6382 if gajim
.config
.get('show_roster_on_startup') == 'always':
6383 self
.window
.show_all()
6384 elif gajim
.config
.get('show_roster_on_startup') == 'never':
6385 if gajim
.config
.get('trayicon') != 'always':
6386 # Without trayicon, user should see the roster!
6387 self
.window
.show_all()
6388 gajim
.config
.set('last_roster_visible', True)
6390 if gajim
.config
.get('last_roster_visible') or \
6391 gajim
.config
.get('trayicon') != 'always':
6392 self
.window
.show_all()
6394 if len(gajim
.connections
) == 0: # if we have no account
6396 gajim
.interface
.instances
['account_creation_wizard'] = \
6397 config
.AccountCreationWizardWindow()
6398 # Open wizard only after roster is created, so we can make it
6399 # transient for the roster window
6400 gobject
.idle_add(_open_wizard
)
6401 if not gajim
.ZEROCONF_ACC_NAME
in gajim
.config
.get_per('accounts'):
6402 # Create zeroconf in config file
6403 from common
.zeroconf
import connection_zeroconf
6404 connection_zeroconf
.ConnectionZeroconf(gajim
.ZEROCONF_ACC_NAME
)
6406 # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
6408 accel_group
= gtk
.accel_groups_from_object(self
.window
)[0]
6409 accel_group
.connect_group(gtk
.keysyms
.j
, gtk
.gdk
.CONTROL_MASK
,
6410 gtk
.ACCEL_MASK
, self
.on_ctrl_j
)
6412 # Setting CTRL+N to be the shortcut for show Start chat dialog
6413 new_chat_menuitem
= self
.xml
.get_object('new_chat_menuitem')
6414 new_chat_menuitem
.add_accelerator('activate', accel_group
,
6415 gtk
.keysyms
.n
, gtk
.gdk
.CONTROL_MASK
, gtk
.ACCEL_VISIBLE
)
6417 # Setting the search stuff
6418 self
.rfilter_entry
= self
.xml
.get_object('rfilter_entry')
6419 self
.rfilter_string
= ''
6420 self
.rfilter_enabled
= False
6422 gajim
.ged
.register_event_handler('presence-received', ged
.GUI1
,
6423 self
._nec
_presence
_received
)
6424 # presence has to be fully handled so that contact is added to occupant
6425 # list before roster can be correctly updated
6426 gajim
.ged
.register_event_handler('gc-presence-received', ged
.GUI2
,
6427 self
._nec
_gc
_presence
_received
)
6428 gajim
.ged
.register_event_handler('roster-received', ged
.GUI1
,
6429 self
._nec
_roster
_received
)
6430 gajim
.ged
.register_event_handler('anonymous-auth', ged
.GUI1
,
6431 self
._nec
_anonymous
_auth
)
6432 gajim
.ged
.register_event_handler('our-show', ged
.GUI1
,
6434 gajim
.ged
.register_event_handler('connection-type', ged
.GUI1
,
6435 self
._nec
_connection
_type
)
6436 gajim
.ged
.register_event_handler('agent-removed', ged
.GUI1
,
6437 self
._nec
_agent
_removed
)
6438 gajim
.ged
.register_event_handler('pep-received', ged
.GUI1
,
6439 self
._nec
_pep
_received
)
6440 gajim
.ged
.register_event_handler('vcard-received', ged
.GUI1
,
6441 self
._nec
_vcard
_received
)
6442 gajim
.ged
.register_event_handler('gc-subject-received', ged
.GUI1
,
6443 self
._nec
_gc
_subject
_received
)
6444 gajim
.ged
.register_event_handler('metacontacts-received', ged
.GUI2
,
6445 self
._nec
_metacontacts
_received
)
6446 gajim
.ged
.register_event_handler('signed-in', ged
.GUI1
,
6447 self
._nec
_signed
_in
)
6448 gajim
.ged
.register_event_handler('decrypted-message-received', ged
.GUI2
,
6449 self
._nec
_decrypted
_message
_received
)