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