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': {}}
1421 for acct
in gajim
.contacts
.get_accounts():
1422 self
._iters
[acct
] = {'account': None, 'groups': {}, 'contacts': {}}
1424 for acct
in gajim
.contacts
.get_accounts():
1425 self
.add_account(acct
)
1426 self
.add_account_contacts(acct
)
1428 # Recalculate column width for ellipsizing
1429 self
.tree
.columns_autosize()
1432 def select_contact(self
, jid
, account
):
1434 Select contact in roster. If contact is hidden but has events, show him
1436 # Refiltering SHOULD NOT be needed:
1437 # When a contact gets a new event he will be redrawn and his
1438 # icon changes, so _visible_func WILL be called on him anyway
1439 iters
= self
._get
_contact
_iter
(jid
, account
)
1441 # Not visible in roster
1443 path
= self
.modelfilter
.get_path(iters
[0])
1444 if self
.dragging
or not gajim
.config
.get(
1445 'scroll_roster_to_last_message'):
1446 # do not change selection while DND'ing
1448 # Expand his parent, so this path is visible, don't expand it.
1449 self
.tree
.expand_to_path(path
[:-1])
1450 self
.tree
.scroll_to_cell(path
)
1451 self
.tree
.set_cursor(path
)
1454 def _adjust_account_expand_collapse_state(self
, account
):
1456 Expand/collapse account row based on self.collapsed_rows
1458 if not self
.tree
.get_model():
1460 iterA
= self
._get
_account
_iter
(account
)
1462 # thank you modelfilter
1464 path
= self
.modelfilter
.get_path(iterA
)
1465 if account
in self
.collapsed_rows
:
1466 self
.tree
.collapse_row(path
)
1468 self
.tree
.expand_row(path
, False)
1472 def _adjust_group_expand_collapse_state(self
, group
, account
):
1474 Expand/collapse group row based on self.collapsed_rows
1476 if not self
.tree
.get_model():
1478 delimiter
= gajim
.connections
[account
].nested_group_delimiter
1479 group_splited
= group
.split(delimiter
)
1481 while i
< len(group_splited
) + 1:
1482 g
= delimiter
.join(group_splited
[:i
])
1483 iterG
= self
._get
_group
_iter
(g
, account
)
1487 path
= self
.modelfilter
.get_path(iterG
)
1488 if account
+ g
in self
.collapsed_rows
:
1489 self
.tree
.collapse_row(path
)
1491 self
.tree
.expand_row(path
, False)
1494 ##############################################################################
1495 ### Roster and Modelfilter handling
1496 ##############################################################################
1498 def refilter_shown_roster_items(self
):
1499 self
.filtering
= True
1500 self
.modelfilter
.refilter()
1501 self
.filtering
= False
1503 def contact_has_pending_roster_events(self
, contact
, account
):
1505 Return True if the contact or one if it resources has pending events
1507 # jid has pending events
1508 if gajim
.events
.get_nb_roster_events(account
, contact
.jid
) > 0:
1510 # check events of all resources
1511 for contact_
in gajim
.contacts
.get_contacts(account
, contact
.jid
):
1512 if contact_
.resource
and gajim
.events
.get_nb_roster_events(account
,
1513 contact_
.get_full_jid()) > 0:
1517 def contact_is_visible(self
, contact
, account
):
1518 if self
.contact_has_pending_roster_events(contact
, account
):
1521 if contact
.show
in ('offline', 'error'):
1522 if contact
.jid
in gajim
.to_be_removed
[account
]:
1525 if gajim
.config
.get('show_only_chat_and_online') and contact
.show
in (
1526 'away', 'xa', 'busy'):
1530 def _visible_func(self
, model
, titer
):
1532 Determine whether iter should be visible in the treeview
1534 type_
= model
[titer
][C_TYPE
]
1537 if type_
== 'account':
1538 # Always show account
1541 account
= model
[titer
][C_ACCOUNT
]
1545 account
= account
.decode('utf-8')
1546 jid
= model
[titer
][C_JID
]
1549 jid
= jid
.decode('utf-8')
1550 if type_
== 'group':
1552 if group
== _('Transports'):
1554 accounts
= gajim
.contacts
.get_accounts()
1556 accounts
= [account
]
1557 for _acc
in accounts
:
1558 for contact
in gajim
.contacts
.iter_contacts(_acc
):
1559 if group
in contact
.get_shown_groups() and \
1560 self
.contact_has_pending_roster_events(contact
, _acc
):
1562 return gajim
.config
.get('show_transports_group') and \
1563 (gajim
.account_is_connected(account
) or \
1564 gajim
.config
.get('showoffline'))
1565 if gajim
.config
.get('showoffline'):
1569 # C_ACCOUNT for groups depends on the order
1570 # accounts were connected
1571 # Check all accounts for online group contacts
1572 accounts
= gajim
.contacts
.get_accounts()
1574 accounts
= [account
]
1575 for _acc
in accounts
:
1576 delimiter
= gajim
.connections
[_acc
].nested_group_delimiter
1577 for contact
in gajim
.contacts
.iter_contacts(_acc
):
1578 if not self
.contact_is_visible(contact
, _acc
):
1580 # Is this contact in this group?
1581 for grp
in contact
.get_shown_groups():
1585 grp
= delimiter
.join(grp
.split(delimiter
)[:-1])
1587 if type_
== 'contact':
1588 if self
.rfilter_enabled
:
1589 return self
.rfilter_string
in model
[titer
][C_NAME
].lower()
1590 if gajim
.config
.get('showoffline'):
1594 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
1596 nearby_family
, bb_jid
, bb_account
= \
1597 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)
1598 if (bb_jid
, bb_account
) == (jid
, account
):
1599 # Show the big brother if a child has pending events
1600 for data
in nearby_family
:
1602 account
= data
['account']
1603 contact
= gajim
.contacts
.get_contact_with_highest_priority(
1605 if contact
and self
.contact_is_visible(contact
, account
):
1609 contact
= gajim
.contacts
.get_contact_with_highest_priority(
1611 return self
.contact_is_visible(contact
, account
)
1612 if type_
== 'agent':
1613 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
1615 return self
.contact_has_pending_roster_events(contact
, account
) or \
1616 (gajim
.config
.get('show_transports_group') and \
1617 (gajim
.account_is_connected(account
) or \
1618 gajim
.config
.get('showoffline')))
1621 def _compareIters(self
, model
, iter1
, iter2
, data
=None):
1623 Compare two iters to sort them
1625 name1
= model
[iter1
][C_NAME
]
1626 name2
= model
[iter2
][C_NAME
]
1627 if not name1
or not name2
:
1629 name1
= name1
.decode('utf-8')
1630 name2
= name2
.decode('utf-8')
1631 type1
= model
[iter1
][C_TYPE
]
1632 type2
= model
[iter2
][C_TYPE
]
1633 if type1
== 'self_contact':
1635 if type2
== 'self_contact':
1637 if type1
== 'group':
1638 name1
= model
[iter1
][C_JID
]
1639 name2
= model
[iter2
][C_JID
]
1640 if name1
== _('Transports'):
1642 if name2
== _('Transports'):
1644 if name1
== _('Not in Roster'):
1646 if name2
== _('Not in Roster'):
1648 if name1
== _('Groupchats'):
1650 if name2
== _('Groupchats'):
1652 account1
= model
[iter1
][C_ACCOUNT
]
1653 account2
= model
[iter2
][C_ACCOUNT
]
1654 if not account1
or not account2
:
1656 account1
= account1
.decode('utf-8')
1657 account2
= account2
.decode('utf-8')
1658 if type1
== 'account':
1659 return locale
.strcoll(account1
, account2
)
1660 jid1
= model
[iter1
][C_JID
].decode('utf-8')
1661 jid2
= model
[iter2
][C_JID
].decode('utf-8')
1662 if type1
== 'contact':
1663 lcontact1
= gajim
.contacts
.get_contacts(account1
, jid1
)
1664 contact1
= gajim
.contacts
.get_first_contact_from_jid(account1
, jid1
)
1667 name1
= contact1
.get_shown_name()
1668 if type2
== 'contact':
1669 lcontact2
= gajim
.contacts
.get_contacts(account2
, jid2
)
1670 contact2
= gajim
.contacts
.get_first_contact_from_jid(account2
, jid2
)
1673 name2
= contact2
.get_shown_name()
1674 # We first compare by show if sort_by_show_in_roster is True or if it's
1676 if type1
== 'contact' and type2
== 'contact' and \
1677 gajim
.config
.get('sort_by_show_in_roster'):
1678 cshow
= {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
1679 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
1680 s
= self
.get_show(lcontact1
)
1681 show1
= cshow
.get(s
, 9)
1682 s
= self
.get_show(lcontact2
)
1683 show2
= cshow
.get(s
, 9)
1686 if show1
== 6 and jid1
in gajim
.to_be_removed
[account1
]:
1688 if show2
== 6 and jid2
in gajim
.to_be_removed
[account2
]:
1690 if removing1
and not removing2
:
1692 if removing2
and not removing1
:
1696 # none and from goes after
1697 if sub1
not in ['none', 'from'] and sub2
in ['none', 'from']:
1699 if sub1
in ['none', 'from'] and sub2
not in ['none', 'from']:
1706 cmp_result
= locale
.strcoll(name1
.lower(), name2
.lower())
1711 if type1
== 'contact' and type2
== 'contact':
1712 # We compare account names
1713 cmp_result
= locale
.strcoll(account1
.lower(), account2
.lower())
1719 cmp_result
= locale
.strcoll(jid1
.lower(), jid2
.lower())
1726 ################################################################################
1727 ### FIXME: Methods that don't belong to roster window...
1728 ### ... atleast not in there current form
1729 ################################################################################
1731 def fire_up_unread_messages_events(self
, account
):
1733 Read from db the unread messages, and fire them up, and if we find very
1734 old unread messages, delete them from unread table
1736 results
= gajim
.logger
.get_unread_msgs()
1737 for result
in results
:
1740 if gajim
.contacts
.get_first_contact_from_jid(account
, jid
) and not \
1742 # We have this jid in our contacts list
1743 # XXX unread messages should probably have their session saved
1745 session
= gajim
.connections
[account
].make_new_session(jid
)
1747 tim
= time
.localtime(float(result
[2]))
1748 session
.roster_message(jid
, result
[1], tim
, msg_type
='chat',
1750 gajim
.logger
.set_shown_unread_msgs(result
[0])
1752 elif (time
.time() - result
[2]) > 2592000:
1753 # ok, here we see that we have a message in unread messages
1754 # table that is older than a month. It is probably from someone
1755 # not in our roster for accounts we usually launch, so we will
1756 # delete this id from unread message tables.
1757 gajim
.logger
.set_read_messages([result
[0]])
1759 def fill_contacts_and_groups_dicts(self
, array
, account
):
1761 Fill gajim.contacts and gajim.groups
1763 # FIXME: This function needs to be splitted
1764 # Most of the logic SHOULD NOT be done at GUI level
1765 if account
not in gajim
.contacts
.get_accounts():
1766 gajim
.contacts
.add_account(account
)
1767 if not account
in self
._iters
:
1768 self
._iters
[account
] = {'account': None, 'groups': {},
1770 if account
not in gajim
.groups
:
1771 gajim
.groups
[account
] = {}
1772 if gajim
.config
.get('show_self_contact') == 'always':
1773 self_jid
= gajim
.get_jid_from_account(account
)
1774 if gajim
.connections
[account
].server_resource
:
1775 self_jid
+= '/' + gajim
.connections
[account
].server_resource
1776 array
[self_jid
] = {'name': gajim
.nicks
[account
],
1777 'groups': ['self_contact'], 'subscription': 'both',
1780 for jid
in array
.keys():
1781 # Remove the contact in roster. It might has changed
1782 self
.remove_contact(jid
, account
, force
=True)
1783 # Remove old Contact instances
1784 gajim
.contacts
.remove_jid(account
, jid
, remove_meta
=False)
1785 jids
= jid
.split('/')
1791 resource
= '/'.join(jids
[1:])
1793 name
= array
[jid
]['name'] or ''
1794 show
= 'offline' # show is offline by default
1795 status
= '' # no status message by default
1798 attached_keys
= gajim
.config
.get_per('accounts', account
,
1799 'attached_gpg_keys').split()
1800 if jid
in attached_keys
:
1801 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
1803 if gajim
.jid_is_transport(jid
):
1804 array
[jid
]['groups'] = [_('Transports')]
1806 contact1
= gajim
.contacts
.create_contact(jid
=ji
, account
=account
,
1807 name
=name
, groups
=array
[jid
]['groups'], show
=show
,
1808 status
=status
, sub
=array
[jid
]['subscription'],
1809 ask
=array
[jid
]['ask'], resource
=resource
, keyID
=keyID
)
1810 gajim
.contacts
.add_contact(account
, contact1
)
1812 if gajim
.config
.get('ask_avatars_on_startup'):
1813 pixbuf
= gtkgui_helpers
.get_avatar_pixbuf_from_cache(ji
)
1815 transport
= gajim
.get_transport_name_from_jid(contact1
.jid
)
1816 if not transport
or gajim
.jid_is_transport(contact1
.jid
):
1817 jid_with_resource
= contact1
.jid
1818 if contact1
.resource
:
1819 jid_with_resource
+= '/' + contact1
.resource
1820 gajim
.connections
[account
].request_vcard(
1823 host
= gajim
.get_server_from_jid(contact1
.jid
)
1824 if host
not in gajim
.transport_avatar
[account
]:
1825 gajim
.transport_avatar
[account
][host
] = \
1828 gajim
.transport_avatar
[account
][host
].append(
1831 # If we already have chat windows opened, update them with new
1833 chat_control
= gajim
.interface
.msg_win_mgr
.get_control(ji
, account
)
1835 chat_control
.contact
= contact1
1837 def connected_rooms(self
, account
):
1838 if account
in gajim
.gc_connected
[account
].values():
1842 def on_event_removed(self
, event_list
):
1844 Remove contacts on last events removed
1846 Only performed if removal was requested before but the contact still had
1849 contact_list
= ((event
.jid
.split('/')[0], event
.account
) for event
in \
1852 for jid
, account
in contact_list
:
1853 self
.draw_contact(jid
, account
)
1854 # Remove contacts in roster if removal was requested
1855 key
= (jid
, account
)
1856 if key
in self
.contacts_to_be_removed
.keys():
1857 backend
= self
.contacts_to_be_removed
[key
]['backend']
1858 del self
.contacts_to_be_removed
[key
]
1859 # Remove contact will delay removal if there are more events
1861 self
.remove_contact(jid
, account
, backend
=backend
)
1864 def open_event(self
, account
, jid
, event
):
1866 If an event was handled, return True, else return False
1868 data
= event
.parameters
1869 ft
= gajim
.interface
.instances
['file_transfers']
1870 event
= gajim
.events
.get_first_event(account
, jid
, event
.type_
)
1871 if event
.type_
== 'normal':
1872 dialogs
.SingleMessageWindow(account
, jid
,
1873 action
='receive', from_whom
=jid
, subject
=data
[1],
1874 message
=data
[0], resource
=data
[5], session
=data
[8],
1876 gajim
.events
.remove_events(account
, jid
, event
)
1878 elif event
.type_
== 'file-request':
1879 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
1881 ft
.show_file_request(account
, contact
, data
)
1882 gajim
.events
.remove_events(account
, jid
, event
)
1884 elif event
.type_
in ('file-request-error', 'file-send-error'):
1885 ft
.show_send_error(data
)
1886 gajim
.events
.remove_events(account
, jid
, event
)
1888 elif event
.type_
in ('file-error', 'file-stopped'):
1890 if data
['error'] == -1:
1891 msg_err
= _('Remote contact stopped transfer')
1892 elif data
['error'] == -6:
1893 msg_err
= _('Error opening file')
1894 ft
.show_stopped(jid
, data
, error_msg
=msg_err
)
1895 gajim
.events
.remove_events(account
, jid
, event
)
1897 elif event
.type_
== 'file-completed':
1898 ft
.show_completed(jid
, data
)
1899 gajim
.events
.remove_events(account
, jid
, event
)
1901 elif event
.type_
== 'gc-invitation':
1902 dialogs
.InvitationReceivedDialog(account
, data
[0], jid
, data
[2],
1904 gajim
.events
.remove_events(account
, jid
, event
)
1906 elif event
.type_
== 'subscription_request':
1907 dialogs
.SubscriptionRequestWindow(jid
, data
[0], account
, data
[1])
1908 gajim
.events
.remove_events(account
, jid
, event
)
1910 elif event
.type_
== 'unsubscribed':
1911 gajim
.interface
.show_unsubscribed_dialog(account
, data
)
1912 gajim
.events
.remove_events(account
, jid
, event
)
1914 elif event
.type_
== 'jingle-incoming':
1915 peerjid
, sid
, content_types
= data
1916 dialogs
.VoIPCallReceivedDialog(account
, peerjid
, sid
, content_types
)
1917 gajim
.events
.remove_events(account
, jid
, event
)
1921 ################################################################################
1922 ### This and that... random.
1923 ################################################################################
1925 def show_roster_vbox(self
, active
):
1926 vb
= self
.xml
.get_object('roster_vbox2')
1928 vb
.set_no_show_all(False)
1932 vb
.set_no_show_all(True)
1934 def show_tooltip(self
, contact
):
1935 pointer
= self
.tree
.get_pointer()
1936 props
= self
.tree
.get_path_at_pos(pointer
[0], pointer
[1])
1937 # check if the current pointer is at the same path
1938 # as it was before setting the timeout
1939 if props
and self
.tooltip
.id == props
[0]:
1940 # bounding rectangle of coordinates for the cell within the treeview
1941 rect
= self
.tree
.get_cell_area(props
[0], props
[1])
1943 # position of the treeview on the screen
1944 position
= self
.tree
.window
.get_origin()
1945 self
.tooltip
.show_tooltip(contact
, rect
.height
, position
[1] + \
1948 self
.tooltip
.hide_tooltip()
1951 def authorize(self
, widget
, jid
, account
):
1953 Authorize a contact (by re-sending auth menuitem)
1955 gajim
.connections
[account
].send_authorization(jid
)
1956 dialogs
.InformationDialog(_('Authorization has been sent'),
1957 _('Now "%s" will know your status.') %jid
)
1959 def req_sub(self
, widget
, jid
, txt
, account
, groups
=None, nickname
=None,
1962 Request subscription to a contact
1964 groups_list
= groups
or []
1965 gajim
.connections
[account
].request_subscription(jid
, txt
, nickname
,
1966 groups_list
, auto_auth
, gajim
.nicks
[account
])
1967 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
1970 attached_keys
= gajim
.config
.get_per('accounts', account
,
1971 'attached_gpg_keys').split()
1972 if jid
in attached_keys
:
1973 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
1974 contact
= gajim
.contacts
.create_contact(jid
=jid
, account
=account
,
1975 name
=nickname
, groups
=groups_list
, show
='requested', status
='',
1976 ask
='none', sub
='subscribe', keyID
=keyID
)
1977 gajim
.contacts
.add_contact(account
, contact
)
1979 if not _('Not in Roster') in contact
.get_shown_groups():
1980 dialogs
.InformationDialog(_('Subscription request has been '
1981 'sent'), _('If "%s" accepts this request you will know his '
1982 'or her status.') % jid
)
1984 self
.remove_contact(contact
.jid
, account
, force
=True)
1985 contact
.groups
= groups_list
1987 contact
.name
= nickname
1988 self
.add_contact(jid
, account
)
1990 def revoke_auth(self
, widget
, jid
, account
):
1992 Revoke a contact's authorization
1994 gajim
.connections
[account
].refuse_authorization(jid
)
1995 dialogs
.InformationDialog(_('Authorization has been removed'),
1996 _('Now "%s" will always see you as offline.') %jid
)
1998 def set_state(self
, account
, state
):
1999 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
2001 self
.model
[child_iterA
][0] = \
2002 gajim
.interface
.jabber_state_images
['16'][state
]
2003 if gajim
.interface
.systray_enabled
:
2004 gajim
.interface
.systray
.change_status(state
)
2006 def set_connecting_state(self
, account
):
2007 self
.set_state(account
, 'connecting')
2009 def send_status(self
, account
, status
, txt
, auto
=False, to
=None):
2010 if status
!= 'offline':
2012 if status
== gajim
.connections
[account
].get_status() and \
2013 txt
== gajim
.connections
[account
].status
:
2015 gajim
.config
.set_per('accounts', account
, 'last_status', status
)
2016 gajim
.config
.set_per('accounts', account
, 'last_status_msg',
2017 helpers
.to_one_line(txt
))
2018 if gajim
.connections
[account
].connected
< 2:
2019 self
.set_connecting_state(account
)
2021 keyid
= gajim
.config
.get_per('accounts', account
, 'keyid')
2022 if keyid
and not gajim
.connections
[account
].gpg
:
2023 dialogs
.WarningDialog(_('GPG is not usable'),
2024 _('You will be connected to %s without OpenPGP.') % \
2027 self
.send_status_continue(account
, status
, txt
, auto
, to
)
2029 def send_pep(self
, account
, pep_dict
):
2030 connection
= gajim
.connections
[account
]
2032 if 'activity' in pep_dict
:
2033 activity
= pep_dict
['activity']
2034 subactivity
= pep_dict
.get('subactivity', None)
2035 activity_text
= pep_dict
.get('activity_text', None)
2036 connection
.send_activity(activity
, subactivity
, activity_text
)
2038 connection
.retract_activity()
2040 if 'mood' in pep_dict
:
2041 mood
= pep_dict
['mood']
2042 mood_text
= pep_dict
.get('mood_text', None)
2043 connection
.send_mood(mood
, mood_text
)
2045 connection
.retract_mood()
2047 def delete_pep(self
, jid
, account
):
2048 if jid
== gajim
.get_jid_from_account(account
):
2049 gajim
.connections
[account
].pep
= {}
2050 self
.draw_account(account
)
2052 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
2055 self
.draw_all_pep_types(jid
, account
)
2056 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(jid
, account
)
2058 ctrl
.update_all_pep_types()
2060 def send_status_continue(self
, account
, status
, txt
, auto
, to
):
2061 if gajim
.account_is_connected(account
) and not to
:
2062 if status
== 'online' and gajim
.interface
.sleeper
.getState() != \
2063 common
.sleepy
.STATE_UNKNOWN
:
2064 gajim
.sleeper_state
[account
] = 'online'
2065 elif gajim
.sleeper_state
[account
] not in ('autoaway', 'autoxa') or \
2066 status
== 'offline':
2067 gajim
.sleeper_state
[account
] = 'off'
2070 gajim
.connections
[account
].send_custom_status(status
, txt
, to
)
2072 if status
in ('invisible', 'offline'):
2073 self
.delete_pep(gajim
.get_jid_from_account(account
), account
)
2074 was_invisible
= gajim
.connections
[account
].connected
== \
2075 gajim
.SHOW_LIST
.index('invisible')
2076 gajim
.connections
[account
].change_status(status
, txt
, auto
)
2078 if account
in gajim
.interface
.status_sent_to_users
:
2079 gajim
.interface
.status_sent_to_users
[account
] = {}
2080 if account
in gajim
.interface
.status_sent_to_groups
:
2081 gajim
.interface
.status_sent_to_groups
[account
] = {}
2082 for gc_control
in gajim
.interface
.msg_win_mgr
.get_controls(
2083 message_control
.TYPE_GC
) + \
2084 gajim
.interface
.minimized_controls
[account
].values():
2085 if gc_control
.account
== account
:
2086 if gajim
.gc_connected
[account
][gc_control
.room_jid
]:
2087 gajim
.connections
[account
].send_gc_status(
2088 gc_control
.nick
, gc_control
.room_jid
, status
, txt
)
2090 # for some reason, we are not connected to the room even
2091 # if tab is opened, send initial join_gc()
2092 gajim
.connections
[account
].join_gc(gc_control
.nick
,
2093 gc_control
.room_jid
, None)
2094 if was_invisible
and status
!= 'offline':
2095 # We come back from invisible, join bookmarks
2096 gajim
.interface
.auto_join_bookmarks(account
)
2099 def chg_contact_status(self
, contact
, show
, status
, account
):
2101 When a contact changes his or her status
2103 contact_instances
= gajim
.contacts
.get_contacts(account
, contact
.jid
)
2105 contact
.status
= status
2106 # name is to show in conversation window
2107 name
= contact
.get_shown_name()
2108 fjid
= contact
.get_full_jid()
2110 # The contact has several resources
2111 if len(contact_instances
) > 1:
2112 if contact
.resource
!= '':
2113 name
+= '/' + contact
.resource
2115 # Remove resource when going offline
2116 if show
in ('offline', 'error') and \
2117 not self
.contact_has_pending_roster_events(contact
, account
):
2118 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(fjid
, account
)
2121 ctrl
.parent_win
.redraw_tab(ctrl
)
2122 # keep the contact around, since it's
2123 # already attached to the control
2125 gajim
.contacts
.remove_contact(account
, contact
)
2127 elif contact
.jid
== gajim
.get_jid_from_account(account
) and \
2128 show
in ('offline', 'error'):
2129 if gajim
.config
.get('show_self_contact') != 'never':
2130 # SelfContact went offline. Remove him when last pending
2132 self
.remove_contact(contact
.jid
, account
, backend
=True)
2134 uf_show
= helpers
.get_uf_show(show
)
2136 # print status in chat window and update status/GPG image
2137 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(contact
.jid
, account
)
2138 if ctrl
and ctrl
.type_id
!= message_control
.TYPE_GC
:
2139 ctrl
.contact
= gajim
.contacts
.get_contact_with_highest_priority(
2140 account
, contact
.jid
)
2141 ctrl
.update_status_display(name
, uf_show
, status
)
2143 if contact
.resource
:
2144 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(fjid
, account
)
2146 ctrl
.update_status_display(name
, uf_show
, status
)
2148 # Delete pep if needed
2149 keep_pep
= any(c
.show
not in ('error', 'offline') for c
in
2151 if not keep_pep
and contact
.jid
!= gajim
.get_jid_from_account(account
) \
2152 and not contact
.is_groupchat():
2153 self
.delete_pep(contact
.jid
, account
)
2155 # Redraw everything and select the sender
2156 self
.adjust_and_draw_contact_context(contact
.jid
, account
)
2159 def on_status_changed(self
, account
, show
):
2161 The core tells us that our status has changed
2163 if account
not in gajim
.contacts
.get_accounts():
2165 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
2166 if gajim
.config
.get('show_self_contact') == 'always':
2167 self_resource
= gajim
.connections
[account
].server_resource
2168 self_contact
= gajim
.contacts
.get_contact(account
,
2169 gajim
.get_jid_from_account(account
), resource
=self_resource
)
2171 status
= gajim
.connections
[account
].status
2172 self
.chg_contact_status(self_contact
, show
, status
, account
)
2173 self
.set_account_status_icon(account
)
2174 if show
== 'offline':
2175 if self
.quit_on_next_offline
> -1:
2176 # we want to quit, we are waiting for all accounts to be offline
2177 self
.quit_on_next_offline
-= 1
2178 if self
.quit_on_next_offline
< 1:
2179 # all accounts offline, quit
2180 self
.quit_gtkgui_interface()
2182 # No need to redraw contacts if we're quitting
2184 self
.model
[child_iterA
][C_AVATAR_PIXBUF
] = None
2185 if account
in gajim
.con_types
:
2186 gajim
.con_types
[account
] = None
2187 for jid
in gajim
.contacts
.get_jid_list(account
):
2188 lcontact
= gajim
.contacts
.get_contacts(account
, jid
)
2189 ctrl
= gajim
.interface
.msg_win_mgr
.get_gc_control(jid
,
2191 for contact
in [c
for c
in lcontact
if (
2192 (c
.show
!= 'offline' or c
.is_transport()) and not ctrl
)]:
2193 self
.chg_contact_status(contact
, 'offline', '', account
)
2194 self
.set_actions_menu_needs_rebuild()
2195 self
.update_status_combobox()
2197 def get_status_message(self
, show
, on_response
, show_pep
=True,
2200 Get the status message by:
2202 1/ looking in default status message
2203 2/ asking to user if needed depending on ask_on(ff)line_status and
2205 show_pep can be False to hide pep things from status message or True
2207 empty_pep
= {'activity': '', 'subactivity': '', 'activity_text': '',
2208 'mood': '', 'mood_text': ''}
2209 if show
in gajim
.config
.get_per('defaultstatusmsg'):
2210 if gajim
.config
.get_per('defaultstatusmsg', show
, 'enabled'):
2211 msg
= gajim
.config
.get_per('defaultstatusmsg', show
, 'message')
2212 msg
= helpers
.from_one_line(msg
)
2213 on_response(msg
, empty_pep
)
2215 if not always_ask
and ((show
== 'online' and not gajim
.config
.get(
2216 'ask_online_status')) or (show
in ('offline', 'invisible') and not \
2217 gajim
.config
.get('ask_offline_status'))):
2218 on_response('', empty_pep
)
2221 dlg
= dialogs
.ChangeStatusMessageDialog(on_response
, show
, show_pep
)
2222 dlg
.dialog
.present() # show it on current workspace
2224 def change_status(self
, widget
, account
, status
):
2225 def change(account
, status
):
2226 def on_response(message
, pep_dict
):
2228 # user pressed Cancel to change status message dialog
2230 self
.send_status(account
, status
, message
)
2231 self
.send_pep(account
, pep_dict
)
2232 self
.get_status_message(status
, on_response
)
2234 if status
== 'invisible' and self
.connected_rooms(account
):
2235 dialogs
.ConfirmationDialog(
2236 _('You are participating in one or more group chats'),
2237 _('Changing your status to invisible will result in '
2238 'disconnection from those group chats. Are you sure you want '
2239 'to go invisible?'), on_response_ok
= (change
, account
, status
))
2241 change(account
, status
)
2243 def update_status_combobox(self
):
2244 # table to change index in connection.connected to index in combobox
2245 table
= {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
2246 'xa':3, 'dnd':4, 'invisible':5}
2248 # we check if there are more options in the combobox that it should
2249 # if yes, we remove the first ones
2250 while len(self
.status_combobox
.get_model()) > len(table
)+2:
2251 self
.status_combobox
.remove_text(0)
2253 show
= helpers
.get_global_show()
2254 # temporarily block signal in order not to send status that we show
2256 self
.combobox_callback_active
= False
2257 if helpers
.statuses_unified():
2258 self
.status_combobox
.set_active(table
[show
])
2260 uf_show
= helpers
.get_uf_show(show
)
2261 liststore
= self
.status_combobox
.get_model()
2262 liststore
.prepend(['SEPARATOR', None, '', True])
2263 status_combobox_text
= uf_show
+ ' (' + _("desync'ed") +')'
2264 liststore
.prepend([status_combobox_text
,
2265 gajim
.interface
.jabber_state_images
['16'][show
], show
, False])
2266 self
.status_combobox
.set_active(0)
2267 gajim
.interface
.change_awn_icon_status(show
)
2268 self
.combobox_callback_active
= True
2269 if gajim
.interface
.systray_enabled
:
2270 gajim
.interface
.systray
.change_status(show
)
2272 def get_show(self
, lcontact
):
2273 prio
= lcontact
[0].priority
2274 show
= lcontact
[0].show
2276 if u
.priority
> prio
:
2281 def on_message_window_delete(self
, win_mgr
, msg_win
):
2282 if gajim
.config
.get('one_message_window') == 'always_with_roster':
2283 self
.show_roster_vbox(True)
2284 gtkgui_helpers
.resize_window(self
.window
,
2285 gajim
.config
.get('roster_width'),
2286 gajim
.config
.get('roster_height'))
2288 def close_all_from_dict(self
, dic
):
2290 Close all the windows in the given dictionary
2292 for w
in dic
.values():
2293 if isinstance(w
, dict):
2294 self
.close_all_from_dict(w
)
2298 def close_all(self
, account
, force
=False):
2300 Close all the windows from an account. If force is True, do not ask
2301 confirmation before closing chat/gc windows
2303 if account
in gajim
.interface
.instances
:
2304 self
.close_all_from_dict(gajim
.interface
.instances
[account
])
2305 for ctrl
in gajim
.interface
.msg_win_mgr
.get_controls(acct
=account
):
2306 ctrl
.parent_win
.remove_tab(ctrl
, ctrl
.parent_win
.CLOSE_CLOSE_BUTTON
,
2309 def on_roster_window_delete_event(self
, widget
, event
):
2311 Main window X button was clicked
2313 if not gajim
.config
.get('quit_on_roster_x_button') and (
2314 (gajim
.interface
.systray_enabled
and gajim
.config
.get('trayicon') != \
2315 'on_event') or gajim
.config
.get('allow_hide_roster')):
2316 self
.tooltip
.hide_tooltip()
2317 if gajim
.config
.get('save-roster-position'):
2318 x
, y
= self
.window
.get_position()
2319 gajim
.config
.set('roster_x-position', x
)
2320 gajim
.config
.set('roster_y-position', y
)
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 elif gtk
.gdk
.keyval_to_unicode(event
.keyval
): # if we got unicode symbol
3371 num
= gtk
.gdk
.keyval_to_unicode(event
.keyval
)
3372 self
.enable_rfilter(unichr(num
))
3374 def on_roster_treeview_button_release_event(self
, widget
, event
):
3376 path
= self
.tree
.get_path_at_pos(int(event
.x
), int(event
.y
))[0]
3380 if event
.button
== 1: # Left click
3381 if gajim
.single_click
and not event
.state
& gtk
.gdk
.SHIFT_MASK
and \
3382 not event
.state
& gtk
.gdk
.CONTROL_MASK
:
3383 # Check if button has been pressed on the same row
3384 if self
.clicked_path
== path
:
3385 self
.on_row_activated(widget
, path
)
3386 self
.clicked_path
= None
3388 def on_roster_treeview_button_press_event(self
, widget
, event
):
3389 # hide tooltip, no matter the button is pressed
3390 self
.tooltip
.hide_tooltip()
3392 pos
= self
.tree
.get_path_at_pos(int(event
.x
), int(event
.y
))
3393 path
, x
= pos
[0], pos
[2]
3395 self
.tree
.get_selection().unselect_all()
3398 if event
.button
== 3: # Right click
3400 model
, list_of_paths
= self
.tree
.get_selection().\
3404 if path
not in list_of_paths
:
3405 self
.tree
.get_selection().unselect_all()
3406 self
.tree
.get_selection().select_path(path
)
3407 return self
.show_treeview_menu(event
)
3409 elif event
.button
== 2: # Middle click
3411 model
, list_of_paths
= self
.tree
.get_selection().\
3415 if list_of_paths
!= [path
]:
3416 self
.tree
.get_selection().unselect_all()
3417 self
.tree
.get_selection().select_path(path
)
3418 type_
= model
[path
][C_TYPE
]
3419 if type_
in ('agent', 'contact', 'self_contact', 'groupchat'):
3420 self
.on_row_activated(widget
, path
)
3421 elif type_
== 'account':
3422 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3423 if account
!= 'all':
3424 show
= gajim
.connections
[account
].connected
3425 if show
> 1: # We are connected
3426 self
.on_change_status_message_activate(widget
, account
)
3428 show
= helpers
.get_global_show()
3429 if show
== 'offline':
3431 def on_response(message
, pep_dict
):
3434 for acct
in gajim
.connections
:
3435 if not gajim
.config
.get_per('accounts', acct
,
3436 'sync_with_global_status'):
3438 current_show
= gajim
.SHOW_LIST
[gajim
.connections
[acct
].\
3440 self
.send_status(acct
, current_show
, message
)
3441 self
.send_pep(acct
, pep_dict
)
3442 dialogs
.ChangeStatusMessageDialog(on_response
, show
)
3445 elif event
.button
== 1: # Left click
3446 model
= self
.modelfilter
3447 type_
= model
[path
][C_TYPE
]
3448 # x_min is the x start position of status icon column
3449 if gajim
.config
.get('avatar_position_in_roster') == 'left':
3450 x_min
= gajim
.config
.get('roster_avatar_width')
3453 if gajim
.single_click
and not event
.state
& gtk
.gdk
.SHIFT_MASK
and \
3454 not event
.state
& gtk
.gdk
.CONTROL_MASK
:
3455 # Don't handle double click if we press icon of a metacontact
3456 titer
= model
.get_iter(path
)
3457 if x
> x_min
and x
< x_min
+ 27 and type_
== 'contact' and \
3458 model
.iter_has_child(titer
):
3459 if (self
.tree
.row_expanded(path
)):
3460 self
.tree
.collapse_row(path
)
3462 self
.tree
.expand_row(path
, False)
3464 # We just save on which row we press button, and open chat
3465 # window on button release to be able to do DND without opening
3467 self
.clicked_path
= path
3470 if type_
== 'group' and x
< 27:
3471 # first cell in 1st column (the arrow SINGLE clicked)
3472 if (self
.tree
.row_expanded(path
)):
3473 self
.tree
.collapse_row(path
)
3475 self
.expand_group_row(path
)
3477 elif type_
== 'contact' and x
> x_min
and x
< x_min
+ 27:
3478 if (self
.tree
.row_expanded(path
)):
3479 self
.tree
.collapse_row(path
)
3481 self
.tree
.expand_row(path
, False)
3483 def expand_group_row(self
, path
):
3484 self
.tree
.expand_row(path
, False)
3485 iter = self
.modelfilter
.get_iter(path
)
3486 child_iter
= self
.modelfilter
.iter_children(iter)
3488 type_
= self
.modelfilter
[child_iter
][C_TYPE
]
3489 account
= self
.modelfilter
[child_iter
][C_ACCOUNT
]
3490 group
= self
.modelfilter
[child_iter
][C_JID
]
3491 if type_
== 'group' and account
+ group
not in self
.collapsed_rows
:
3492 self
.expand_group_row(self
.modelfilter
.get_path(child_iter
))
3493 child_iter
= self
.modelfilter
.iter_next(child_iter
)
3495 def on_req_usub(self
, widget
, list_
):
3497 Remove a contact. list_ is a list of (contact, account) tuples
3499 def on_ok(is_checked
, list_
):
3502 contact
= list_
[0][0]
3503 if contact
.sub
!= 'to' and is_checked
:
3505 for (contact
, account
) in list_
:
3506 if _('Not in Roster') not in contact
.get_shown_groups():
3507 gajim
.connections
[account
].unsubscribe(contact
.jid
,
3509 self
.remove_contact(contact
.jid
, account
, backend
=True)
3510 if not remove_auth
and contact
.sub
== 'both':
3513 contact
.sub
= 'from'
3514 # we can't see him, but have to set it manually in contact
3515 contact
.show
= 'offline'
3516 gajim
.contacts
.add_contact(account
, contact
)
3517 self
.add_contact(contact
.jid
, account
)
3522 contact
= list_
[0][0]
3523 pritext
= _('Contact "%s" will be removed from your roster') % \
3524 contact
.get_shown_name()
3525 sectext
= _('You are about to remove "%(name)s" (%(jid)s) from '
3526 'your roster.\n') % {'name': contact
.get_shown_name(),
3528 if contact
.sub
== 'to':
3529 dialogs
.ConfirmationDialog(pritext
, sectext
+ \
3530 _('By removing this contact you also remove authorization '
3531 'resulting in him or her always seeing you as offline.'),
3532 on_response_ok
=(on_ok2
, list_
))
3533 elif _('Not in Roster') in contact
.get_shown_groups():
3534 # Contact is not in roster
3535 dialogs
.ConfirmationDialog(pritext
, sectext
+ \
3536 _('Do you want to continue?'), on_response_ok
=(on_ok2
,
3539 dialogs
.ConfirmationDialogCheck(pritext
, sectext
+ \
3540 _('By removing this contact you also by default remove '
3541 'authorization resulting in him or her always seeing you as'
3543 _('I want this contact to know my status after removal'),
3544 on_response_ok
=(on_ok
, list_
))
3546 # several contact to remove at the same time
3547 pritext
= _('Contacts will be removed from your roster')
3549 for (contact
, account
) in list_
:
3550 jids
+= '\n ' + contact
.get_shown_name() + ' (%s)' % \
3552 sectext
= _('By removing these contacts:%s\nyou also remove '
3553 'authorization resulting in them always seeing you as '
3555 dialogs
.ConfirmationDialog(pritext
, sectext
,
3556 on_response_ok
=(on_ok2
, list_
))
3558 def on_send_custom_status(self
, widget
, contact_list
, show
, group
=None):
3562 # contact_list has only one element except if group != None
3563 def on_response(message
, pep_dict
):
3564 if message
is None: # None if user pressed Cancel
3567 for (contact
, account
) in contact_list
:
3568 if account
not in account_list
:
3569 account_list
.append(account
)
3570 # 1. update status_sent_to_[groups|users] list
3572 for account
in account_list
:
3573 if account
not in gajim
.interface
.status_sent_to_groups
:
3574 gajim
.interface
.status_sent_to_groups
[account
] = {}
3575 gajim
.interface
.status_sent_to_groups
[account
][group
] = show
3577 for (contact
, account
) in contact_list
:
3578 if account
not in gajim
.interface
.status_sent_to_users
:
3579 gajim
.interface
.status_sent_to_users
[account
] = {}
3580 gajim
.interface
.status_sent_to_users
[account
][contact
.jid
] \
3583 # 2. update privacy lists if main status is invisible
3584 for account
in account_list
:
3585 if gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
] == \
3587 gajim
.connections
[account
].set_invisible_rule()
3589 # 3. send directed presence
3590 for (contact
, account
) in contact_list
:
3591 our_jid
= gajim
.get_jid_from_account(account
)
3594 jid
+= '/' + contact
.resource
3595 self
.send_status(account
, show
, message
, to
=jid
)
3597 def send_it(is_checked
=None):
3598 if is_checked
is not None: # dialog has been shown
3599 if is_checked
: # user does not want to be asked again
3600 gajim
.config
.set('confirm_custom_status', 'no')
3602 gajim
.config
.set('confirm_custom_status', 'yes')
3603 self
.get_status_message(show
, on_response
, show_pep
=False,
3606 confirm_custom_status
= gajim
.config
.get('confirm_custom_status')
3607 if confirm_custom_status
== 'no':
3610 pritext
= _('You are about to send a custom status. Are you sure you '
3611 'want to continue?')
3612 sectext
= _('This contact will temporarily see you as %(status)s, '
3613 'but only until you change your status. Then he or she will see '
3614 'your global status.') % {'status': show
}
3615 dialogs
.ConfirmationDialogCheck(pritext
, sectext
,
3616 _('_Do not ask me again'), on_response_ok
=send_it
)
3618 def on_status_combobox_changed(self
, widget
):
3620 When we change our status via the combobox
3622 model
= self
.status_combobox
.get_model()
3623 active
= self
.status_combobox
.get_active()
3624 if active
== -1: # no active item
3626 if not self
.combobox_callback_active
:
3627 self
.previous_status_combobox_active
= active
3629 accounts
= gajim
.connections
.keys()
3630 if len(accounts
) == 0:
3631 dialogs
.ErrorDialog(_('No account available'),
3632 _('You must create an account before you can chat with other '
3634 self
.update_status_combobox()
3636 status
= model
[active
][2].decode('utf-8')
3637 # status "desync'ed" or not
3638 statuses_unified
= helpers
.statuses_unified()
3639 if (active
== 7 and statuses_unified
) or (active
== 9 and \
3640 not statuses_unified
):
3641 # 'Change status message' selected:
3642 # do not change show, just show change status dialog
3643 status
= model
[self
.previous_status_combobox_active
][2].decode(
3645 def on_response(message
, pep_dict
):
3646 if message
is not None: # None if user pressed Cancel
3647 for account
in accounts
:
3648 if not gajim
.config
.get_per('accounts', account
,
3649 'sync_with_global_status'):
3651 current_show
= gajim
.SHOW_LIST
[
3652 gajim
.connections
[account
].connected
]
3653 self
.send_status(account
, current_show
, message
)
3654 self
.send_pep(account
, pep_dict
)
3655 self
.combobox_callback_active
= False
3656 self
.status_combobox
.set_active(
3657 self
.previous_status_combobox_active
)
3658 self
.combobox_callback_active
= True
3659 dialogs
.ChangeStatusMessageDialog(on_response
, status
)
3661 # we are about to change show, so save this new show so in case
3662 # after user chooses "Change status message" menuitem
3663 # we can return to this show
3664 self
.previous_status_combobox_active
= active
3665 connected_accounts
= gajim
.get_number_of_connected_accounts()
3667 def on_continue(message
, pep_dict
):
3669 # user pressed Cancel to change status message dialog
3670 self
.update_status_combobox()
3672 global_sync_accounts
= []
3673 for acct
in accounts
:
3674 if gajim
.config
.get_per('accounts', acct
,
3675 'sync_with_global_status'):
3676 global_sync_accounts
.append(acct
)
3677 global_sync_connected_accounts
= \
3678 gajim
.get_number_of_connected_accounts(global_sync_accounts
)
3679 for account
in accounts
:
3680 if not gajim
.config
.get_per('accounts', account
,
3681 'sync_with_global_status'):
3683 # we are connected (so we wanna change show and status)
3684 # or no account is connected and we want to connect with new
3687 if not global_sync_connected_accounts
> 0 or \
3688 gajim
.connections
[account
].connected
> 0:
3689 self
.send_status(account
, status
, message
)
3690 self
.send_pep(account
, pep_dict
)
3691 self
.update_status_combobox()
3693 if status
== 'invisible':
3695 for account
in accounts
:
3696 if connected_accounts
< 1 or gajim
.account_is_connected(
3698 if not gajim
.config
.get_per('accounts', account
,
3699 'sync_with_global_status'):
3701 # We're going to change our status to invisible
3702 if self
.connected_rooms(account
):
3707 self
.get_status_message(status
, on_continue
, show_pep
=False)
3710 self
.update_status_combobox()
3712 dialogs
.ConfirmationDialog(
3713 _('You are participating in one or more group chats'),
3714 _('Changing your status to invisible will result in '
3715 'disconnection from those group chats. Are you sure you '
3716 'want to go invisible?'), on_reponse_ok
=on_ok
,
3717 on_response_cancel
=on_cancel
)
3720 self
.get_status_message(status
, on_continue
)
3722 def on_preferences_menuitem_activate(self
, widget
):
3723 if 'preferences' in gajim
.interface
.instances
:
3724 gajim
.interface
.instances
['preferences'].window
.present()
3726 gajim
.interface
.instances
['preferences'] = config
.PreferencesWindow(
3729 def on_plugins_menuitem_activate(self
, widget
):
3730 if gajim
.interface
.instances
.has_key('plugins'):
3731 gajim
.interface
.instances
['plugins'].window
.present()
3733 gajim
.interface
.instances
['plugins'] = plugins
.gui
.PluginsWindow()
3735 def on_publish_tune_toggled(self
, widget
, account
):
3736 active
= widget
.get_active()
3737 gajim
.config
.set_per('accounts', account
, 'publish_tune', active
)
3739 gajim
.interface
.enable_music_listener()
3741 gajim
.connections
[account
].retract_tune()
3742 # disable music listener only if no other account uses it
3743 for acc
in gajim
.connections
:
3744 if gajim
.config
.get_per('accounts', acc
, 'publish_tune'):
3747 gajim
.interface
.disable_music_listener()
3749 helpers
.update_optional_features(account
)
3751 def on_publish_location_toggled(self
, widget
, account
):
3752 active
= widget
.get_active()
3753 gajim
.config
.set_per('accounts', account
, 'publish_location', active
)
3755 location_listener
.enable()
3757 gajim
.connections
[account
].retract_location()
3758 # disable music listener only if no other account uses it
3759 for acc
in gajim
.connections
:
3760 if gajim
.config
.get_per('accounts', acc
, 'publish_location'):
3763 location_listener
.disable()
3765 helpers
.update_optional_features(account
)
3767 def on_pep_services_menuitem_activate(self
, widget
, account
):
3768 if 'pep_services' in gajim
.interface
.instances
[account
]:
3769 gajim
.interface
.instances
[account
]['pep_services'].window
.present()
3771 gajim
.interface
.instances
[account
]['pep_services'] = \
3772 config
.ManagePEPServicesWindow(account
)
3774 def on_add_new_contact(self
, widget
, account
):
3775 dialogs
.AddNewContactWindow(account
)
3777 def on_join_gc_activate(self
, widget
, account
):
3779 When the join gc menuitem is clicked, show the join gc window
3781 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
3782 if gajim
.connections
[account
].connected
== invisible_show
:
3783 dialogs
.ErrorDialog(_('You cannot join a group chat while you are '
3786 if 'join_gc' in gajim
.interface
.instances
[account
]:
3787 gajim
.interface
.instances
[account
]['join_gc'].window
.present()
3790 gajim
.interface
.instances
[account
]['join_gc'] = \
3791 dialogs
.JoinGroupchatWindow(account
)
3792 except GajimGeneralException
:
3795 def on_new_chat_menuitem_activate(self
, widget
, account
):
3796 dialogs
.NewChatDialog(account
)
3798 def on_contents_menuitem_activate(self
, widget
):
3799 helpers
.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
3801 def on_faq_menuitem_activate(self
, widget
):
3802 helpers
.launch_browser_mailer('url',
3803 'http://trac.gajim.org/wiki/GajimFaq')
3805 def on_features_menuitem_activate(self
, widget
):
3806 features_window
.FeaturesWindow()
3808 def on_about_menuitem_activate(self
, widget
):
3809 dialogs
.AboutDialog()
3811 def on_accounts_menuitem_activate(self
, widget
):
3812 if 'accounts' in gajim
.interface
.instances
:
3813 gajim
.interface
.instances
['accounts'].window
.present()
3815 gajim
.interface
.instances
['accounts'] = config
.AccountsWindow()
3817 def on_file_transfers_menuitem_activate(self
, widget
):
3818 if gajim
.interface
.instances
['file_transfers'].window
.get_property(
3820 gajim
.interface
.instances
['file_transfers'].window
.present()
3822 gajim
.interface
.instances
['file_transfers'].window
.show_all()
3824 def on_history_menuitem_activate(self
, widget
):
3825 if 'logs' in gajim
.interface
.instances
:
3826 gajim
.interface
.instances
['logs'].window
.present()
3828 gajim
.interface
.instances
['logs'] = history_window
.\
3831 def on_show_transports_menuitem_activate(self
, widget
):
3832 gajim
.config
.set('show_transports_group', widget
.get_active())
3833 self
.refilter_shown_roster_items()
3835 def on_manage_bookmarks_menuitem_activate(self
, widget
):
3836 config
.ManageBookmarksWindow()
3838 def on_profile_avatar_menuitem_activate(self
, widget
, account
):
3839 gajim
.interface
.edit_own_details(account
)
3841 def on_execute_command(self
, widget
, contact
, account
, resource
=None):
3843 Execute command. Full JID needed; if it is other contact, resource is
3844 necessary. Widget is unnecessary, only to be able to make this a
3848 if resource
is not None:
3849 jid
= jid
+ u
'/' + resource
3850 adhoc_commands
.CommandWindow(account
, jid
)
3852 def on_roster_window_focus_in_event(self
, widget
, event
):
3853 # roster received focus, so if we had urgency REMOVE IT
3854 # NOTE: we do not have to read the message to remove urgency
3855 # so this functions does that
3856 gtkgui_helpers
.set_unset_urgency_hint(widget
, False)
3858 # if a contact row is selected, update colors (eg. for status msg)
3859 # because gtk engines may differ in bg when window is selected
3861 if len(self
._last
_selected
_contact
):
3862 for (jid
, account
) in self
._last
_selected
_contact
:
3863 self
.draw_contact(jid
, account
, selected
=True, focus
=True)
3865 def on_roster_window_focus_out_event(self
, widget
, event
):
3866 # if a contact row is selected, update colors (eg. for status msg)
3867 # because gtk engines may differ in bg when window is selected
3869 if len(self
._last
_selected
_contact
):
3870 for (jid
, account
) in self
._last
_selected
_contact
:
3871 self
.draw_contact(jid
, account
, selected
=True, focus
=False)
3873 def on_roster_window_key_press_event(self
, widget
, event
):
3874 if event
.keyval
== gtk
.keysyms
.Escape
:
3875 if gajim
.interface
.msg_win_mgr
.mode
== \
3876 MessageWindowMgr
.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER
and \
3877 gajim
.interface
.msg_win_mgr
.one_window_opened():
3878 # let message window close the tab
3880 list_of_paths
= self
.tree
.get_selection().get_selected_rows()[1]
3881 if not len(list_of_paths
) and not gajim
.config
.get(
3882 'quit_on_roster_x_button') and ((gajim
.interface
.systray_enabled
and\
3883 gajim
.config
.get('trayicon') == 'always') or gajim
.config
.get(
3884 'allow_hide_roster')):
3885 self
.tooltip
.hide_tooltip()
3887 elif event
.state
& gtk
.gdk
.CONTROL_MASK
and event
.keyval
== \
3889 treeselection
= self
.tree
.get_selection()
3890 model
, list_of_paths
= treeselection
.get_selected_rows()
3891 for path
in list_of_paths
:
3892 type_
= model
[path
][C_TYPE
]
3893 if type_
in ('contact', 'agent'):
3894 jid
= model
[path
][C_JID
].decode('utf-8')
3895 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3896 contact
= gajim
.contacts
.get_first_contact_from_jid(account
,
3898 self
.on_info(widget
, contact
, account
)
3899 elif event
.state
& gtk
.gdk
.CONTROL_MASK
and event
.keyval
== \
3901 treeselection
= self
.tree
.get_selection()
3902 model
, list_of_paths
= treeselection
.get_selected_rows()
3903 if len(list_of_paths
) != 1:
3905 path
= list_of_paths
[0]
3906 type_
= model
[path
][C_TYPE
]
3907 if type_
in ('contact', 'agent'):
3908 jid
= model
[path
][C_JID
].decode('utf-8')
3909 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3910 contact
= gajim
.contacts
.get_first_contact_from_jid(account
,
3912 self
.on_history(widget
, contact
, account
)
3914 def on_roster_window_popup_menu(self
, widget
):
3915 event
= gtk
.gdk
.Event(gtk
.gdk
.KEY_PRESS
)
3916 self
.show_treeview_menu(event
)
3918 def on_row_activated(self
, widget
, path
):
3920 When an iter is activated (double-click or single click if gnome is set
3923 model
= self
.modelfilter
3924 account
= model
[path
][C_ACCOUNT
].decode('utf-8')
3925 type_
= model
[path
][C_TYPE
]
3926 if type_
in ('group', 'account'):
3927 if self
.tree
.row_expanded(path
):
3928 self
.tree
.collapse_row(path
)
3930 self
.tree
.expand_row(path
, False)
3932 jid
= model
[path
][C_JID
].decode('utf-8')
3934 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
3935 titer
= model
.get_iter(path
)
3936 if contact
.is_groupchat():
3937 first_ev
= gajim
.events
.get_first_event(account
, jid
)
3938 if first_ev
and self
.open_event(account
, jid
, first_ev
):
3939 # We are invited to a GC
3940 # open event cares about connecting to it
3941 self
.remove_groupchat(jid
, account
)
3943 self
.on_groupchat_maximized(None, jid
, account
)
3947 first_ev
= gajim
.events
.get_first_event(account
, jid
)
3949 # look in other resources
3950 for c
in gajim
.contacts
.get_contacts(account
, jid
):
3951 fjid
= c
.get_full_jid()
3952 first_ev
= gajim
.events
.get_first_event(account
, fjid
)
3954 resource
= c
.resource
3956 if not first_ev
and model
.iter_has_child(titer
):
3957 child_iter
= model
.iter_children(titer
)
3958 while not first_ev
and child_iter
:
3959 child_jid
= model
[child_iter
][C_JID
].decode('utf-8')
3960 first_ev
= gajim
.events
.get_first_event(account
, child_jid
)
3964 child_iter
= model
.iter_next(child_iter
)
3967 if first_ev
.type_
in ('chat', 'normal'):
3968 session
= first_ev
.parameters
[8]
3971 fjid
+= '/' + resource
3972 if self
.open_event(account
, fjid
, first_ev
):
3975 contact
= gajim
.contacts
.get_contact(account
, jid
, resource
)
3976 if not contact
or isinstance(contact
, list):
3977 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
3979 if jid
== gajim
.get_jid_from_account(account
):
3980 resource
= contact
.resource
3982 gajim
.interface
.on_open_chat_window(None, contact
, account
, \
3983 resource
=resource
, session
=session
)
3985 def on_roster_treeview_row_activated(self
, widget
, path
, col
=0):
3987 When an iter is double clicked: open the first event window
3989 if not gajim
.single_click
:
3990 self
.on_row_activated(widget
, path
)
3992 def on_roster_treeview_row_expanded(self
, widget
, titer
, path
):
3994 When a row is expanded change the icon of the arrow
3996 self
._toggeling
_row
= True
3997 model
= widget
.get_model()
3998 child_model
= model
.get_model()
3999 child_iter
= model
.convert_iter_to_child_iter(titer
)
4001 if self
.regroup
: # merged accounts
4002 accounts
= gajim
.connections
.keys()
4004 accounts
= [model
[titer
][C_ACCOUNT
].decode('utf-8')]
4006 type_
= model
[titer
][C_TYPE
]
4007 if type_
== 'group':
4008 group
= model
[titer
][C_JID
].decode('utf-8')
4009 child_model
[child_iter
][C_IMG
] = \
4010 gajim
.interface
.jabber_state_images
['16']['opened']
4011 for account
in accounts
:
4012 if group
in gajim
.groups
[account
]: # This account has this group
4013 gajim
.groups
[account
][group
]['expand'] = True
4014 if account
+ group
in self
.collapsed_rows
:
4015 self
.collapsed_rows
.remove(account
+ group
)
4016 for contact
in gajim
.contacts
.iter_contacts(account
):
4018 if group
in contact
.groups
and \
4019 gajim
.contacts
.is_big_brother(account
, jid
, accounts
) and \
4020 account
+ group
+ jid
not in self
.collapsed_rows
:
4021 titers
= self
._get
_contact
_iter
(jid
, account
)
4022 for titer
in titers
:
4023 path
= model
.get_path(titer
)
4024 self
.tree
.expand_row(path
, False)
4025 elif type_
== 'account':
4026 account
= accounts
[0] # There is only one cause we don't use merge
4027 if account
in self
.collapsed_rows
:
4028 self
.collapsed_rows
.remove(account
)
4029 self
.draw_account(account
)
4030 # When we expand, groups are collapsed. Restore expand state
4031 for group
in gajim
.groups
[account
]:
4032 if gajim
.groups
[account
][group
]['expand']:
4033 titer
= self
._get
_group
_iter
(group
, account
)
4035 path
= model
.get_path(titer
)
4036 self
.tree
.expand_row(path
, False)
4037 elif type_
== 'contact':
4038 # Metacontact got toggled, update icon
4039 jid
= model
[titer
][C_JID
].decode('utf-8')
4040 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4041 contact
= gajim
.contacts
.get_contact(account
, jid
)
4042 for group
in contact
.groups
:
4043 if account
+ group
+ jid
in self
.collapsed_rows
:
4044 self
.collapsed_rows
.remove(account
+ group
+ jid
)
4045 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
4047 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)[0]
4048 # Redraw all brothers to show pending events
4049 for data
in nearby_family
:
4050 self
.draw_contact(data
['jid'], data
['account'])
4052 self
._toggeling
_row
= False
4054 def on_roster_treeview_row_collapsed(self
, widget
, titer
, path
):
4056 When a row is collapsed change the icon of the arrow
4058 self
._toggeling
_row
= True
4059 model
= widget
.get_model()
4060 child_model
= model
.get_model()
4061 child_iter
= model
.convert_iter_to_child_iter(titer
)
4063 if self
.regroup
: # merged accounts
4064 accounts
= gajim
.connections
.keys()
4066 accounts
= [model
[titer
][C_ACCOUNT
].decode('utf-8')]
4068 type_
= model
[titer
][C_TYPE
]
4069 if type_
== 'group':
4070 child_model
[child_iter
][C_IMG
] = gajim
.interface
.\
4071 jabber_state_images
['16']['closed']
4072 group
= model
[titer
][C_JID
].decode('utf-8')
4073 for account
in accounts
:
4074 if group
in gajim
.groups
[account
]: # This account has this group
4075 gajim
.groups
[account
][group
]['expand'] = False
4076 if account
+ group
not in self
.collapsed_rows
:
4077 self
.collapsed_rows
.append(account
+ group
)
4078 elif type_
== 'account':
4079 account
= accounts
[0] # There is only one cause we don't use merge
4080 if account
not in self
.collapsed_rows
:
4081 self
.collapsed_rows
.append(account
)
4082 self
.draw_account(account
)
4083 elif type_
== 'contact':
4084 # Metacontact got toggled, update icon
4085 jid
= model
[titer
][C_JID
].decode('utf-8')
4086 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4087 contact
= gajim
.contacts
.get_contact(account
, jid
)
4088 for group
in contact
.groups
:
4089 if account
+ group
+ jid
not in self
.collapsed_rows
:
4090 self
.collapsed_rows
.append(account
+ group
+ jid
)
4091 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
4093 self
._get
_nearby
_family
_and
_big
_brother
(family
, account
)[0]
4094 # Redraw all brothers to show pending events
4095 for data
in nearby_family
:
4096 self
.draw_contact(data
['jid'], data
['account'])
4098 self
._toggeling
_row
= False
4100 def on_modelfilter_row_has_child_toggled(self
, model
, path
, titer
):
4102 Called when a row has gotten the first or lost its last child row
4104 Expand Parent if necessary.
4106 if self
._toggeling
_row
:
4107 # Signal is emitted when we write to our model
4110 type_
= model
[titer
][C_TYPE
]
4111 account
= model
[titer
][C_ACCOUNT
]
4115 account
= account
.decode('utf-8')
4117 if type_
== 'contact':
4118 child_iter
= model
.convert_iter_to_child_iter(titer
)
4119 if self
.model
.iter_has_child(child_iter
):
4120 # we are a bigbrother metacontact
4121 # redraw us to show/hide expand icon
4123 # Prevent endless loops
4124 jid
= model
[titer
][C_JID
].decode('utf-8')
4125 gobject
.idle_add(self
.draw_contact
, jid
, account
)
4126 elif type_
== 'group':
4127 group
= model
[titer
][C_JID
].decode('utf-8')
4128 self
._adjust
_group
_expand
_collapse
_state
(group
, account
)
4129 elif type_
== 'account':
4130 self
._adjust
_account
_expand
_collapse
_state
(account
)
4132 # Selection can change when the model is filtered
4133 # Only write to the model when filtering is finished!
4135 # FIXME: When we are filtering our custom colors are somehow lost
4137 # def on_treeview_selection_changed(self, selection):
4138 # '''Called when selection in TreeView has changed.
4140 # Redraw unselected rows to make status message readable
4141 # on all possible backgrounds.
4143 # model, list_of_paths = selection.get_selected_rows()
4144 # if len(self._last_selected_contact):
4145 # # update unselected rows
4146 # for (jid, account) in self._last_selected_contact:
4147 # gobject.idle_add(self.draw_contact, jid,
4149 # self._last_selected_contact = []
4150 # if len(list_of_paths) == 0:
4152 # for path in list_of_paths:
4154 # if row[C_TYPE] != 'contact':
4155 # self._last_selected_contact = []
4157 # jid = row[C_JID].decode('utf-8')
4158 # account = row[C_ACCOUNT].decode('utf-8')
4159 # self._last_selected_contact.append((jid, account))
4160 # gobject.idle_add(self.draw_contact, jid, account, True)
4162 def on_service_disco_menuitem_activate(self
, widget
, account
):
4163 server_jid
= gajim
.config
.get_per('accounts', account
, 'hostname')
4164 if server_jid
in gajim
.interface
.instances
[account
]['disco']:
4165 gajim
.interface
.instances
[account
]['disco'][server_jid
].\
4169 # Object will add itself to the window dict
4170 disco
.ServiceDiscoveryWindow(account
, address_entry
=True)
4171 except GajimGeneralException
:
4174 def on_show_offline_contacts_menuitem_activate(self
, widget
):
4176 When show offline option is changed: redraw the treeview
4178 gajim
.config
.set('showoffline', not gajim
.config
.get('showoffline'))
4179 self
.refilter_shown_roster_items()
4180 w
= self
.xml
.get_object('show_only_active_contacts_menuitem')
4181 if gajim
.config
.get('showoffline'):
4182 # We need to filter twice to show groups with no contacts inside
4183 # in the correct expand state
4184 self
.refilter_shown_roster_items()
4185 w
.set_sensitive(False)
4187 w
.set_sensitive(True)
4189 def on_show_only_active_contacts_menuitem_activate(self
, widget
):
4191 When show only active contact option is changed: redraw the treeview
4193 gajim
.config
.set('show_only_chat_and_online', not gajim
.config
.get(
4194 'show_only_chat_and_online'))
4195 self
.refilter_shown_roster_items()
4196 w
= self
.xml
.get_object('show_offline_contacts_menuitem')
4197 if gajim
.config
.get('show_only_chat_and_online'):
4198 # We need to filter twice to show groups with no contacts inside
4199 # in the correct expand state
4200 self
.refilter_shown_roster_items()
4201 w
.set_sensitive(False)
4203 w
.set_sensitive(True)
4205 def on_view_menu_activate(self
, widget
):
4206 # Hide the show roster menu if we are not in the right windowing mode.
4207 if self
.hpaned
.get_child2() is not None:
4208 self
.xml
.get_object('show_roster_menuitem').show()
4210 self
.xml
.get_object('show_roster_menuitem').hide()
4212 def on_show_roster_menuitem_toggled(self
, widget
):
4213 # when num controls is 0 this menuitem is hidden, but still need to
4214 # disable keybinding
4215 if self
.hpaned
.get_child2() is not None:
4216 self
.show_roster_vbox(widget
.get_active())
4218 def on_rfilter_entry_changed(self
, widget
):
4219 """ When we update the content of the filter """
4220 self
.rfilter_string
= widget
.get_text().lower()
4221 if self
.rfilter_string
== '':
4222 self
.disable_rfilter()
4223 self
.refilter_shown_roster_items()
4225 def on_rfilter_entry_icon_press(self
, widget
, icon
, event
):
4227 Disable the roster filtering by clicking the icon in the textEntry
4229 self
.disable_rfilter()
4231 def enable_rfilter(self
, search_string
):
4232 self
.rfilter_enabled
= True
4233 self
.rfilter_entry
.set_visible(True)
4234 self
.rfilter_entry
.set_editable(True)
4235 self
.rfilter_entry
.set_text(search_string
)
4236 self
.rfilter_entry
.grab_focus()
4237 self
.rfilter_entry
.set_position(-1)
4239 def disable_rfilter(self
):
4240 self
.rfilter_enabled
= False
4241 self
.rfilter_entry
.set_visible(False)
4242 self
.rfilter_entry
.set_editable(False)
4243 self
.refilter_shown_roster_items()
4244 self
.tree
.grab_focus()
4246 def on_roster_hpaned_notify(self
, pane
, gparamspec
):
4248 Keep changing the width of the roster
4249 (when a gtk.Paned widget handle is dragged)
4251 if gparamspec
.name
== 'position':
4252 roster_width
= pane
.get_child1().allocation
.width
4253 gajim
.config
.set('roster_width', roster_width
)
4255 ################################################################################
4256 ### Drag and Drop handling
4257 ################################################################################
4259 def drag_data_get_data(self
, treeview
, context
, selection
, target_id
,
4261 model
, list_of_paths
= self
.tree
.get_selection().get_selected_rows()
4262 if len(list_of_paths
) != 1:
4264 path
= list_of_paths
[0]
4267 data
= model
[path
][C_JID
]
4268 selection
.set(selection
.target
, 8, data
)
4270 def drag_begin(self
, treeview
, context
):
4271 self
.dragging
= True
4273 def drag_end(self
, treeview
, context
):
4274 self
.dragging
= False
4276 def on_drop_rosterx(self
, widget
, account_source
, c_source
, account_dest
,
4277 c_dest
, was_big_brother
, context
, etime
):
4278 gajim
.connections
[account_dest
].send_contacts([c_source
], c_dest
.jid
)
4280 def on_drop_in_contact(self
, widget
, account_source
, c_source
, account_dest
,
4281 c_dest
, was_big_brother
, context
, etime
):
4283 if not gajim
.connections
[account_source
].private_storage_supported
or \
4284 not gajim
.connections
[account_dest
].private_storage_supported
:
4285 dialogs
.WarningDialog(_('Metacontacts storage not supported by '
4287 _('Your server does not support storing metacontacts '
4288 'information. So those information will not be saved on next '
4291 def merge_contacts(is_checked
=None):
4293 if is_checked
is not None: # dialog has been shown
4294 if is_checked
: # user does not want to be asked again
4295 gajim
.config
.set('confirm_metacontacts', 'no')
4297 gajim
.config
.set('confirm_metacontacts', 'yes')
4299 # We might have dropped on a metacontact.
4300 # Remove it and readd later with updated family info
4301 dest_family
= gajim
.contacts
.get_metacontacts_family(account_dest
,
4304 self
._remove
_metacontact
_family
(dest_family
, account_dest
)
4305 source_family
= gajim
.contacts
.get_metacontacts_family(
4306 account_source
, c_source
.jid
)
4307 if dest_family
== source_family
:
4308 n
= contacts
= len(dest_family
)
4309 for tag
in source_family
:
4310 if tag
['jid'] == c_source
.jid
:
4311 tag
['order'] = contacts
4317 self
._remove
_entity
(c_dest
, account_dest
)
4319 old_family
= gajim
.contacts
.get_metacontacts_family(account_source
,
4321 old_groups
= c_source
.groups
4323 # Remove old source contact(s)
4325 # We have got little brothers. Readd them all
4326 self
._remove
_metacontact
_family
(old_family
, account_source
)
4328 # We are only a litle brother. Simply remove us from our big
4330 if self
._get
_contact
_iter
(c_source
.jid
, account_source
):
4331 # When we have been in the group before.
4332 # Do not try to remove us again
4333 self
._remove
_entity
(c_source
, account_source
)
4336 own_data
['jid'] = c_source
.jid
4337 own_data
['account'] = account_source
4338 # Don't touch the rest of the family
4339 old_family
= [own_data
]
4341 # Apply new tag and update contact
4342 for data
in old_family
:
4343 if account_source
!= data
['account'] and not self
.regroup
:
4346 _account
= data
['account']
4348 _contact
= gajim
.contacts
.get_first_contact_from_jid(_account
,
4351 # One of the metacontacts may be not connected.
4354 _contact
.groups
= c_dest
.groups
[:]
4355 gajim
.contacts
.add_metacontact(account_dest
, c_dest
.jid
,
4356 _account
, _contact
.jid
, contacts
)
4357 gajim
.connections
[account_source
].update_contact(_contact
.jid
,
4358 _contact
.name
, _contact
.groups
)
4360 # Re-add all and update GUI
4361 new_family
= gajim
.contacts
.get_metacontacts_family(account_source
,
4363 brothers
= self
._add
_metacontact
_family
(new_family
, account_source
)
4365 for c
, acc
in brothers
:
4366 self
.draw_completely(c
.jid
, acc
)
4368 old_groups
.extend(c_dest
.groups
)
4369 for g
in old_groups
:
4370 self
.draw_group(g
, account_source
)
4372 self
.draw_account(account_source
)
4373 context
.finish(True, True, etime
)
4375 confirm_metacontacts
= gajim
.config
.get('confirm_metacontacts')
4376 if confirm_metacontacts
== 'no':
4379 pritext
= _('You are about to create a metacontact. Are you sure you '
4380 'want to continue?')
4381 sectext
= _('Metacontacts are a way to regroup several contacts in one '
4382 'line. Generally it is used when the same person has several '
4383 'Jabber accounts or transport accounts.')
4384 dlg
= dialogs
.ConfirmationDialogCheck(pritext
, sectext
,
4385 _('_Do not ask me again'), on_response_ok
=merge_contacts
)
4386 if not confirm_metacontacts
: # First time we see this window
4387 dlg
.checkbutton
.set_active(True)
4389 def on_drop_in_group(self
, widget
, account
, c_source
, grp_dest
,
4390 is_big_brother
, context
, etime
, grp_source
= None):
4392 # add whole metacontact to new group
4393 self
.add_contact_to_groups(c_source
.jid
, account
, [grp_dest
, ])
4394 # remove afterwards so the contact is not moved to General in the
4396 if grp_dest
!= grp_source
:
4397 self
.remove_contact_from_groups(c_source
.jid
, account
,
4400 # Normal contact or little brother
4401 family
= gajim
.contacts
.get_metacontacts_family(account
,
4405 # Remove whole family. Remove us from the family.
4406 # Then re-add other family members.
4407 self
._remove
_metacontact
_family
(family
, account
)
4408 gajim
.contacts
.remove_metacontact(account
, c_source
.jid
)
4410 if account
!= data
['account'] and not self
.regroup
:
4412 if data
['jid'] == c_source
.jid
and\
4413 data
['account'] == account
:
4415 self
.add_contact(data
['jid'], data
['account'])
4418 self
.add_contact_to_groups(c_source
.jid
, account
, [grp_dest
, ])
4422 self
.add_contact_to_groups(c_source
.jid
, account
, [grp_dest
, ])
4423 # remove afterwards so the contact is not moved to General in
4425 if grp_dest
!= grp_source
:
4426 self
.remove_contact_from_groups(c_source
.jid
, account
,
4429 if context
.action
in (gtk
.gdk
.ACTION_MOVE
, gtk
.gdk
.ACTION_COPY
):
4430 context
.finish(True, True, etime
)
4432 def drag_drop(self
, treeview
, context
, x
, y
, timestamp
):
4433 target_list
= treeview
.drag_dest_get_target_list()
4434 target
= treeview
.drag_dest_find_target(context
, target_list
)
4435 treeview
.drag_get_data(context
, target
)
4436 context
.finish(False, True)
4439 def move_group(self
, old_name
, new_name
, account
):
4440 for group
in gajim
.groups
[account
].keys():
4441 if group
.startswith(old_name
):
4442 self
.rename_group(group
, group
.replace(old_name
, new_name
),
4445 def drag_data_received_data(self
, treeview
, context
, x
, y
, selection
, info
,
4447 treeview
.stop_emission('drag_data_received')
4448 drop_info
= treeview
.get_dest_row_at_pos(x
, y
)
4451 if not selection
.data
:
4452 return # prevents tb when several entrys are dragged
4453 model
= treeview
.get_model()
4454 data
= selection
.data
4455 path_dest
, position
= drop_info
4457 if position
== gtk
.TREE_VIEW_DROP_BEFORE
and len(path_dest
) == 2 \
4458 and path_dest
[1] == 0: # dropped before the first group
4460 if position
== gtk
.TREE_VIEW_DROP_BEFORE
and len(path_dest
) == 2:
4461 # dropped before a group: we drop it in the previous group every
4463 path_dest
= (path_dest
[0], path_dest
[1]-1)
4464 # destination: the row something got dropped on
4465 iter_dest
= model
.get_iter(path_dest
)
4466 type_dest
= model
[iter_dest
][C_TYPE
].decode('utf-8')
4467 jid_dest
= model
[iter_dest
][C_JID
].decode('utf-8')
4468 account_dest
= model
[iter_dest
][C_ACCOUNT
].decode('utf-8')
4470 # drop on account row in merged mode, we cannot know the desired account
4471 if account_dest
== 'all':
4473 # nothing can be done, if destination account is offline
4474 if gajim
.connections
[account_dest
].connected
< 2:
4477 # A file got dropped on the roster
4478 if info
== self
.TARGET_TYPE_URI_LIST
:
4479 if len(path_dest
) < 3:
4481 if type_dest
!= 'contact':
4483 c_dest
= gajim
.contacts
.get_contact_with_highest_priority(
4484 account_dest
, jid_dest
)
4485 if not c_dest
.supports(NS_FILE
):
4488 uri_splitted
= uri
.split() # we may have more than one file dropped
4490 # This is always the last element in windows
4491 uri_splitted
.remove('\0')
4494 nb_uri
= len(uri_splitted
)
4497 for a_uri
in uri_splitted
:
4498 path
= helpers
.get_file_path_from_dnd_dropped_uri(a_uri
)
4499 if not os
.path
.isfile(path
):
4500 bad_uris
.append(a_uri
)
4502 dialogs
.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris
))
4504 def _on_send_files(account
, jid
, uris
):
4505 c
= gajim
.contacts
.get_contact_with_highest_priority(account
,
4508 path
= helpers
.get_file_path_from_dnd_dropped_uri(uri
)
4509 if os
.path
.isfile(path
): # is it file?
4510 gajim
.interface
.instances
['file_transfers'].send_file(
4512 # Popup dialog to confirm sending
4513 prim_text
= 'Send file?'
4514 sec_text
= i18n
.ngettext('Do you want to send this file to %s:',
4515 'Do you want to send these files to %s:', nb_uri
) %\
4516 c_dest
.get_shown_name()
4517 for uri
in uri_splitted
:
4518 path
= helpers
.get_file_path_from_dnd_dropped_uri(uri
)
4519 sec_text
+= '\n' + os
.path
.basename(path
)
4520 dialog
= dialogs
.NonModalConfirmationDialog(prim_text
, sec_text
,
4521 on_response_ok
=(_on_send_files
, account_dest
, jid_dest
,
4526 # a roster entry was dragged and dropped somewhere in the roster
4528 # source: the row that was dragged
4529 path_source
= treeview
.get_selection().get_selected_rows()[1][0]
4530 iter_source
= model
.get_iter(path_source
)
4531 type_source
= model
[iter_source
][C_TYPE
]
4532 account_source
= model
[iter_source
][C_ACCOUNT
].decode('utf-8')
4534 if gajim
.config
.get_per('accounts', account_source
, 'is_zeroconf'):
4537 if type_dest
== 'self_contact':
4538 # drop on self contact row
4541 if type_dest
== 'groupchat':
4542 # drop on a minimized groupchat
4543 # TODO: Invite to groupchat if type_dest = contact
4546 if type_source
== 'group':
4547 if account_source
!= account_dest
:
4548 # drop on another account
4550 grp_source
= model
[iter_source
][C_JID
].decode('utf-8')
4551 delimiter
= gajim
.connections
[account_source
].nested_group_delimiter
4552 grp_source_list
= grp_source
.split(delimiter
)
4554 if type_dest
== 'account':
4555 new_grp
= grp_source_list
[-1]
4556 elif type_dest
== 'group':
4557 new_grp
= model
[iter_dest
][C_JID
].decode('utf-8') + delimiter
+\
4560 self
.move_group(grp_source
, new_grp
, account_source
)
4562 # Only normal contacts and group can be dragged
4563 if type_source
!= 'contact':
4566 # A contact was dropped
4567 if gajim
.config
.get_per('accounts', account_dest
, 'is_zeroconf'):
4568 # drop on zeroconf account, adding not possible
4571 if type_dest
== 'account' and account_source
== account_dest
:
4572 # drop on the account it was dragged from
4575 # Get valid source group, jid and contact
4577 while model
[it
][C_TYPE
] == 'contact':
4578 it
= model
.iter_parent(it
)
4579 grp_source
= model
[it
][C_JID
].decode('utf-8')
4580 if grp_source
in helpers
.special_groups
and \
4581 grp_source
not in ('Not in Roster', 'Observers'):
4582 # a transport or a minimized groupchat was dragged
4583 # we can add it to other accounts but not move it to another group,
4586 jid_source
= data
.decode('utf-8')
4587 c_source
= gajim
.contacts
.get_contact_with_highest_priority(
4588 account_source
, jid_source
)
4590 # Get destination group
4592 if type_dest
== 'group':
4593 grp_dest
= model
[iter_dest
][C_JID
].decode('utf-8')
4594 elif type_dest
in ('contact', 'agent'):
4596 while model
[it
][C_TYPE
] != 'group':
4597 it
= model
.iter_parent(it
)
4598 grp_dest
= model
[it
][C_JID
].decode('utf-8')
4599 if grp_dest
in helpers
.special_groups
:
4602 if jid_source
== jid_dest
:
4603 if grp_source
== grp_dest
and account_source
== account_dest
:
4607 # contact drop somewhere in or on a foreign account
4608 if (type_dest
== 'account' or not self
.regroup
) and \
4609 account_source
!= account_dest
:
4610 # add to account in specified group
4611 dialogs
.AddNewContactWindow(account
=account_dest
, jid
=jid_source
,
4612 user_nick
=c_source
.name
, group
=grp_dest
)
4615 # we may not add contacts from special_groups
4616 if grp_source
in helpers
.special_groups
:
4619 # Is the contact we drag a meta contact?
4620 accounts
= (self
.regroup
and gajim
.contacts
.get_accounts()) or \
4622 is_big_brother
= gajim
.contacts
.is_big_brother(account_source
,
4623 jid_source
, accounts
)
4625 drop_in_middle_of_meta
= False
4626 if type_dest
== 'contact':
4627 if position
== gtk
.TREE_VIEW_DROP_BEFORE
and len(path_dest
) == 4:
4628 drop_in_middle_of_meta
= True
4629 if position
== gtk
.TREE_VIEW_DROP_AFTER
and (len(path_dest
) == 4 or\
4630 self
.modelfilter
.iter_has_child(iter_dest
)):
4631 drop_in_middle_of_meta
= True
4632 # Contact drop on group row or between two contacts that are
4634 if (type_dest
== 'group' or position
in (gtk
.TREE_VIEW_DROP_BEFORE
,
4635 gtk
.TREE_VIEW_DROP_AFTER
)) and not drop_in_middle_of_meta
:
4636 self
.on_drop_in_group(None, account_source
, c_source
, grp_dest
,
4637 is_big_brother
, context
, etime
, grp_source
)
4640 # Contact drop on another contact, make meta contacts
4641 if position
== gtk
.TREE_VIEW_DROP_INTO_OR_AFTER
or \
4642 position
== gtk
.TREE_VIEW_DROP_INTO_OR_BEFORE
or drop_in_middle_of_meta
:
4643 c_dest
= gajim
.contacts
.get_contact_with_highest_priority(
4644 account_dest
, jid_dest
)
4646 # c_dest is None if jid_dest doesn't belong to account
4649 item
= gtk
.MenuItem(_('Send %s to %s') % (c_source
.get_shown_name(),
4650 c_dest
.get_shown_name()))
4651 item
.connect('activate', self
.on_drop_rosterx
, account_source
,
4652 c_source
, account_dest
, c_dest
, is_big_brother
, context
, etime
)
4655 dest_family
= gajim
.contacts
.get_metacontacts_family(account_dest
,
4657 source_family
= gajim
.contacts
.get_metacontacts_family(
4658 account_source
, c_source
.jid
)
4659 if dest_family
== source_family
:
4660 item
= gtk
.MenuItem(_('Make %s first contact') % (
4661 c_source
.get_shown_name()))
4663 item
= gtk
.MenuItem(_('Make %s and %s metacontacts') % (
4664 c_source
.get_shown_name(), c_dest
.get_shown_name()))
4666 item
.connect('activate', self
.on_drop_in_contact
, account_source
,
4667 c_source
, account_dest
, c_dest
, is_big_brother
, context
, etime
)
4671 menu
.attach_to_widget(self
.tree
, None)
4672 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
4674 menu
.popup(None, None, None, 1, etime
)
4676 ################################################################################
4677 ### Everything about images and icons....
4678 ### Cleanup assigned to Jim++ :-)
4679 ################################################################################
4681 def get_appropriate_state_images(self
, jid
, size
='16', icon_name
='online'):
4683 Check jid and return the appropriate state images dict for the demanded
4684 size. icon_name is taken into account when jid is from transport:
4685 transport iconset doesn't contain all icons, so we fall back to jabber
4688 transport
= gajim
.get_transport_name_from_jid(jid
)
4689 if transport
and size
in self
.transports_state_images
:
4690 if transport
not in self
.transports_state_images
[size
]:
4691 # we don't have iconset for this transport loaded yet. Let's do
4693 self
.make_transport_state_images(transport
)
4694 if transport
in self
.transports_state_images
[size
] and \
4695 icon_name
in self
.transports_state_images
[size
][transport
]:
4696 return self
.transports_state_images
[size
][transport
]
4697 return gajim
.interface
.jabber_state_images
[size
]
4699 def make_transport_state_images(self
, transport
):
4701 Initialize opened and closed 'transport' iconset dict
4703 if gajim
.config
.get('use_transports_iconsets'):
4704 folder
= os
.path
.join(helpers
.get_transport_path(transport
),
4706 pixo
, pixc
= gtkgui_helpers
.load_icons_meta()
4707 self
.transports_state_images
['opened'][transport
] = \
4708 gtkgui_helpers
.load_iconset(folder
, pixo
, transport
=True)
4709 self
.transports_state_images
['closed'][transport
] = \
4710 gtkgui_helpers
.load_iconset(folder
, pixc
, transport
=True)
4711 folder
= os
.path
.join(helpers
.get_transport_path(transport
),
4713 self
.transports_state_images
['32'][transport
] = \
4714 gtkgui_helpers
.load_iconset(folder
, transport
=True)
4715 folder
= os
.path
.join(helpers
.get_transport_path(transport
),
4717 self
.transports_state_images
['16'][transport
] = \
4718 gtkgui_helpers
.load_iconset(folder
, transport
=True)
4720 def update_jabber_state_images(self
):
4722 self
.setup_and_draw_roster()
4723 # Update the status combobox
4724 model
= self
.status_combobox
.get_model()
4725 titer
= model
.get_iter_root()
4727 if model
[titer
][2] != '':
4728 # If it's not change status message iter
4729 # eg. if it has show parameter not ''
4730 model
[titer
][1] = gajim
.interface
.jabber_state_images
['16'][
4732 titer
= model
.iter_next(titer
)
4733 # Update the systray
4734 if gajim
.interface
.systray_enabled
:
4735 gajim
.interface
.systray
.set_img()
4737 for win
in gajim
.interface
.msg_win_mgr
.windows():
4738 for ctrl
in win
.controls():
4740 win
.redraw_tab(ctrl
)
4742 self
.update_status_combobox()
4744 def set_account_status_icon(self
, account
):
4745 status
= gajim
.connections
[account
].connected
4746 child_iterA
= self
._get
_account
_iter
(account
, self
.model
)
4749 if not self
.regroup
:
4750 show
= gajim
.SHOW_LIST
[status
]
4751 else: # accounts merged
4752 show
= helpers
.get_global_show()
4753 self
.model
[child_iterA
][C_IMG
] = gajim
.interface
.jabber_state_images
[
4756 ################################################################################
4757 ### Style and theme related methods
4758 ################################################################################
4760 def show_title(self
):
4761 change_title_allowed
= gajim
.config
.get('change_roster_title')
4762 if not change_title_allowed
:
4765 if gajim
.config
.get('one_message_window') == 'always_with_roster':
4766 # always_with_roster mode defers to the MessageWindow
4767 if not gajim
.interface
.msg_win_mgr
.one_window_opened():
4768 # No MessageWindow to defer to
4769 self
.window
.set_title('Gajim')
4774 for account
in gajim
.connections
:
4775 # Count events in roster title only if we don't auto open them
4776 if not helpers
.allow_popup_window(account
):
4777 nb_unread
+= gajim
.events
.get_nb_events(['chat', 'normal',
4778 'file-request', 'file-error', 'file-completed',
4779 'file-request-error', 'file-send-error', 'file-stopped',
4780 'printed_chat'], account
)
4782 start
= '[' + str(nb_unread
) + '] '
4783 elif nb_unread
== 1:
4786 self
.window
.set_title(start
+ 'Gajim')
4788 gtkgui_helpers
.set_unset_urgency_hint(self
.window
, nb_unread
)
4790 def _change_style(self
, model
, path
, titer
, option
):
4791 if option
is None or model
[titer
][C_TYPE
] == option
:
4792 # We changed style for this type of row
4793 model
[titer
][C_NAME
] = model
[titer
][C_NAME
]
4795 def change_roster_style(self
, option
):
4796 self
.model
.foreach(self
._change
_style
, option
)
4797 for win
in gajim
.interface
.msg_win_mgr
.windows():
4798 win
.repaint_themed_widgets()
4800 def repaint_themed_widgets(self
):
4802 Notify windows that contain themed widgets to repaint them
4804 for win
in gajim
.interface
.msg_win_mgr
.windows():
4805 win
.repaint_themed_widgets()
4806 for account
in gajim
.connections
:
4807 for addr
in gajim
.interface
.instances
[account
]['disco']:
4808 gajim
.interface
.instances
[account
]['disco'][addr
].paint_banner()
4809 for ctrl
in gajim
.interface
.minimized_controls
[account
].values():
4810 ctrl
.repaint_themed_widgets()
4812 def update_avatar_in_gui(self
, jid
, account
):
4814 self
.draw_avatar(jid
, account
)
4815 # Update chat window
4817 ctrl
= gajim
.interface
.msg_win_mgr
.get_control(jid
, account
)
4821 def set_renderer_color(self
, renderer
, style
, set_background
=True):
4823 Set style for treeview cell, using PRELIGHT system color
4826 bgcolor
= self
.tree
.style
.bg
[style
]
4827 renderer
.set_property('cell-background-gdk', bgcolor
)
4829 fgcolor
= self
.tree
.style
.fg
[style
]
4830 renderer
.set_property('foreground-gdk', fgcolor
)
4832 def _iconCellDataFunc(self
, column
, renderer
, model
, titer
, data
=None):
4834 When a row is added, set properties for icon renderer
4836 type_
= model
[titer
][C_TYPE
]
4837 if type_
== 'account':
4838 self
._set
_account
_row
_background
_color
(renderer
)
4839 renderer
.set_property('xalign', 0)
4840 elif type_
== 'group':
4841 self
._set
_group
_row
_background
_color
(renderer
)
4842 parent_iter
= model
.iter_parent(titer
)
4843 if model
[parent_iter
][C_TYPE
] == 'group':
4844 renderer
.set_property('xalign', 0.4)
4846 renderer
.set_property('xalign', 0.2)
4848 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4849 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4850 # This can append when at the moment we add the row
4852 jid
= model
[titer
][C_JID
].decode('utf-8')
4853 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4854 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4855 parent_iter
= model
.iter_parent(titer
)
4856 if model
[parent_iter
][C_TYPE
] == 'contact':
4857 renderer
.set_property('xalign', 1)
4859 renderer
.set_property('xalign', 0.6)
4860 renderer
.set_property('width', 26)
4862 def _nameCellDataFunc(self
, column
, renderer
, model
, titer
, data
=None):
4864 When a row is added, set properties for name renderer
4866 theme
= gajim
.config
.get('roster_theme')
4867 type_
= model
[titer
][C_TYPE
]
4868 if type_
== 'account':
4869 color
= gajim
.config
.get_per('themes', theme
, 'accounttextcolor')
4871 renderer
.set_property('foreground', color
)
4873 self
.set_renderer_color(renderer
, gtk
.STATE_ACTIVE
, False)
4874 renderer
.set_property('font',
4875 gtkgui_helpers
.get_theme_font_for_option(theme
, 'accountfont'))
4876 renderer
.set_property('xpad', 0)
4877 renderer
.set_property('width', 3)
4878 self
._set
_account
_row
_background
_color
(renderer
)
4879 elif type_
== 'group':
4880 color
= gajim
.config
.get_per('themes', theme
, 'grouptextcolor')
4882 renderer
.set_property('foreground', color
)
4884 self
.set_renderer_color(renderer
, gtk
.STATE_PRELIGHT
, False)
4885 renderer
.set_property('font',
4886 gtkgui_helpers
.get_theme_font_for_option(theme
, 'groupfont'))
4887 parent_iter
= model
.iter_parent(titer
)
4888 if model
[parent_iter
][C_TYPE
] == 'group':
4889 renderer
.set_property('xpad', 8)
4891 renderer
.set_property('xpad', 4)
4892 self
._set
_group
_row
_background
_color
(renderer
)
4894 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4895 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4896 # This can append when at the moment we add the row
4898 jid
= model
[titer
][C_JID
].decode('utf-8')
4899 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4901 if type_
== 'groupchat':
4902 ctrl
= gajim
.interface
.minimized_controls
[account
].get(jid
,
4904 if ctrl
and ctrl
.attention_flag
:
4905 color
= gajim
.config
.get_per('themes', theme
,
4906 'state_muc_directed_msg_color')
4907 renderer
.set_property('foreground', 'red')
4909 color
= gajim
.config
.get_per('themes', theme
,
4912 renderer
.set_property('foreground', color
)
4914 renderer
.set_property('foreground', None)
4915 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4916 renderer
.set_property('font',
4917 gtkgui_helpers
.get_theme_font_for_option(theme
, 'contactfont'))
4918 parent_iter
= model
.iter_parent(titer
)
4919 if model
[parent_iter
][C_TYPE
] == 'contact':
4920 renderer
.set_property('xpad', 16)
4922 renderer
.set_property('xpad', 12)
4924 def _fill_pep_pixbuf_renderer(self
, column
, renderer
, model
, titer
,
4927 When a row is added, draw the respective pep icon
4929 type_
= model
[titer
][C_TYPE
]
4931 # allocate space for the icon only if needed
4932 if not model
[titer
][data
]:
4933 renderer
.set_property('visible', False)
4935 renderer
.set_property('visible', True)
4937 if type_
== 'account':
4938 self
._set
_account
_row
_background
_color
(renderer
)
4939 renderer
.set_property('xalign', 1)
4941 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4942 # This can append at the moment we add the row
4944 jid
= model
[titer
][C_JID
].decode('utf-8')
4945 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4946 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4948 def _fill_avatar_pixbuf_renderer(self
, column
, renderer
, model
, titer
,
4951 When a row is added, set properties for avatar renderer
4953 type_
= model
[titer
][C_TYPE
]
4954 if type_
in ('group', 'account'):
4955 renderer
.set_property('visible', False)
4958 # allocate space for the icon only if needed
4959 if model
[titer
][C_AVATAR_PIXBUF
] or \
4960 gajim
.config
.get('avatar_position_in_roster') == 'left':
4961 renderer
.set_property('visible', True)
4963 # prevent type_ = None, see http://trac.gajim.org/ticket/2534
4964 if not model
[titer
][C_JID
] or not model
[titer
][C_ACCOUNT
]:
4965 # This can append at the moment we add the row
4967 jid
= model
[titer
][C_JID
].decode('utf-8')
4968 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
4969 self
._set
_contact
_row
_background
_color
(renderer
, jid
, account
)
4971 renderer
.set_property('visible', False)
4973 if gajim
.config
.get('avatar_position_in_roster') == 'left':
4974 renderer
.set_property('width', gajim
.config
.get(
4975 'roster_avatar_width'))
4976 renderer
.set_property('xalign', 0.5)
4978 renderer
.set_property('xalign', 1) # align pixbuf to the right
4980 def _fill_padlock_pixbuf_renderer(self
, column
, renderer
, model
, titer
,
4983 When a row is added, set properties for padlock renderer
4985 type_
= model
[titer
][C_TYPE
]
4986 # allocate space for the icon only if needed
4987 if type_
== 'account' and model
[titer
][C_PADLOCK_PIXBUF
]:
4988 renderer
.set_property('visible', True)
4989 self
._set
_account
_row
_background
_color
(renderer
)
4990 renderer
.set_property('xalign', 1) # align pixbuf to the right
4992 renderer
.set_property('visible', False)
4994 def _set_account_row_background_color(self
, renderer
):
4995 theme
= gajim
.config
.get('roster_theme')
4996 color
= gajim
.config
.get_per('themes', theme
, 'accountbgcolor')
4998 renderer
.set_property('cell-background', color
)
5000 self
.set_renderer_color(renderer
, gtk
.STATE_ACTIVE
)
5002 def _set_contact_row_background_color(self
, renderer
, jid
, account
):
5003 theme
= gajim
.config
.get('roster_theme')
5004 if jid
in gajim
.newly_added
[account
]:
5005 renderer
.set_property('cell-background', gajim
.config
.get(
5006 'just_connected_bg_color'))
5007 elif jid
in gajim
.to_be_removed
[account
]:
5008 renderer
.set_property('cell-background', gajim
.config
.get(
5009 'just_disconnected_bg_color'))
5011 color
= gajim
.config
.get_per('themes', theme
, 'contactbgcolor')
5012 renderer
.set_property('cell-background', color
if color
else None)
5014 def _set_group_row_background_color(self
, renderer
):
5015 theme
= gajim
.config
.get('roster_theme')
5016 color
= gajim
.config
.get_per('themes', theme
, 'groupbgcolor')
5018 renderer
.set_property('cell-background', color
)
5020 self
.set_renderer_color(renderer
, gtk
.STATE_PRELIGHT
)
5022 ################################################################################
5023 ### Everything about building menus
5024 ### FIXME: We really need to make it simpler! 1465 lines are a few to much....
5025 ################################################################################
5027 def make_menu(self
, force
=False):
5029 Create the main window's menus
5031 if not force
and not self
.actions_menu_needs_rebuild
:
5033 new_chat_menuitem
= self
.xml
.get_object('new_chat_menuitem')
5034 single_message_menuitem
= self
.xml
.get_object(
5035 'send_single_message_menuitem')
5036 join_gc_menuitem
= self
.xml
.get_object('join_gc_menuitem')
5037 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5039 join_gc_menuitem
.set_image(muc_icon
)
5040 add_new_contact_menuitem
= self
.xml
.get_object(
5041 'add_new_contact_menuitem')
5042 service_disco_menuitem
= self
.xml
.get_object('service_disco_menuitem')
5043 advanced_menuitem
= self
.xml
.get_object('advanced_menuitem')
5044 profile_avatar_menuitem
= self
.xml
.get_object('profile_avatar_menuitem')
5046 # destroy old advanced menus
5047 for m
in self
.advanced_menus
:
5050 # make it sensitive. it is insensitive only if no accounts are
5052 advanced_menuitem
.set_sensitive(True)
5054 if self
.add_new_contact_handler_id
:
5055 add_new_contact_menuitem
.handler_disconnect(
5056 self
.add_new_contact_handler_id
)
5057 self
.add_new_contact_handler_id
= None
5059 if self
.service_disco_handler_id
:
5060 service_disco_menuitem
.handler_disconnect(
5061 self
.service_disco_handler_id
)
5062 self
.service_disco_handler_id
= None
5064 if self
.single_message_menuitem_handler_id
:
5065 single_message_menuitem
.handler_disconnect(
5066 self
.single_message_menuitem_handler_id
)
5067 self
.single_message_menuitem_handler_id
= None
5069 if self
.profile_avatar_menuitem_handler_id
:
5070 profile_avatar_menuitem
.handler_disconnect(
5071 self
.profile_avatar_menuitem_handler_id
)
5072 self
.profile_avatar_menuitem_handler_id
= None
5074 # remove the existing submenus
5075 add_new_contact_menuitem
.remove_submenu()
5076 service_disco_menuitem
.remove_submenu()
5077 join_gc_menuitem
.remove_submenu()
5078 single_message_menuitem
.remove_submenu()
5079 advanced_menuitem
.remove_submenu()
5080 profile_avatar_menuitem
.remove_submenu()
5082 gc_sub_menu
= gtk
.Menu() # gc is always a submenu
5083 join_gc_menuitem
.set_submenu(gc_sub_menu
)
5085 connected_accounts
= gajim
.get_number_of_connected_accounts()
5087 connected_accounts_with_private_storage
= 0
5089 # items that get shown whether an account is zeroconf or not
5090 accounts_list
= sorted(gajim
.contacts
.get_accounts())
5091 if connected_accounts
> 2 or \
5092 (connected_accounts
> 1 and not gajim
.zeroconf_is_connected()):
5093 # 2 or more "real" (no zeroconf) accounts? make submenus
5094 new_chat_sub_menu
= gtk
.Menu()
5096 for account
in accounts_list
:
5097 if gajim
.connections
[account
].connected
<= 1 or \
5098 gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5099 # if offline or connecting or zeroconf
5103 new_chat_item
= gtk
.MenuItem(_('using account %s') % account
,
5105 new_chat_sub_menu
.append(new_chat_item
)
5106 new_chat_item
.connect('activate',
5107 self
.on_new_chat_menuitem_activate
, account
)
5109 new_chat_menuitem
.set_submenu(new_chat_sub_menu
)
5110 new_chat_sub_menu
.show_all()
5112 # menu items that don't apply to zeroconf connections
5113 if connected_accounts
== 1 or (connected_accounts
== 2 and \
5114 gajim
.zeroconf_is_connected()):
5115 # only one 'real' (non-zeroconf) account is connected, don't need
5118 for account
in accounts_list
:
5119 if gajim
.account_is_connected(account
) and \
5120 not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5122 if gajim
.connections
[account
].private_storage_supported
:
5123 connected_accounts_with_private_storage
+= 1
5124 self
.add_bookmarks_list(gc_sub_menu
, account
)
5125 gc_sub_menu
.show_all()
5127 if not self
.add_new_contact_handler_id
:
5128 self
.add_new_contact_handler_id
= \
5129 add_new_contact_menuitem
.connect(
5130 'activate', self
.on_add_new_contact
, account
)
5132 if not self
.service_disco_handler_id
:
5133 self
.service_disco_handler_id
= service_disco_menuitem
.\
5135 self
.on_service_disco_menuitem_activate
, account
)
5138 if not self
.single_message_menuitem_handler_id
:
5139 self
.single_message_menuitem_handler_id
= \
5140 single_message_menuitem
.connect('activate', \
5141 self
.on_send_single_message_menuitem_activate
, account
)
5143 break # No other account connected
5145 # 2 or more 'real' accounts are connected, make submenus
5146 single_message_sub_menu
= gtk
.Menu()
5147 add_sub_menu
= gtk
.Menu()
5148 disco_sub_menu
= gtk
.Menu()
5150 for account
in accounts_list
:
5151 if gajim
.connections
[account
].connected
<= 1 or \
5152 gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5153 # skip account if it's offline or connecting or is zeroconf
5157 single_message_item
= gtk
.MenuItem(_('using account %s') % \
5159 single_message_sub_menu
.append(single_message_item
)
5160 single_message_item
.connect('activate',
5161 self
.on_send_single_message_menuitem_activate
, account
)
5164 if gajim
.connections
[account
].private_storage_supported
:
5165 connected_accounts_with_private_storage
+= 1
5166 gc_item
= gtk
.MenuItem(_('using account %s') % account
, False)
5167 gc_sub_menu
.append(gc_item
)
5168 gc_menuitem_menu
= gtk
.Menu()
5169 self
.add_bookmarks_list(gc_menuitem_menu
, account
)
5170 gc_item
.set_submenu(gc_menuitem_menu
)
5173 add_item
= gtk
.MenuItem(_('to %s account') % account
, False)
5174 add_sub_menu
.append(add_item
)
5175 add_item
.connect('activate', self
.on_add_new_contact
, account
)
5178 disco_item
= gtk
.MenuItem(_('using %s account') % account
,
5180 disco_sub_menu
.append(disco_item
)
5181 disco_item
.connect('activate',
5182 self
.on_service_disco_menuitem_activate
, account
)
5184 single_message_menuitem
.set_submenu(single_message_sub_menu
)
5185 single_message_sub_menu
.show_all()
5186 gc_sub_menu
.show_all()
5187 add_new_contact_menuitem
.set_submenu(add_sub_menu
)
5188 add_sub_menu
.show_all()
5189 service_disco_menuitem
.set_submenu(disco_sub_menu
)
5190 disco_sub_menu
.show_all()
5192 if connected_accounts
== 0:
5193 # no connected accounts, make the menuitems insensitive
5194 for item
in (new_chat_menuitem
, join_gc_menuitem
,
5195 add_new_contact_menuitem
, service_disco_menuitem
,
5196 single_message_menuitem
):
5197 item
.set_sensitive(False)
5198 else: # we have one or more connected accounts
5199 for item
in (new_chat_menuitem
, join_gc_menuitem
,
5200 add_new_contact_menuitem
, service_disco_menuitem
,
5201 single_message_menuitem
):
5202 item
.set_sensitive(True)
5203 # disable some fields if only local account is there
5204 if connected_accounts
== 1:
5205 for account
in gajim
.connections
:
5206 if gajim
.account_is_connected(account
) and \
5207 gajim
.connections
[account
].is_zeroconf
:
5208 for item
in (new_chat_menuitem
, join_gc_menuitem
,
5209 add_new_contact_menuitem
, service_disco_menuitem
,
5210 single_message_menuitem
):
5211 item
.set_sensitive(False)
5213 # Manage GC bookmarks
5214 newitem
= gtk
.SeparatorMenuItem() # separator
5215 gc_sub_menu
.append(newitem
)
5217 newitem
= gtk
.ImageMenuItem(_('_Manage Bookmarks...'))
5218 img
= gtk
.image_new_from_stock(gtk
.STOCK_PREFERENCES
,
5220 newitem
.set_image(img
)
5221 newitem
.connect('activate', self
.on_manage_bookmarks_menuitem_activate
)
5222 gc_sub_menu
.append(newitem
)
5223 gc_sub_menu
.show_all()
5224 if connected_accounts_with_private_storage
== 0:
5225 newitem
.set_sensitive(False)
5227 connected_accounts_with_vcard
= []
5228 for account
in gajim
.connections
:
5229 if gajim
.account_is_connected(account
) and \
5230 gajim
.connections
[account
].vcard_supported
:
5231 connected_accounts_with_vcard
.append(account
)
5232 if len(connected_accounts_with_vcard
) > 1:
5233 # 2 or more accounts? make submenus
5234 profile_avatar_sub_menu
= gtk
.Menu()
5235 for account
in connected_accounts_with_vcard
:
5237 profile_avatar_item
= gtk
.MenuItem(_('of account %s') % account
,
5239 profile_avatar_sub_menu
.append(profile_avatar_item
)
5240 profile_avatar_item
.connect('activate',
5241 self
.on_profile_avatar_menuitem_activate
, account
)
5242 profile_avatar_menuitem
.set_submenu(profile_avatar_sub_menu
)
5243 profile_avatar_sub_menu
.show_all()
5244 elif len(connected_accounts_with_vcard
) == 1:
5245 # user has only one account
5246 account
= connected_accounts_with_vcard
[0]
5248 if not self
.profile_avatar_menuitem_handler_id
:
5249 self
.profile_avatar_menuitem_handler_id
= \
5250 profile_avatar_menuitem
.connect('activate',
5251 self
.on_profile_avatar_menuitem_activate
, account
)
5253 if len(connected_accounts_with_vcard
) == 0:
5254 profile_avatar_menuitem
.set_sensitive(False)
5256 profile_avatar_menuitem
.set_sensitive(True)
5259 if len(gajim
.connections
) == 0: # user has no accounts
5260 advanced_menuitem
.set_sensitive(False)
5261 elif len(gajim
.connections
) == 1: # we have one acccount
5262 account
= gajim
.connections
.keys()[0]
5263 advanced_menuitem_menu
= \
5264 self
.get_and_connect_advanced_menuitem_menu(account
)
5265 self
.advanced_menus
.append(advanced_menuitem_menu
)
5267 self
.add_history_manager_menuitem(advanced_menuitem_menu
)
5269 advanced_menuitem
.set_submenu(advanced_menuitem_menu
)
5270 advanced_menuitem_menu
.show_all()
5271 else: # user has *more* than one account : build advanced submenus
5272 advanced_sub_menu
= gtk
.Menu()
5273 accounts
= [] # Put accounts in a list to sort them
5274 for account
in gajim
.connections
:
5275 accounts
.append(account
)
5277 for account
in accounts
:
5278 advanced_item
= gtk
.MenuItem(_('for account %s') % account
,
5280 advanced_sub_menu
.append(advanced_item
)
5281 advanced_menuitem_menu
= \
5282 self
.get_and_connect_advanced_menuitem_menu(account
)
5283 self
.advanced_menus
.append(advanced_menuitem_menu
)
5284 advanced_item
.set_submenu(advanced_menuitem_menu
)
5286 self
.add_history_manager_menuitem(advanced_sub_menu
)
5288 advanced_menuitem
.set_submenu(advanced_sub_menu
)
5289 advanced_sub_menu
.show_all()
5291 self
.actions_menu_needs_rebuild
= False
5293 def build_account_menu(self
, account
):
5294 # we have to create our own set of icons for the menu
5295 # using self.jabber_status_images is poopoo
5296 iconset
= gajim
.config
.get('iconset')
5297 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5298 state_images
= gtkgui_helpers
.load_iconset(path
)
5300 if not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5301 xml
= gtkgui_helpers
.get_gtk_builder('account_context_menu.ui')
5302 account_context_menu
= xml
.get_object('account_context_menu')
5304 status_menuitem
= xml
.get_object('status_menuitem')
5305 start_chat_menuitem
= xml
.get_object('start_chat_menuitem')
5306 join_group_chat_menuitem
= xml
.get_object(
5307 'join_group_chat_menuitem')
5308 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5310 join_group_chat_menuitem
.set_image(muc_icon
)
5311 open_gmail_inbox_menuitem
= xml
.get_object(
5312 'open_gmail_inbox_menuitem')
5313 add_contact_menuitem
= xml
.get_object('add_contact_menuitem')
5314 service_discovery_menuitem
= xml
.get_object(
5315 'service_discovery_menuitem')
5316 execute_command_menuitem
= xml
.get_object(
5317 'execute_command_menuitem')
5318 edit_account_menuitem
= xml
.get_object('edit_account_menuitem')
5319 sub_menu
= gtk
.Menu()
5320 status_menuitem
.set_submenu(sub_menu
)
5322 for show
in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
5323 uf_show
= helpers
.get_uf_show(show
, use_mnemonic
=True)
5324 item
= gtk
.ImageMenuItem(uf_show
)
5325 icon
= state_images
[show
]
5326 item
.set_image(icon
)
5327 sub_menu
.append(item
)
5328 con
= gajim
.connections
[account
]
5329 if show
== 'invisible' and con
.connected
> 1 and \
5330 not con
.privacy_rules_supported
:
5331 item
.set_sensitive(False)
5333 item
.connect('activate', self
.change_status
, account
, show
)
5335 item
= gtk
.SeparatorMenuItem()
5336 sub_menu
.append(item
)
5338 item
= gtk
.ImageMenuItem(_('_Change Status Message'))
5339 gtkgui_helpers
.add_image_to_menuitem(item
, 'gajim-kbd_input')
5340 sub_menu
.append(item
)
5341 item
.connect('activate', self
.on_change_status_message_activate
,
5343 if gajim
.connections
[account
].connected
< 2:
5344 item
.set_sensitive(False)
5346 item
= gtk
.SeparatorMenuItem()
5347 sub_menu
.append(item
)
5349 uf_show
= helpers
.get_uf_show('offline', use_mnemonic
=True)
5350 item
= gtk
.ImageMenuItem(uf_show
)
5351 icon
= state_images
['offline']
5352 item
.set_image(icon
)
5353 sub_menu
.append(item
)
5354 item
.connect('activate', self
.change_status
, account
, 'offline')
5356 pep_menuitem
= xml
.get_object('pep_menuitem')
5357 if gajim
.connections
[account
].pep_supported
:
5358 pep_submenu
= gtk
.Menu()
5359 pep_menuitem
.set_submenu(pep_submenu
)
5360 def add_item(label
, opt_name
, func
):
5361 item
= gtk
.CheckMenuItem(label
)
5362 pep_submenu
.append(item
)
5363 if not dbus_support
.supported
:
5364 item
.set_sensitive(False)
5366 activ
= gajim
.config
.get_per('accounts', account
,
5368 item
.set_active(activ
)
5369 item
.connect('toggled', func
, account
)
5371 add_item(_('Publish Tune'), 'publish_tune',
5372 self
.on_publish_tune_toggled
)
5373 add_item(_('Publish Location'), 'publish_location',
5374 self
.on_publish_location_toggled
)
5376 pep_config
= gtk
.ImageMenuItem(_('Configure Services...'))
5377 item
= gtk
.SeparatorMenuItem()
5378 pep_submenu
.append(item
)
5379 pep_config
.set_sensitive(True)
5380 pep_submenu
.append(pep_config
)
5381 pep_config
.connect('activate',
5382 self
.on_pep_services_menuitem_activate
, account
)
5383 img
= gtk
.image_new_from_stock(gtk
.STOCK_PREFERENCES
,
5385 pep_config
.set_image(img
)
5388 pep_menuitem
.set_sensitive(False)
5390 if not gajim
.connections
[account
].gmail_url
:
5391 open_gmail_inbox_menuitem
.set_no_show_all(True)
5392 open_gmail_inbox_menuitem
.hide()
5394 open_gmail_inbox_menuitem
.connect('activate',
5395 self
.on_open_gmail_inbox
, account
)
5397 edit_account_menuitem
.connect('activate', self
.on_edit_account
,
5399 add_contact_menuitem
.connect('activate', self
.on_add_new_contact
,
5401 service_discovery_menuitem
.connect('activate',
5402 self
.on_service_disco_menuitem_activate
, account
)
5403 hostname
= gajim
.config
.get_per('accounts', account
, 'hostname')
5404 contact
= gajim
.contacts
.create_contact(jid
=hostname
,
5405 account
=account
) # Fake contact
5406 execute_command_menuitem
.connect('activate',
5407 self
.on_execute_command
, contact
, account
)
5409 start_chat_menuitem
.connect('activate',
5410 self
.on_new_chat_menuitem_activate
, account
)
5412 gc_sub_menu
= gtk
.Menu() # gc is always a submenu
5413 join_group_chat_menuitem
.set_submenu(gc_sub_menu
)
5414 self
.add_bookmarks_list(gc_sub_menu
, account
)
5416 # make some items insensitive if account is offline
5417 if gajim
.connections
[account
].connected
< 2:
5418 for widget
in (add_contact_menuitem
, service_discovery_menuitem
,
5419 join_group_chat_menuitem
, execute_command_menuitem
,
5420 pep_menuitem
, start_chat_menuitem
):
5421 widget
.set_sensitive(False)
5423 xml
= gtkgui_helpers
.get_gtk_builder('zeroconf_context_menu.ui')
5424 account_context_menu
= xml
.get_object('zeroconf_context_menu')
5426 status_menuitem
= xml
.get_object('status_menuitem')
5427 zeroconf_properties_menuitem
= xml
.get_object(
5428 'zeroconf_properties_menuitem')
5429 sub_menu
= gtk
.Menu()
5430 status_menuitem
.set_submenu(sub_menu
)
5432 for show
in ('online', 'away', 'dnd', 'invisible'):
5433 uf_show
= helpers
.get_uf_show(show
, use_mnemonic
=True)
5434 item
= gtk
.ImageMenuItem(uf_show
)
5435 icon
= state_images
[show
]
5436 item
.set_image(icon
)
5437 sub_menu
.append(item
)
5438 item
.connect('activate', self
.change_status
, account
, show
)
5440 item
= gtk
.SeparatorMenuItem()
5441 sub_menu
.append(item
)
5443 item
= gtk
.ImageMenuItem(_('_Change Status Message'))
5444 gtkgui_helpers
.add_image_to_menuitem(item
, 'gajim-kbd_input')
5445 sub_menu
.append(item
)
5446 item
.connect('activate', self
.on_change_status_message_activate
,
5448 if gajim
.connections
[account
].connected
< 2:
5449 item
.set_sensitive(False)
5451 uf_show
= helpers
.get_uf_show('offline', use_mnemonic
=True)
5452 item
= gtk
.ImageMenuItem(uf_show
)
5453 icon
= state_images
['offline']
5454 item
.set_image(icon
)
5455 sub_menu
.append(item
)
5456 item
.connect('activate', self
.change_status
, account
, 'offline')
5458 zeroconf_properties_menuitem
.connect('activate',
5459 self
.on_zeroconf_properties
, account
)
5461 return account_context_menu
5463 def make_account_menu(self
, event
, titer
):
5465 Make account's popup menu
5467 model
= self
.modelfilter
5468 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5470 if account
!= 'all': # not in merged mode
5471 menu
= self
.build_account_menu(account
)
5474 iconset
= gajim
.config
.get('iconset')
5475 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5476 accounts
= [] # Put accounts in a list to sort them
5477 for account
in gajim
.connections
:
5478 accounts
.append(account
)
5480 for account
in accounts
:
5481 state_images
= gtkgui_helpers
.load_iconset(path
)
5482 item
= gtk
.ImageMenuItem(account
)
5483 show
= gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
]
5484 icon
= state_images
[show
]
5485 item
.set_image(icon
)
5486 account_menu
= self
.build_account_menu(account
)
5487 item
.set_submenu(account_menu
)
5490 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5492 menu
.attach_to_widget(self
.tree
, None)
5493 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5495 menu
.popup(None, None, None, event_button
, event
.time
)
5497 def make_group_menu(self
, event
, titer
):
5499 Make group's popup menu
5501 model
= self
.modelfilter
5502 path
= model
.get_path(titer
)
5503 group
= model
[titer
][C_JID
].decode('utf-8')
5504 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5506 list_
= [] # list of (jid, account) tuples
5507 list_online
= [] # list of (jid, account) tuples
5509 group
= model
[titer
][C_JID
]
5510 for jid
in gajim
.contacts
.get_jid_list(account
):
5511 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
5513 if group
in contact
.get_shown_groups():
5514 if contact
.show
not in ('offline', 'error'):
5515 list_online
.append((contact
, account
))
5516 list_
.append((contact
, account
))
5519 # Make special context menu if group is Groupchats
5520 if group
== _('Groupchats'):
5521 maximize_menuitem
= gtk
.ImageMenuItem(_('_Maximize All'))
5522 icon
= gtk
.image_new_from_stock(gtk
.STOCK_GOTO_TOP
,
5524 maximize_menuitem
.set_image(icon
)
5525 maximize_menuitem
.connect('activate',
5526 self
.on_all_groupchat_maximized
, list_
)
5527 menu
.append(maximize_menuitem
)
5529 # Send Group Message
5530 send_group_message_item
= gtk
.ImageMenuItem(
5531 _('Send Group M_essage'))
5532 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
5533 send_group_message_item
.set_image(icon
)
5535 send_group_message_submenu
= gtk
.Menu()
5536 send_group_message_item
.set_submenu(send_group_message_submenu
)
5537 menu
.append(send_group_message_item
)
5539 group_message_to_all_item
= gtk
.MenuItem(_('To all users'))
5540 send_group_message_submenu
.append(group_message_to_all_item
)
5542 group_message_to_all_online_item
= gtk
.MenuItem(
5543 _('To all online users'))
5544 send_group_message_submenu
.append(group_message_to_all_online_item
)
5546 group_message_to_all_online_item
.connect('activate',
5547 self
.on_send_single_message_menuitem_activate
, account
,
5549 group_message_to_all_item
.connect('activate',
5550 self
.on_send_single_message_menuitem_activate
, account
, list_
)
5553 invite_menuitem
= gtk
.ImageMenuItem(_('In_vite to'))
5554 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5556 invite_menuitem
.set_image(muc_icon
)
5558 gui_menu_builder
.build_invite_submenu(invite_menuitem
, list_online
)
5559 menu
.append(invite_menuitem
)
5561 # Send Custom Status
5562 send_custom_status_menuitem
= gtk
.ImageMenuItem(
5563 _('Send Cus_tom Status'))
5564 # add a special img for this menuitem
5565 if helpers
.group_is_blocked(account
, group
):
5566 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
5568 send_custom_status_menuitem
.set_sensitive(False)
5570 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NETWORK
,
5572 send_custom_status_menuitem
.set_image(icon
)
5573 status_menuitems
= gtk
.Menu()
5574 send_custom_status_menuitem
.set_submenu(status_menuitems
)
5575 iconset
= gajim
.config
.get('iconset')
5576 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5577 for s
in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5578 # icon MUST be different instance for every item
5579 state_images
= gtkgui_helpers
.load_iconset(path
)
5580 status_menuitem
= gtk
.ImageMenuItem(helpers
.get_uf_show(s
))
5581 status_menuitem
.connect('activate', self
.on_send_custom_status
,
5583 icon
= state_images
[s
]
5584 status_menuitem
.set_image(icon
)
5585 status_menuitems
.append(status_menuitem
)
5586 menu
.append(send_custom_status_menuitem
)
5588 # there is no singlemessage and custom status for zeroconf
5589 if gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
5590 send_custom_status_menuitem
.set_sensitive(False)
5591 send_group_message_item
.set_sensitive(False)
5593 if not group
in helpers
.special_groups
:
5594 item
= gtk
.SeparatorMenuItem() # separator
5598 rename_item
= gtk
.ImageMenuItem(_('Re_name'))
5599 # add a special img for rename menuitem
5600 gtkgui_helpers
.add_image_to_menuitem(rename_item
, 'gajim-kbd_input')
5601 menu
.append(rename_item
)
5602 rename_item
.connect('activate', self
.on_rename
, 'group', group
,
5608 for g_account
in gajim
.connections
:
5609 if helpers
.group_is_blocked(g_account
, group
):
5612 if helpers
.group_is_blocked(account
, group
):
5615 if is_blocked
and gajim
.connections
[account
].\
5616 privacy_rules_supported
:
5617 unblock_menuitem
= gtk
.ImageMenuItem(_('_Unblock'))
5618 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
,
5620 unblock_menuitem
.set_image(icon
)
5621 unblock_menuitem
.connect('activate', self
.on_unblock
, list_
,
5623 menu
.append(unblock_menuitem
)
5625 block_menuitem
= gtk
.ImageMenuItem(_('_Block'))
5626 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
,
5628 block_menuitem
.set_image(icon
)
5629 block_menuitem
.connect('activate', self
.on_block
, list_
, group
)
5630 menu
.append(block_menuitem
)
5631 if not gajim
.connections
[account
].privacy_rules_supported
:
5632 block_menuitem
.set_sensitive(False)
5635 remove_item
= gtk
.ImageMenuItem(_('_Remove'))
5636 icon
= gtk
.image_new_from_stock(gtk
.STOCK_REMOVE
,
5638 remove_item
.set_image(icon
)
5639 menu
.append(remove_item
)
5640 remove_item
.connect('activate', self
.on_remove_group_item_activated
,
5643 # unsensitive if account is not connected
5644 if gajim
.connections
[account
].connected
< 2:
5645 rename_item
.set_sensitive(False)
5647 # General group cannot be changed
5648 if group
== _('General'):
5649 rename_item
.set_sensitive(False)
5650 remove_item
.set_sensitive(False)
5652 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5654 menu
.attach_to_widget(self
.tree
, None)
5655 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5657 menu
.popup(None, None, None, event_button
, event
.time
)
5659 def make_contact_menu(self
, event
, titer
):
5661 Make contact's popup menu
5663 model
= self
.modelfilter
5664 jid
= model
[titer
][C_JID
].decode('utf-8')
5665 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5666 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
5667 menu
= gui_menu_builder
.get_contact_menu(contact
, account
)
5668 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5669 menu
.attach_to_widget(self
.tree
, None)
5670 menu
.popup(None, None, None, event_button
, event
.time
)
5672 def make_multiple_contact_menu(self
, event
, iters
):
5674 Make group's popup menu
5676 model
= self
.modelfilter
5677 list_
= [] # list of (jid, account) tuples
5678 one_account_offline
= False
5680 privacy_rules_supported
= True
5682 jid
= model
[titer
][C_JID
].decode('utf-8')
5683 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5684 if gajim
.connections
[account
].connected
< 2:
5685 one_account_offline
= True
5686 if not gajim
.connections
[account
].privacy_rules_supported
:
5687 privacy_rules_supported
= False
5688 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
5690 if helpers
.jid_is_blocked(account
, jid
):
5692 list_
.append((contact
, account
))
5696 for (contact
, current_account
) in list_
:
5697 # check that we use the same account for every sender
5698 if account
is not None and account
!= current_account
:
5701 account
= current_account
5702 if account
is not None:
5703 send_group_message_item
= gtk
.ImageMenuItem(
5704 _('Send Group M_essage'))
5705 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
5706 send_group_message_item
.set_image(icon
)
5707 menu
.append(send_group_message_item
)
5708 send_group_message_item
.connect('activate',
5709 self
.on_send_single_message_menuitem_activate
, account
, list_
)
5711 # Invite to Groupchat
5712 invite_item
= gtk
.ImageMenuItem(_('In_vite to'))
5713 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
5715 invite_item
.set_image(muc_icon
)
5717 gui_menu_builder
.build_invite_submenu(invite_item
, list_
)
5718 menu
.append(invite_item
)
5720 item
= gtk
.SeparatorMenuItem() # separator
5723 # Manage Transport submenu
5724 item
= gtk
.ImageMenuItem(_('_Manage Contacts'))
5725 icon
= gtk
.image_new_from_stock(gtk
.STOCK_PROPERTIES
,
5727 item
.set_image(icon
)
5728 manage_contacts_submenu
= gtk
.Menu()
5729 item
.set_submenu(manage_contacts_submenu
)
5733 edit_groups_item
= gtk
.ImageMenuItem(_('Edit _Groups'))
5734 icon
= gtk
.image_new_from_stock(gtk
.STOCK_EDIT
, gtk
.ICON_SIZE_MENU
)
5735 edit_groups_item
.set_image(icon
)
5736 manage_contacts_submenu
.append(edit_groups_item
)
5737 edit_groups_item
.connect('activate', self
.on_edit_groups
, list_
)
5739 item
= gtk
.SeparatorMenuItem() # separator
5740 manage_contacts_submenu
.append(item
)
5743 if is_blocked
and privacy_rules_supported
:
5744 unblock_menuitem
= gtk
.ImageMenuItem(_('_Unblock'))
5745 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
, gtk
.ICON_SIZE_MENU
)
5746 unblock_menuitem
.set_image(icon
)
5747 unblock_menuitem
.connect('activate', self
.on_unblock
, list_
)
5748 manage_contacts_submenu
.append(unblock_menuitem
)
5750 block_menuitem
= gtk
.ImageMenuItem(_('_Block'))
5751 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
, gtk
.ICON_SIZE_MENU
)
5752 block_menuitem
.set_image(icon
)
5753 block_menuitem
.connect('activate', self
.on_block
, list_
)
5754 manage_contacts_submenu
.append(block_menuitem
)
5756 if not privacy_rules_supported
:
5757 block_menuitem
.set_sensitive(False)
5760 remove_item
= gtk
.ImageMenuItem(_('_Remove'))
5761 icon
= gtk
.image_new_from_stock(gtk
.STOCK_REMOVE
, gtk
.ICON_SIZE_MENU
)
5762 remove_item
.set_image(icon
)
5763 manage_contacts_submenu
.append(remove_item
)
5764 remove_item
.connect('activate', self
.on_req_usub
, list_
)
5765 # unsensitive remove if one account is not connected
5766 if one_account_offline
:
5767 remove_item
.set_sensitive(False)
5769 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5771 menu
.attach_to_widget(self
.tree
, None)
5772 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5774 menu
.popup(None, None, None, event_button
, event
.time
)
5776 def make_transport_menu(self
, event
, titer
):
5778 Make transport's popup menu
5780 model
= self
.modelfilter
5781 jid
= model
[titer
][C_JID
].decode('utf-8')
5782 path
= model
.get_path(titer
)
5783 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5784 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
5787 # Send single message
5788 item
= gtk
.ImageMenuItem(_('Send Single Message'))
5789 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
5790 item
.set_image(icon
)
5791 item
.connect('activate',
5792 self
.on_send_single_message_menuitem_activate
, account
, contact
)
5796 if helpers
.jid_is_blocked(account
, jid
):
5799 # Send Custom Status
5800 send_custom_status_menuitem
= gtk
.ImageMenuItem(
5801 _('Send Cus_tom Status'))
5802 # add a special img for this menuitem
5804 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
5806 send_custom_status_menuitem
.set_sensitive(False)
5808 if account
in gajim
.interface
.status_sent_to_users
and \
5809 jid
in gajim
.interface
.status_sent_to_users
[account
]:
5810 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
5811 gajim
.interface
.status_sent_to_users
[account
][jid
]))
5813 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NETWORK
,
5815 send_custom_status_menuitem
.set_image(icon
)
5816 status_menuitems
= gtk
.Menu()
5817 send_custom_status_menuitem
.set_submenu(status_menuitems
)
5818 iconset
= gajim
.config
.get('iconset')
5819 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
5820 for s
in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
5821 # icon MUST be different instance for every item
5822 state_images
= gtkgui_helpers
.load_iconset(path
)
5823 status_menuitem
= gtk
.ImageMenuItem(helpers
.get_uf_show(s
))
5824 status_menuitem
.connect('activate', self
.on_send_custom_status
,
5825 [(contact
, account
)], s
)
5826 icon
= state_images
[s
]
5827 status_menuitem
.set_image(icon
)
5828 status_menuitems
.append(status_menuitem
)
5829 menu
.append(send_custom_status_menuitem
)
5831 item
= gtk
.SeparatorMenuItem() # separator
5835 item
= gtk
.ImageMenuItem(_('Execute Command...'))
5836 icon
= gtk
.image_new_from_stock(gtk
.STOCK_EXECUTE
, gtk
.ICON_SIZE_MENU
)
5837 item
.set_image(icon
)
5839 item
.connect('activate', self
.on_execute_command
, contact
, account
,
5841 if gajim
.account_is_disconnected(account
):
5842 item
.set_sensitive(False)
5844 # Manage Transport submenu
5845 item
= gtk
.ImageMenuItem(_('_Manage Transport'))
5846 icon
= gtk
.image_new_from_stock(gtk
.STOCK_PROPERTIES
,
5848 item
.set_image(icon
)
5849 manage_transport_submenu
= gtk
.Menu()
5850 item
.set_submenu(manage_transport_submenu
)
5854 item
= gtk
.ImageMenuItem(_('_Modify Transport'))
5855 icon
= gtk
.image_new_from_stock(gtk
.STOCK_PREFERENCES
,
5857 item
.set_image(icon
)
5858 manage_transport_submenu
.append(item
)
5859 item
.connect('activate', self
.on_edit_agent
, contact
, account
)
5860 if gajim
.account_is_disconnected(account
):
5861 item
.set_sensitive(False)
5864 item
= gtk
.ImageMenuItem(_('_Rename'))
5865 # add a special img for rename menuitem
5866 gtkgui_helpers
.add_image_to_menuitem(item
, 'gajim-kbd_input')
5867 manage_transport_submenu
.append(item
)
5868 item
.connect('activate', self
.on_rename
, 'agent', jid
, account
)
5869 if gajim
.account_is_disconnected(account
):
5870 item
.set_sensitive(False)
5872 item
= gtk
.SeparatorMenuItem() # separator
5873 manage_transport_submenu
.append(item
)
5877 item
= gtk
.ImageMenuItem(_('_Unblock'))
5878 item
.connect('activate', self
.on_unblock
, [(contact
, account
)])
5880 item
= gtk
.ImageMenuItem(_('_Block'))
5881 item
.connect('activate', self
.on_block
, [(contact
, account
)])
5883 icon
= gtk
.image_new_from_stock(gtk
.STOCK_STOP
, gtk
.ICON_SIZE_MENU
)
5884 item
.set_image(icon
)
5885 manage_transport_submenu
.append(item
)
5886 if gajim
.account_is_disconnected(account
):
5887 item
.set_sensitive(False)
5890 item
= gtk
.ImageMenuItem(_('_Remove'))
5891 icon
= gtk
.image_new_from_stock(gtk
.STOCK_REMOVE
, gtk
.ICON_SIZE_MENU
)
5892 item
.set_image(icon
)
5893 manage_transport_submenu
.append(item
)
5894 item
.connect('activate', self
.on_remove_agent
, [(contact
, account
)])
5895 if gajim
.account_is_disconnected(account
):
5896 item
.set_sensitive(False)
5898 item
= gtk
.SeparatorMenuItem() # separator
5902 information_menuitem
= gtk
.ImageMenuItem(_('_Information'))
5903 icon
= gtk
.image_new_from_stock(gtk
.STOCK_INFO
, gtk
.ICON_SIZE_MENU
)
5904 information_menuitem
.set_image(icon
)
5905 menu
.append(information_menuitem
)
5906 information_menuitem
.connect('activate', self
.on_info
, contact
, account
)
5908 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5910 menu
.attach_to_widget(self
.tree
, None)
5911 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5913 menu
.popup(None, None, None, event_button
, event
.time
)
5915 def make_groupchat_menu(self
, event
, titer
):
5916 model
= self
.modelfilter
5918 jid
= model
[titer
][C_JID
].decode('utf-8')
5919 account
= model
[titer
][C_ACCOUNT
].decode('utf-8')
5920 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
5923 if jid
in gajim
.interface
.minimized_controls
[account
]:
5924 maximize_menuitem
= gtk
.ImageMenuItem(_('_Maximize'))
5925 icon
= gtk
.image_new_from_stock(gtk
.STOCK_GOTO_TOP
,
5927 maximize_menuitem
.set_image(icon
)
5928 maximize_menuitem
.connect('activate', self
.on_groupchat_maximized
, \
5930 menu
.append(maximize_menuitem
)
5932 if not gajim
.gc_connected
[account
].get(jid
, False):
5933 connect_menuitem
= gtk
.ImageMenuItem(_('_Reconnect'))
5934 connect_icon
= gtk
.image_new_from_stock(gtk
.STOCK_CONNECT
, \
5936 connect_menuitem
.set_image(connect_icon
)
5937 connect_menuitem
.connect('activate', self
.on_reconnect
, jid
,
5939 menu
.append(connect_menuitem
)
5940 disconnect_menuitem
= gtk
.ImageMenuItem(_('_Disconnect'))
5941 disconnect_icon
= gtk
.image_new_from_stock(gtk
.STOCK_DISCONNECT
, \
5943 disconnect_menuitem
.set_image(disconnect_icon
)
5944 disconnect_menuitem
.connect('activate', self
.on_disconnect
, jid
,
5946 menu
.append(disconnect_menuitem
)
5948 item
= gtk
.SeparatorMenuItem() # separator
5951 history_menuitem
= gtk
.ImageMenuItem(_('_History'))
5952 history_icon
= gtk
.image_new_from_stock(gtk
.STOCK_JUSTIFY_FILL
, \
5954 history_menuitem
.set_image(history_icon
)
5955 history_menuitem
.connect('activate', self
.on_history
, contact
, account
)
5956 menu
.append(history_menuitem
)
5958 event_button
= gtkgui_helpers
.get_possible_button_event(event
)
5960 menu
.attach_to_widget(self
.tree
, None)
5961 menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
5963 menu
.popup(None, None, None, event_button
, event
.time
)
5965 def get_and_connect_advanced_menuitem_menu(self
, account
):
5967 Add FOR ACCOUNT options
5969 xml
= gtkgui_helpers
.get_gtk_builder('advanced_menuitem_menu.ui')
5970 advanced_menuitem_menu
= xml
.get_object('advanced_menuitem_menu')
5972 xml_console_menuitem
= xml
.get_object('xml_console_menuitem')
5973 archiving_preferences_menuitem
= xml
.get_object(
5974 'archiving_preferences_menuitem')
5975 privacy_lists_menuitem
= xml
.get_object('privacy_lists_menuitem')
5976 administrator_menuitem
= xml
.get_object('administrator_menuitem')
5977 send_server_message_menuitem
= xml
.get_object(
5978 'send_server_message_menuitem')
5979 set_motd_menuitem
= xml
.get_object('set_motd_menuitem')
5980 update_motd_menuitem
= xml
.get_object('update_motd_menuitem')
5981 delete_motd_menuitem
= xml
.get_object('delete_motd_menuitem')
5983 xml_console_menuitem
.connect('activate',
5984 self
.on_xml_console_menuitem_activate
, account
)
5986 if gajim
.connections
[account
]:
5987 if gajim
.connections
[account
].privacy_rules_supported
:
5988 privacy_lists_menuitem
.connect('activate',
5989 self
.on_privacy_lists_menuitem_activate
, account
)
5991 privacy_lists_menuitem
.set_sensitive(False)
5992 if gajim
.connections
[account
].archive_pref_supported
:
5993 archiving_preferences_menuitem
.connect('activate',
5994 self
.on_archiving_preferences_menuitem_activate
, account
)
5996 archiving_preferences_menuitem
.set_sensitive(False)
5998 if gajim
.connections
[account
].is_zeroconf
:
5999 administrator_menuitem
.set_sensitive(False)
6000 send_server_message_menuitem
.set_sensitive(False)
6001 set_motd_menuitem
.set_sensitive(False)
6002 update_motd_menuitem
.set_sensitive(False)
6003 delete_motd_menuitem
.set_sensitive(False)
6005 send_server_message_menuitem
.connect('activate',
6006 self
.on_send_server_message_menuitem_activate
, account
)
6008 set_motd_menuitem
.connect('activate',
6009 self
.on_set_motd_menuitem_activate
, account
)
6011 update_motd_menuitem
.connect('activate',
6012 self
.on_update_motd_menuitem_activate
, account
)
6014 delete_motd_menuitem
.connect('activate',
6015 self
.on_delete_motd_menuitem_activate
, account
)
6017 advanced_menuitem_menu
.show_all()
6019 return advanced_menuitem_menu
6021 def add_history_manager_menuitem(self
, menu
):
6023 Add a seperator and History Manager menuitem BELOW for account menuitems
6025 item
= gtk
.SeparatorMenuItem() # separator
6029 item
= gtk
.ImageMenuItem(_('History Manager'))
6030 icon
= gtk
.image_new_from_stock(gtk
.STOCK_JUSTIFY_FILL
,
6032 item
.set_image(icon
)
6034 item
.connect('activate', self
.on_history_manager_menuitem_activate
)
6036 def add_bookmarks_list(self
, gc_sub_menu
, account
):
6038 Show join new group chat item and bookmarks list for an account
6040 item
= gtk
.ImageMenuItem(_('_Join New Group Chat'))
6041 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
6042 item
.set_image(icon
)
6043 item
.connect('activate', self
.on_join_gc_activate
, account
)
6045 gc_sub_menu
.append(item
)
6047 # User has at least one bookmark.
6048 if gajim
.connections
[account
].bookmarks
:
6049 item
= gtk
.SeparatorMenuItem()
6050 gc_sub_menu
.append(item
)
6052 for bookmark
in gajim
.connections
[account
].bookmarks
:
6053 # Do not use underline.
6054 item
= gtk
.MenuItem(bookmark
['name'], False)
6055 item
.connect('activate', self
.on_bookmark_menuitem_activate
,
6057 gc_sub_menu
.append(item
)
6059 def set_actions_menu_needs_rebuild(self
):
6060 self
.actions_menu_needs_rebuild
= True
6061 # Just handle new_chat_menuitem to have ctrl+N working even if we don't
6063 new_chat_menuitem
= self
.xml
.get_object('new_chat_menuitem')
6064 ag
= gtk
.accel_groups_from_object(self
.window
)[0]
6066 if self
.new_chat_menuitem_handler_id
:
6067 new_chat_menuitem
.handler_disconnect(
6068 self
.new_chat_menuitem_handler_id
)
6069 self
.new_chat_menuitem_handler_id
= None
6071 new_chat_menuitem
.remove_submenu()
6073 connected_accounts
= gajim
.get_number_of_connected_accounts()
6074 if connected_accounts
== 1 or (connected_accounts
== 2 and \
6075 gajim
.zeroconf_is_connected()):
6076 # only one 'real' (non-zeroconf) account is connected, don't need
6078 accounts_list
= sorted(gajim
.contacts
.get_accounts())
6079 for account
in accounts_list
:
6080 if gajim
.account_is_connected(account
) and \
6081 not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
6082 if not self
.new_chat_menuitem_handler_id
:
6083 self
.new_chat_menuitem_handler_id
= new_chat_menuitem
.\
6085 self
.on_new_chat_menuitem_activate
, account
)
6087 def show_appropriate_context_menu(self
, event
, iters
):
6088 # iters must be all of the same type
6089 model
= self
.modelfilter
6090 type_
= model
[iters
[0]][C_TYPE
]
6091 for titer
in iters
[1:]:
6092 if model
[titer
][C_TYPE
] != type_
:
6094 if type_
== 'group' and len(iters
) == 1:
6095 self
.make_group_menu(event
, iters
[0])
6096 if type_
== 'groupchat' and len(iters
) == 1:
6097 self
.make_groupchat_menu(event
, iters
[0])
6098 elif type_
== 'agent' and len(iters
) == 1:
6099 self
.make_transport_menu(event
, iters
[0])
6100 elif type_
in ('contact', 'self_contact') and len(iters
) == 1:
6101 self
.make_contact_menu(event
, iters
[0])
6102 elif type_
== 'contact':
6103 self
.make_multiple_contact_menu(event
, iters
)
6104 elif type_
== 'account' and len(iters
) == 1:
6105 self
.make_account_menu(event
, iters
[0])
6107 def show_treeview_menu(self
, event
):
6109 model
, list_of_paths
= self
.tree
.get_selection().get_selected_rows()
6111 self
.tree
.get_selection().unselect_all()
6113 if not len(list_of_paths
):
6114 # no row is selected
6116 if len(list_of_paths
) > 1:
6118 for path
in list_of_paths
:
6119 iters
.append(model
.get_iter(path
))
6121 path
= list_of_paths
[0]
6122 iters
= [model
.get_iter(path
)]
6123 self
.show_appropriate_context_menu(event
, iters
)
6127 def on_ctrl_j(self
, accel_group
, acceleratable
, keyval
, modifier
):
6129 Bring up the conference join dialog, when CTRL+J accelerator is being
6132 # find a connected account:
6133 for account
in gajim
.connections
:
6134 if gajim
.account_is_connected(account
):
6136 self
.on_join_gc_activate(None, account
)
6139 def fill_column(self
, col
):
6140 for rend
in self
.renderers_list
:
6141 col
.pack_start(rend
[1], expand
=rend
[2])
6142 col
.add_attribute(rend
[1], rend
[3], rend
[4])
6143 col
.set_cell_data_func(rend
[1], rend
[5], rend
[6])
6144 # set renderers propertys
6145 for renderer
in self
.renderers_propertys
.keys():
6146 renderer
.set_property(self
.renderers_propertys
[renderer
][0],
6147 self
.renderers_propertys
[renderer
][1])
6149 ################################################################################
6151 ################################################################################
6154 self
.filtering
= False
6155 # Number of renderers plugins added
6156 self
.nb_ext_renderers
= 0
6157 # [icon, name, type, jid, account, editable, mood_pixbuf,
6158 # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
6160 self
.columns
= [gtk
.Image
, str, str, str, str,
6161 gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
,
6162 gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
, gtk
.gdk
.Pixbuf
]
6163 self
.xml
= gtkgui_helpers
.get_gtk_builder('roster_window.ui')
6164 self
.window
= self
.xml
.get_object('roster_window')
6165 self
.hpaned
= self
.xml
.get_object('roster_hpaned')
6166 gajim
.interface
.msg_win_mgr
= MessageWindowMgr(self
.window
, self
.hpaned
)
6167 gajim
.interface
.msg_win_mgr
.connect('window-delete',
6168 self
.on_message_window_delete
)
6169 self
.advanced_menus
= [] # We keep them to destroy them
6170 if gajim
.config
.get('roster_window_skip_taskbar'):
6171 self
.window
.set_property('skip-taskbar-hint', True)
6172 self
.tree
= self
.xml
.get_object('roster_treeview')
6173 sel
= self
.tree
.get_selection()
6174 sel
.set_mode(gtk
.SELECTION_MULTIPLE
)
6175 # sel.connect('changed',
6176 # self.on_treeview_selection_changed)
6180 self
._iters
['MERGED'] = {'account': None, 'groups': {}}
6181 # holds a list of (jid, account) tupples
6182 self
._last
_selected
_contact
= []
6183 self
.transports_state_images
= {'16': {}, '32': {}, 'opened': {},
6186 self
.last_save_dir
= None
6187 self
.editing_path
= None # path of row with cell in edit mode
6188 self
.add_new_contact_handler_id
= False
6189 self
.service_disco_handler_id
= False
6190 self
.new_chat_menuitem_handler_id
= False
6191 self
.single_message_menuitem_handler_id
= False
6192 self
.profile_avatar_menuitem_handler_id
= False
6193 #FIXME: When list_accel_closures will be wrapped in pygtk
6194 # no need of this variable
6195 self
.have_new_chat_accel
= False # Is the "Ctrl+N" shown ?
6196 self
.set_actions_menu_needs_rebuild()
6197 self
.regroup
= gajim
.config
.get('mergeaccounts')
6198 self
.clicked_path
= None # Used remember on wich row we clicked
6199 if len(gajim
.connections
) < 2:
6200 # Do not merge accounts if only one exists
6201 self
.regroup
= False
6202 gtkgui_helpers
.resize_window(self
.window
,
6203 gajim
.config
.get('roster_width'),
6204 gajim
.config
.get('roster_height'))
6205 if gajim
.config
.get('save-roster-position'):
6206 gtkgui_helpers
.move_window(self
.window
,
6207 gajim
.config
.get('roster_x-position'),
6208 gajim
.config
.get('roster_y-position'))
6210 self
.popups_notification_height
= 0
6211 self
.popup_notification_windows
= []
6213 # Remove contact from roster when last event opened
6214 # { (contact, account): { backend: boolean }
6215 self
.contacts_to_be_removed
= {}
6216 gajim
.events
.event_removed_subscribe(self
.on_event_removed
)
6218 # when this value become 0 we quit main application. If it's more than 0
6219 # it means we are waiting for this number of accounts to disconnect
6221 self
.quit_on_next_offline
= -1
6223 # uf_show, img, show, sensitive
6224 liststore
= gtk
.ListStore(str, gtk
.Image
, str, bool)
6225 self
.status_combobox
= self
.xml
.get_object('status_combobox')
6227 cell
= cell_renderer_image
.CellRendererImage(0, 1)
6228 self
.status_combobox
.pack_start(cell
, False)
6230 # img to show is in in 2nd column of liststore
6231 self
.status_combobox
.add_attribute(cell
, 'image', 1)
6232 # if it will be sensitive or not it is in the fourth column
6233 # all items in the 'row' must have sensitive to False
6234 # if we want False (so we add it for img_cell too)
6235 self
.status_combobox
.add_attribute(cell
, 'sensitive', 3)
6237 cell
= gtk
.CellRendererText()
6238 cell
.set_property('xpad', 5) # padding for status text
6239 self
.status_combobox
.pack_start(cell
, True)
6240 # text to show is in in first column of liststore
6241 self
.status_combobox
.add_attribute(cell
, 'text', 0)
6242 # if it will be sensitive or not it is in the fourth column
6243 self
.status_combobox
.add_attribute(cell
, 'sensitive', 3)
6245 self
.status_combobox
.set_row_separator_func(self
._iter
_is
_separator
)
6247 for show
in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
6248 uf_show
= helpers
.get_uf_show(show
)
6249 liststore
.append([uf_show
,
6250 gajim
.interface
.jabber_state_images
['16'][show
], show
, True])
6251 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6252 liststore
.append(['SEPARATOR', None, '', True])
6254 path
= gtkgui_helpers
.get_icon_path('gajim-kbd_input')
6256 img
.set_from_file(path
)
6257 # sensitivity to False because by default we're offline
6258 self
.status_message_menuitem_iter
= liststore
.append(
6259 [_('Change Status Message...'), img
, '', False])
6260 # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
6261 liststore
.append(['SEPARATOR', None, '', True])
6263 uf_show
= helpers
.get_uf_show('offline')
6264 liststore
.append([uf_show
, gajim
.interface
.jabber_state_images
['16'][
6265 'offline'], 'offline', True])
6267 status_combobox_items
= ['online', 'chat', 'away', 'xa', 'dnd',
6268 'invisible', 'separator1', 'change_status_msg', 'separator2',
6270 self
.status_combobox
.set_model(liststore
)
6272 # default to offline
6273 number_of_menuitem
= status_combobox_items
.index('offline')
6274 self
.status_combobox
.set_active(number_of_menuitem
)
6276 # holds index to previously selected item so if
6277 # "change status message..." is selected we can fallback to previously
6278 # selected item and not stay with that item selected
6279 self
.previous_status_combobox_active
= number_of_menuitem
6281 showOffline
= gajim
.config
.get('showoffline')
6282 showOnlyChatAndOnline
= gajim
.config
.get('show_only_chat_and_online')
6284 w
= self
.xml
.get_object('show_offline_contacts_menuitem')
6285 w
.set_active(showOffline
)
6286 if showOnlyChatAndOnline
:
6287 w
.set_sensitive(False)
6289 w
= self
.xml
.get_object('show_only_active_contacts_menuitem')
6290 w
.set_active(showOnlyChatAndOnline
)
6292 w
.set_sensitive(False)
6294 show_transports_group
= gajim
.config
.get('show_transports_group')
6295 self
.xml
.get_object('show_transports_menuitem').set_active(
6296 show_transports_group
)
6298 self
.xml
.get_object('show_roster_menuitem').set_active(True)
6301 col
= gtk
.TreeViewColumn()
6302 # list of renderers with attributes / properties in the form:
6303 # (name, renderer_object, expand?, attribute_name, attribute_value,
6304 # cell_data_func, func_arg)
6305 self
.renderers_list
= []
6306 self
.renderers_propertys
={}
6307 self
._pep
_type
_to
_model
_column
= {'mood': C_MOOD_PIXBUF
,
6308 'activity': C_ACTIVITY_PIXBUF
, 'tune': C_TUNE_PIXBUF
,
6309 'location': C_LOCATION_PIXBUF
}
6311 renderer_text
= gtk
.CellRendererText()
6312 self
.renderers_propertys
[renderer_text
] = ('ellipsize',
6313 pango
.ELLIPSIZE_END
)
6315 def add_avatar_renderer():
6316 self
.renderers_list
.append(('avatar', gtk
.CellRendererPixbuf(),
6317 False, 'pixbuf', C_AVATAR_PIXBUF
,
6318 self
._fill
_avatar
_pixbuf
_renderer
, None))
6320 if gajim
.config
.get('avatar_position_in_roster') == 'left':
6321 add_avatar_renderer()
6323 self
.renderers_list
+= (
6324 ('icon', cell_renderer_image
.CellRendererImage(0, 0), False,
6325 'image', C_IMG
, self
._iconCellDataFunc
, None),
6327 ('name', renderer_text
, True,
6328 'markup', C_NAME
, self
._nameCellDataFunc
, None),
6330 ('mood', gtk
.CellRendererPixbuf(), False,
6331 'pixbuf', C_MOOD_PIXBUF
,
6332 self
._fill
_pep
_pixbuf
_renderer
, C_MOOD_PIXBUF
),
6334 ('activity', gtk
.CellRendererPixbuf(), False,
6335 'pixbuf', C_ACTIVITY_PIXBUF
,
6336 self
._fill
_pep
_pixbuf
_renderer
, C_ACTIVITY_PIXBUF
),
6338 ('tune', gtk
.CellRendererPixbuf(), False,
6339 'pixbuf', C_TUNE_PIXBUF
,
6340 self
._fill
_pep
_pixbuf
_renderer
, C_TUNE_PIXBUF
),
6342 ('location', gtk
.CellRendererPixbuf(), False,
6343 'pixbuf', C_LOCATION_PIXBUF
,
6344 self
._fill
_pep
_pixbuf
_renderer
, C_LOCATION_PIXBUF
))
6346 if gajim
.config
.get('avatar_position_in_roster') == 'right':
6347 add_avatar_renderer()
6349 self
.renderers_list
.append(('padlock', gtk
.CellRendererPixbuf(), False,
6350 'pixbuf', C_PADLOCK_PIXBUF
,
6351 self
._fill
_padlock
_pixbuf
_renderer
, None))
6353 # fill and append column
6354 self
.fill_column(col
)
6355 self
.tree
.append_column(col
)
6357 # do not show gtk arrows workaround
6358 col
= gtk
.TreeViewColumn()
6359 render_pixbuf
= gtk
.CellRendererPixbuf()
6360 col
.pack_start(render_pixbuf
, expand
=False)
6361 self
.tree
.append_column(col
)
6362 col
.set_visible(False)
6363 self
.tree
.set_expander_column(col
)
6366 self
.TARGET_TYPE_URI_LIST
= 80
6367 TARGETS
= [('MY_TREE_MODEL_ROW',
6368 gtk
.TARGET_SAME_APP | gtk
.TARGET_SAME_WIDGET
, 0)]
6369 TARGETS2
= [('MY_TREE_MODEL_ROW', gtk
.TARGET_SAME_WIDGET
, 0),
6370 ('text/uri-list', 0, self
.TARGET_TYPE_URI_LIST
)]
6371 self
.tree
.enable_model_drag_source(gtk
.gdk
.BUTTON1_MASK
, TARGETS
,
6372 gtk
.gdk
.ACTION_DEFAULT | gtk
.gdk
.ACTION_MOVE | gtk
.gdk
.ACTION_COPY
)
6373 self
.tree
.enable_model_drag_dest(TARGETS2
, gtk
.gdk
.ACTION_DEFAULT
)
6374 self
.tree
.connect('drag_begin', self
.drag_begin
)
6375 self
.tree
.connect('drag_end', self
.drag_end
)
6376 self
.tree
.connect('drag_drop', self
.drag_drop
)
6377 self
.tree
.connect('drag_data_get', self
.drag_data_get_data
)
6378 self
.tree
.connect('drag_data_received', self
.drag_data_received_data
)
6379 self
.dragging
= False
6380 self
.xml
.connect_signals(self
)
6381 self
.combobox_callback_active
= True
6383 self
.collapsed_rows
= gajim
.config
.get('collapsed_rows').split('\t')
6384 self
.tooltip
= tooltips
.RosterTooltip()
6385 # Workaroung: For strange reasons signal is behaving like row-changed
6386 self
._toggeling
_row
= False
6387 self
.setup_and_draw_roster()
6389 if gajim
.config
.get('show_roster_on_startup') == 'always':
6390 self
.window
.show_all()
6391 elif gajim
.config
.get('show_roster_on_startup') == 'never':
6392 if gajim
.config
.get('trayicon') != 'always':
6393 # Without trayicon, user should see the roster!
6394 self
.window
.show_all()
6395 gajim
.config
.set('last_roster_visible', True)
6397 if gajim
.config
.get('last_roster_visible') or \
6398 gajim
.config
.get('trayicon') != 'always':
6399 self
.window
.show_all()
6401 if not gajim
.config
.get_per('accounts') or \
6402 gajim
.config
.get_per('accounts') == ['Local'] and not \
6403 gajim
.config
.get_per('accounts', 'Local', 'active'):
6404 # if we have no account configured or only Local account but not enabled
6406 gajim
.interface
.instances
['account_creation_wizard'] = \
6407 config
.AccountCreationWizardWindow()
6408 # Open wizard only after roster is created, so we can make it
6409 # transient for the roster window
6410 gobject
.idle_add(_open_wizard
)
6411 if not gajim
.ZEROCONF_ACC_NAME
in gajim
.config
.get_per('accounts'):
6412 # Create zeroconf in config file
6413 from common
.zeroconf
import connection_zeroconf
6414 connection_zeroconf
.ConnectionZeroconf(gajim
.ZEROCONF_ACC_NAME
)
6416 # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
6418 accel_group
= gtk
.accel_groups_from_object(self
.window
)[0]
6419 accel_group
.connect_group(gtk
.keysyms
.j
, gtk
.gdk
.CONTROL_MASK
,
6420 gtk
.ACCEL_MASK
, self
.on_ctrl_j
)
6422 # Setting CTRL+N to be the shortcut for show Start chat dialog
6423 new_chat_menuitem
= self
.xml
.get_object('new_chat_menuitem')
6424 new_chat_menuitem
.add_accelerator('activate', accel_group
,
6425 gtk
.keysyms
.n
, gtk
.gdk
.CONTROL_MASK
, gtk
.ACCEL_VISIBLE
)
6427 # Setting the search stuff
6428 self
.rfilter_entry
= self
.xml
.get_object('rfilter_entry')
6429 self
.rfilter_string
= ''
6430 self
.rfilter_enabled
= False
6432 gajim
.ged
.register_event_handler('presence-received', ged
.GUI1
,
6433 self
._nec
_presence
_received
)
6434 # presence has to be fully handled so that contact is added to occupant
6435 # list before roster can be correctly updated
6436 gajim
.ged
.register_event_handler('gc-presence-received', ged
.GUI2
,
6437 self
._nec
_gc
_presence
_received
)
6438 gajim
.ged
.register_event_handler('roster-received', ged
.GUI1
,
6439 self
._nec
_roster
_received
)
6440 gajim
.ged
.register_event_handler('anonymous-auth', ged
.GUI1
,
6441 self
._nec
_anonymous
_auth
)
6442 gajim
.ged
.register_event_handler('our-show', ged
.GUI1
,
6444 gajim
.ged
.register_event_handler('connection-type', ged
.GUI1
,
6445 self
._nec
_connection
_type
)
6446 gajim
.ged
.register_event_handler('agent-removed', ged
.GUI1
,
6447 self
._nec
_agent
_removed
)
6448 gajim
.ged
.register_event_handler('pep-received', ged
.GUI1
,
6449 self
._nec
_pep
_received
)
6450 gajim
.ged
.register_event_handler('vcard-received', ged
.GUI1
,
6451 self
._nec
_vcard
_received
)
6452 gajim
.ged
.register_event_handler('gc-subject-received', ged
.GUI1
,
6453 self
._nec
_gc
_subject
_received
)
6454 gajim
.ged
.register_event_handler('metacontacts-received', ged
.GUI2
,
6455 self
._nec
_metacontacts
_received
)
6456 gajim
.ged
.register_event_handler('signed-in', ged
.GUI1
,
6457 self
._nec
_signed
_in
)
6458 gajim
.ged
.register_event_handler('decrypted-message-received', ged
.GUI2
,
6459 self
._nec
_decrypted
_message
_received
)