diff --git a/gajim/roster_window.py b/gajim/roster_window.py deleted file mode 100644 index 113c8c5df5e20143ffea7a52f5404624d40c53db..0000000000000000000000000000000000000000 --- a/gajim/roster_window.py +++ /dev/null @@ -1,5167 +0,0 @@ -# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org> -# Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net> -# Stéphan Kochen <stephan AT kochen.nl> -# Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> -# Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com> -# Nikos Kouremenos <kourem AT gmail.com> -# Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de> -# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> -# Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net> -# James Newton <redshodan AT gmail.com> -# Tomasz Melcer <liori AT exroot.org> -# Julien Pivotto <roidelapluie AT gmail.com> -# Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de> -# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> -# Jonathan Schleifer <js-gajim AT webkeks.org> -# -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import time -import locale -import logging -from enum import IntEnum, unique - -from gi.repository import Gtk -from gi.repository import Gdk -from gi.repository import Pango -from gi.repository import GObject -from gi.repository import GLib -from gi.repository import Gio -from nbxmpp.namespaces import Namespace - -from gajim import dialogs -from gajim import vcard -from gajim import gtkgui_helpers -from gajim import gui_menu_builder - -from gajim.common import app -from gajim.common import helpers -from gajim.common.exceptions import GajimGeneralException -from gajim.common import i18n -from gajim.common.helpers import save_roster_position -from gajim.common.helpers import ask_for_status_message -from gajim.common.i18n import _ -from gajim.common.const import PEPEventType, AvatarSize, StyleAttr -from gajim.common.dbus import location - -from gajim.common import ged - -from gajim.gui.dialogs import DialogButton -from gajim.gui.dialogs import ConfirmationDialog -from gajim.gui.dialogs import ConfirmationCheckDialog -from gajim.gui.dialogs import ErrorDialog -from gajim.gui.dialogs import InputDialog -from gajim.gui.dialogs import InformationDialog -from gajim.gui.add_contact import AddNewContactWindow -from gajim.gui.service_registration import ServiceRegistration -from gajim.gui.discovery import ServiceDiscoveryWindow -from gajim.gui.tooltips import RosterTooltip -from gajim.gui.adhoc import AdHocCommand -from gajim.gui.status_selector import StatusSelector -from gajim.gui.util import get_icon_name -from gajim.gui.util import resize_window -from gajim.gui.util import restore_roster_position -from gajim.gui.util import get_metacontact_surface -from gajim.gui.util import get_builder -from gajim.gui.util import set_urgency_hint -from gajim.gui.util import get_activity_icon_name -from gajim.gui.util import get_account_activity_icon_name -from gajim.gui.util import get_account_mood_icon_name -from gajim.gui.util import get_account_tune_icon_name -from gajim.gui.util import get_account_location_icon_name -from gajim.gui.util import open_window - - -log = logging.getLogger('gajim.roster') - -@unique -class Column(IntEnum): - IMG = 0 # image to show state (online, new message etc) - NAME = 1 # cellrenderer text that holds contact nickname - TYPE = 2 # account, group or contact? - JID = 3 # the jid of the row - ACCOUNT = 4 # cellrenderer text that holds account name - MOOD_PIXBUF = 5 - ACTIVITY_PIXBUF = 6 - TUNE_ICON = 7 - LOCATION_ICON = 8 - AVATAR_IMG = 9 # avatar_sha - PADLOCK_PIXBUF = 10 # use for account row only - VISIBLE = 11 - - -class RosterWindow: - """ - Class for main window of the GTK interface - """ - - def _get_account_iter(self, name, model=None): - """ - Return the Gtk.TreeIter of the given account or None if not found - - Keyword arguments: - name -- the account name - model -- the data model (default TreeFilterModel) - """ - if model is None: - model = self.modelfilter - if model is None: - return - - if self.regroup: - name = 'MERGED' - if name not in self._iters: - return None - it = self._iters[name]['account'] - - if model == self.model or it is None: - return it - try: - (ok, it) = self.modelfilter.convert_child_iter_to_iter(it) - if ok: - return it - return None - except RuntimeError: - return None - - - def _get_group_iter(self, name, account, model=None): - """ - Return the Gtk.TreeIter of the given group or None if not found - - Keyword arguments: - name -- the group name - account -- the account name - model -- the data model (default TreeFilterModel) - """ - if model is None: - model = self.modelfilter - if model is None: - return - - if self.regroup: - account = 'MERGED' - - if account not in self._iters: - return None - if name not in self._iters[account]['groups']: - return None - - it = self._iters[account]['groups'][name] - if model == self.model or it is None: - return it - try: - (ok, it) = self.modelfilter.convert_child_iter_to_iter(it) - if ok: - return it - return None - except RuntimeError: - return None - - - def _get_self_contact_iter(self, account, model=None): - """ - Return the Gtk.TreeIter of SelfContact or None if not found - - Keyword arguments: - account -- the account of SelfContact - model -- the data model (default TreeFilterModel) - """ - jid = app.get_jid_from_account(account) - its = self._get_contact_iter(jid, account, model=model) - if its: - return its[0] - return None - - - def _get_contact_iter(self, jid, account, contact=None, model=None): - """ - Return a list of Gtk.TreeIter of the given contact - - Keyword arguments: - jid -- the jid without resource - account -- the account - contact -- the contact (default None) - model -- the data model (default TreeFilterModel) - """ - if model is None: - model = self.modelfilter - # when closing Gajim model can be none (async pbs?) - if model is None: - return [] - - if not contact: - contact = app.contacts.get_first_contact_from_jid(account, jid) - if not contact: - # We don't know this contact - return [] - - if account not in self._iters: - return [] - - if jid not in self._iters[account]['contacts']: - return [] - - its = self._iters[account]['contacts'][jid] - - if not its: - return [] - - if model == self.model: - return its - - its2 = [] - for it in its: - try: - (ok, it) = self.modelfilter.convert_child_iter_to_iter(it) - if ok: - its2.append(it) - except RuntimeError: - pass - return its2 - - @staticmethod - def _iter_is_separator(model, titer): - """ - Return True if the given iter is a separator - - Keyword arguments: - model -- the data model - iter -- the Gtk.TreeIter to test - """ - if model[titer][0] == 'SEPARATOR': - return True - return False - -############################################################################# -### Methods for adding and removing roster window items -############################################################################# - - def add_account(self, account): - """ - Add account to roster and draw it. Do nothing if it is already in - """ - if self._get_account_iter(account): - # Will happen on reconnect or for merged accounts - return - - if self.regroup: - # Merged accounts view - show = helpers.get_global_show() - it = self.model.append(None, [get_icon_name(show), - _('Merged accounts'), 'account', '', 'all', None, None, None, - None, None, None, True] + [None] * self.nb_ext_renderers) - self._iters['MERGED']['account'] = it - else: - show = helpers.get_connection_status(account) - our_jid = app.get_jid_from_account(account) - - it = self.model.append(None, [get_icon_name(show), - GLib.markup_escape_text(account), 'account', our_jid, - account, None, None, None, None, None, None, True] + - [None] * self.nb_ext_renderers) - self._iters[account]['account'] = it - - self.draw_account(account) - - - def add_account_contacts(self, account, improve_speed=True, - draw_contacts=True): - """ - Add all contacts and groups of the given account to roster, draw them - and account - """ - if improve_speed: - self._before_fill() - jids = app.contacts.get_jid_list(account) - - for jid in jids: - self.add_contact(jid, account) - - if draw_contacts: - # Do not freeze the GUI when drawing the contacts - if jids: - # Overhead is big, only invoke when needed - self._idle_draw_jids_of_account(jids, account) - - # Draw all known groups - for group in app.groups[account]: - self.draw_group(group, account) - self.draw_account(account) - - if improve_speed: - self._after_fill() - - def _add_group_iter(self, account, group): - """ - Add a group iter in roster and return the newly created iter - """ - if self.regroup: - account_group = 'MERGED' - else: - account_group = account - delimiter = app.connections[account].get_module('Delimiter').delimiter - group_splited = group.split(delimiter) - parent_group = delimiter.join(group_splited[:-1]) - if len(group_splited) > 1 and parent_group in self._iters[account_group]['groups']: - iter_parent = self._iters[account_group]['groups'][parent_group] - elif parent_group: - iter_parent = self._add_group_iter(account, parent_group) - if parent_group not in app.groups[account]: - if account + parent_group in self.collapsed_rows: - is_expanded = False - else: - is_expanded = True - app.groups[account][parent_group] = {'expand': is_expanded} - else: - iter_parent = self._get_account_iter(account, self.model) - iter_group = self.model.append(iter_parent, - [get_icon_name('closed'), - GLib.markup_escape_text(group), 'group', group, account, None, - None, None, None, None, None, False] + [None] * self.nb_ext_renderers) - self.draw_group(group, account) - self._iters[account_group]['groups'][group] = iter_group - return iter_group - - def _add_entity(self, contact, account, groups=None, - big_brother_contact=None, big_brother_account=None): - """ - Add the given contact to roster data model - - Contact is added regardless if he is already in roster or not. Return - list of newly added iters. - - Keyword arguments: - contact -- the contact to add - account -- the contacts account - groups -- list of groups to add the contact to. - (default groups in contact.get_shown_groups()). - Parameter ignored when big_brother_contact is specified. - big_brother_contact -- if specified contact is added as child - big_brother_contact. (default None) - """ - added_iters = [] - visible = self.contact_is_visible(contact, account) - if big_brother_contact: - # Add contact under big brother - - parent_iters = self._get_contact_iter( - big_brother_contact.jid, big_brother_account, - big_brother_contact, self.model) - - # Do not confuse get_contact_iter: Sync groups of family members - contact.groups = big_brother_contact.groups[:] - - image = self._get_avatar_image(account, contact.jid) - - for child_iter in parent_iters: - it = self.model.append(child_iter, [None, - contact.get_shown_name(), 'contact', contact.jid, account, - None, None, None, None, image, None, visible] + \ - [None] * self.nb_ext_renderers) - added_iters.append(it) - if contact.jid in self._iters[account]['contacts']: - self._iters[account]['contacts'][contact.jid].append(it) - else: - self._iters[account]['contacts'][contact.jid] = [it] - else: - # We are a normal contact. Add us to our groups. - if not groups: - groups = contact.get_shown_groups() - for group in groups: - child_iterG = self._get_group_iter(group, account, - model=self.model) - if not child_iterG: - # Group is not yet in roster, add it! - child_iterG = self._add_group_iter(account, group) - - if contact.is_transport(): - typestr = 'agent' - elif contact.is_groupchat: - typestr = 'groupchat' - else: - typestr = 'contact' - - image = self._get_avatar_image(account, contact.jid) - - # we add some values here. see draw_contact - # for more - i_ = self.model.append(child_iterG, [None, - contact.get_shown_name(), typestr, contact.jid, account, - None, None, None, None, image, None, visible] + \ - [None] * self.nb_ext_renderers) - added_iters.append(i_) - if contact.jid in self._iters[account]['contacts']: - self._iters[account]['contacts'][contact.jid].append(i_) - else: - self._iters[account]['contacts'][contact.jid] = [i_] - - # Restore the group expand state - if account + group in self.collapsed_rows: - is_expanded = False - else: - is_expanded = True - if group not in app.groups[account]: - app.groups[account][group] = {'expand': is_expanded} - - return added_iters - - def _remove_entity(self, contact, account, groups=None): - """ - Remove the given contact from roster data model - - Empty groups after contact removal are removed too. - Return False if contact still has children and deletion was - not performed. - Return True on success. - - Keyword arguments: - contact -- the contact to add - account -- the contacts account - groups -- list of groups to remove the contact from. - """ - iters = self._get_contact_iter(contact.jid, account, contact, - self.model) - - parent_iter = self.model.iter_parent(iters[0]) - parent_type = self.model[parent_iter][Column.TYPE] - - if groups: - # Only remove from specified groups - all_iters = iters[:] - group_iters = [self._get_group_iter(group, account) - for group in groups] - iters = [titer for titer in all_iters - if self.model.iter_parent(titer) in group_iters] - - iter_children = self.model.iter_children(iters[0]) - - if iter_children: - # We have children. We cannot be removed! - return False - # Remove us and empty groups from the model - for i in iters: - parent_i = self.model.iter_parent(i) - parent_type = self.model[parent_i][Column.TYPE] - - to_be_removed = i - while parent_type == 'group' and \ - self.model.iter_n_children(parent_i) == 1: - if self.regroup: - account_group = 'MERGED' - else: - account_group = account - group = self.model[parent_i][Column.JID] - if group in app.groups[account]: - del app.groups[account][group] - to_be_removed = parent_i - del self._iters[account_group]['groups'][group] - parent_i = self.model.iter_parent(parent_i) - parent_type = self.model[parent_i][Column.TYPE] - self.model.remove(to_be_removed) - - del self._iters[account]['contacts'][contact.jid] - return True - - def _add_metacontact_family(self, family, account): - """ - Add the give Metacontact family to roster data model - - Add Big Brother to his groups and all others under him. - Return list of all added (contact, account) tuples with - Big Brother as first element. - - Keyword arguments: - family -- the family, see Contacts.get_metacontacts_family() - """ - - nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) - if not big_brother_jid: - return [] - big_brother_contact = app.contacts.get_first_contact_from_jid( - big_brother_account, big_brother_jid) - - self._add_entity(big_brother_contact, big_brother_account) - - brothers = [] - # Filter family members - for data in nearby_family: - _account = data['account'] - _jid = data['jid'] - _contact = app.contacts.get_first_contact_from_jid( - _account, _jid) - - if not _contact or _contact == big_brother_contact: - # Corresponding account is not connected - # or brother already added - continue - - self._add_entity(_contact, _account, - big_brother_contact=big_brother_contact, - big_brother_account=big_brother_account) - brothers.append((_contact, _account)) - - brothers.insert(0, (big_brother_contact, big_brother_account)) - return brothers - - def _remove_metacontact_family(self, family, account): - """ - Remove the given Metacontact family from roster data model - - See Contacts.get_metacontacts_family() and - RosterWindow._remove_entity() - """ - nearby_family = self._get_nearby_family_and_big_brother( - family, account)[0] - - # Family might has changed (actual big brother not on top). - # Remove children first then big brother - family_in_roster = False - for data in nearby_family: - _account = data['account'] - _jid = data['jid'] - _contact = app.contacts.get_first_contact_from_jid(_account, _jid) - - iters = self._get_contact_iter(_jid, _account, _contact, self.model) - if not iters or not _contact: - # Family might not be up to date. - # Only try to remove what is actually in the roster - continue - - family_in_roster = True - - parent_iter = self.model.iter_parent(iters[0]) - parent_type = self.model[parent_iter][Column.TYPE] - - if parent_type != 'contact': - # The contact on top - old_big_account = _account - old_big_contact = _contact - continue - - self._remove_entity(_contact, _account) - - if not family_in_roster: - return False - - self._remove_entity(old_big_contact, old_big_account) - - return True - - def _recalibrate_metacontact_family(self, family, account): - """ - Regroup metacontact family if necessary - """ - - brothers = [] - nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) - big_brother_contact = app.contacts.get_contact(big_brother_account, - big_brother_jid) - child_iters = self._get_contact_iter(big_brother_jid, - big_brother_account, model=self.model) - if child_iters: - parent_iter = self.model.iter_parent(child_iters[0]) - parent_type = self.model[parent_iter][Column.TYPE] - - # Check if the current BigBrother has even been before. - if parent_type == 'contact': - for data in nearby_family: - # recalibrate after remove to keep highlight - if data['jid'] in app.to_be_removed[data['account']]: - return - - self._remove_metacontact_family(family, account) - brothers = self._add_metacontact_family(family, account) - - for c, acc in brothers: - self.draw_completely(c.jid, acc) - - # Check is small brothers are under the big brother - for child in nearby_family: - _jid = child['jid'] - _account = child['account'] - if _account == big_brother_account and _jid == big_brother_jid: - continue - child_iters = self._get_contact_iter(_jid, _account, - model=self.model) - if not child_iters: - continue - parent_iter = self.model.iter_parent(child_iters[0]) - parent_type = self.model[parent_iter][Column.TYPE] - if parent_type != 'contact': - _contact = app.contacts.get_contact(_account, _jid) - self._remove_entity(_contact, _account) - self._add_entity(_contact, _account, groups=None, - big_brother_contact=big_brother_contact, - big_brother_account=big_brother_account) - - def _get_nearby_family_and_big_brother(self, family, account): - return app.contacts.get_nearby_family_and_big_brother(family, account) - - def _add_self_contact(self, account): - """ - Add account's SelfContact to roster and draw it and the account - - Return the SelfContact contact instance - """ - jid = app.get_jid_from_account(account) - contact = app.contacts.get_first_contact_from_jid(account, jid) - - child_iterA = self._get_account_iter(account, self.model) - self._iters[account]['contacts'][jid] = [self.model.append(child_iterA, - [None, app.nicks[account], 'self_contact', jid, account, None, - None, None, None, None, None, True] + [None] * self.nb_ext_renderers)] - - self.draw_completely(jid, account) - self.draw_account(account) - - return contact - - def redraw_metacontacts(self, account): - for family in app.contacts.iter_metacontacts_families(account): - self._recalibrate_metacontact_family(family, account) - - def add_contact(self, jid, account): - """ - Add contact to roster and draw him - - Add contact to all its group and redraw the groups, the contact and the - account. If it's a Metacontact, add and draw the whole family. - Do nothing if the contact is already in roster. - - Return the added contact instance. If it is a Metacontact return - Big Brother. - - Keyword arguments: - jid -- the contact's jid or SelfJid to add SelfContact - account -- the corresponding account. - """ - contact = app.contacts.get_contact_with_highest_priority(account, jid) - if self._get_contact_iter(jid, account, contact, self.model): - # If contact already in roster, do nothing - return - - if jid == app.get_jid_from_account(account): - return self._add_self_contact(account) - - is_observer = contact.is_observer() - if is_observer: - # if he has a tag, remove it - app.contacts.remove_metacontact(account, jid) - - # Add contact to roster - family = app.contacts.get_metacontacts_family(account, jid) - contacts = [] - if family: - # We have a family. So we are a metacontact. - # Add all family members that we shall be grouped with - if self.regroup: - # remove existing family members to regroup them - self._remove_metacontact_family(family, account) - contacts = self._add_metacontact_family(family, account) - else: - # We are a normal contact - contacts = [(contact, account), ] - self._add_entity(contact, account) - - # Draw the contact and its groups contact - if not self.starting: - for c, acc in contacts: - self.draw_completely(c.jid, acc) - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self._adjust_group_expand_collapse_state(group, account) - self.draw_account(account) - - return contacts[0][0] # it's contact/big brother with highest priority - - def remove_contact(self, jid, account, force=False, backend=False, maximize=False): - """ - Remove contact from roster - - Remove contact from all its group. Remove empty groups or redraw - otherwise. - Draw the account. - If it's a Metacontact, remove the whole family. - Do nothing if the contact is not in roster. - - Keyword arguments: - jid -- the contact's jid or SelfJid to remove SelfContact - account -- the corresponding account. - force -- remove contact even it has pending evens (Default False) - backend -- also remove contact instance (Default False) - """ - contact = app.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - return - - if not force and self.contact_has_pending_roster_events(contact, - account): - return False - - iters = self._get_contact_iter(jid, account, contact, self.model) - if iters: - # no more pending events - # Remove contact from roster directly - family = app.contacts.get_metacontacts_family(account, jid) - if family: - # We have a family. So we are a metacontact. - self._remove_metacontact_family(family, account) - else: - self._remove_entity(contact, account) - - old_grps = [] - if backend: - if not app.window.get_control(account, jid) or \ - force: - # If a window is still opened: don't remove contact instance - # Remove contact before redrawing, otherwise the old - # numbers will still be show - if not maximize: - # Don't remove contact when we maximize a room - app.contacts.remove_jid(account, jid, remove_meta=True) - if iters: - rest_of_family = [data for data in family - if account != data['account'] or jid != data['jid']] - if rest_of_family: - # reshow the rest of the family - brothers = self._add_metacontact_family(rest_of_family, - account) - for c, acc in brothers: - self.draw_completely(c.jid, acc) - else: - for c in app.contacts.get_contacts(account, jid): - c.sub = 'none' - c.show = 'not in roster' - c.status = '' - old_grps = c.get_shown_groups() - c.groups = [_('Not in contact list')] - self._add_entity(c, account) - self.draw_contact(jid, account) - - if iters: - # Draw all groups of the contact - for group in contact.get_shown_groups() + old_grps: - self.draw_group(group, account) - self.draw_account(account) - - return True - - def rename_self_contact(self, old_jid, new_jid, account): - """ - Rename the self_contact jid - - Keyword arguments: - old_jid -- our old jid - new_jid -- our new jid - account -- the corresponding account. - """ - app.contacts.change_contact_jid(old_jid, new_jid, account) - self_iter = self._get_self_contact_iter(account, model=self.model) - if not self_iter: - return - self.model[self_iter][Column.JID] = new_jid - self.draw_contact(new_jid, account) - - def minimize_groupchat(self, account, jid, status=''): - gc_control = app.interface.msg_win_mgr.get_gc_control(jid, account) - app.interface.minimized_controls[account][jid] = gc_control - self.add_groupchat(jid, account) - - def add_groupchat(self, jid, account): - """ - Add groupchat to roster and draw it. Return the added contact instance - """ - contact = app.contacts.get_groupchat_contact(account, jid) - show = 'offline' - if app.account_is_available(account): - show = 'online' - - contact.show = show - self.add_contact(jid, account) - - return contact - - def remove_groupchat(self, jid, account, maximize=False): - """ - Remove groupchat from roster and redraw account and group - """ - contact = app.contacts.get_contact_with_highest_priority(account, jid) - if contact.is_groupchat: - if jid in app.interface.minimized_controls[account]: - del app.interface.minimized_controls[account][jid] - self.remove_contact(jid, account, force=True, backend=True, maximize=maximize) - return True - return False - - # FIXME: This function is yet unused! Port to new API - def add_transport(self, jid, account): - """ - Add transport to roster and draw it. Return the added contact instance - """ - contact = app.contacts.get_contact_with_highest_priority(account, jid) - if contact is None: - contact = app.contacts.create_contact(jid=jid, account=account, - name=jid, groups=[_('Transports')], show='offline', - status='offline', sub='from') - app.contacts.add_contact(account, contact) - self.add_contact(jid, account) - return contact - - def remove_transport(self, jid, account): - """ - Remove transport from roster and redraw account and group - """ - self.remove_contact(jid, account, force=True, backend=True) - return True - - def rename_group(self, old_name, new_name, account): - """ - Rename a roster group - """ - if old_name == new_name: - return - - # Groups may not change name from or to a special groups - for g in helpers.special_groups: - if g in (new_name, old_name): - return - - # update all contacts in the given group - if self.regroup: - accounts = app.connections.keys() - else: - accounts = [account, ] - - for acc in accounts: - changed_contacts = [] - for jid in app.contacts.get_jid_list(acc): - contact = app.contacts.get_first_contact_from_jid(acc, jid) - if old_name not in contact.groups: - continue - - self.remove_contact(jid, acc, force=True) - - contact.groups.remove(old_name) - if new_name not in contact.groups: - contact.groups.append(new_name) - - changed_contacts.append({'jid': jid, 'name': contact.name, - 'groups':contact.groups}) - - app.connections[acc].get_module('Roster').update_contacts( - changed_contacts) - - for c in changed_contacts: - self.add_contact(c['jid'], acc) - - self._adjust_group_expand_collapse_state(new_name, acc) - - self.draw_group(old_name, acc) - self.draw_group(new_name, acc) - - - def add_contact_to_groups(self, jid, account, groups, update=True): - """ - Add contact to given groups and redraw them - - Contact on server is updated too. When the contact has a family, - the action will be performed for all members. - - Keyword Arguments: - jid -- the jid - account -- the corresponding account - groups -- list of Groups to add the contact to. - update -- update contact on the server - """ - self.remove_contact(jid, account, force=True) - for contact in app.contacts.get_contacts(account, jid): - for group in groups: - if group not in contact.groups: - # we might be dropped from meta to group - contact.groups.append(group) - if update: - con = app.connections[account] - con.get_module('Roster').update_contact( - jid, contact.name, contact.groups) - - self.add_contact(jid, account) - - for group in groups: - self._adjust_group_expand_collapse_state(group, account) - - def remove_contact_from_groups(self, jid, account, groups, update=True): - """ - Remove contact from given groups and redraw them - - Contact on server is updated too. When the contact has a family, - the action will be performed for all members. - - Keyword Arguments: - jid -- the jid - account -- the corresponding account - groups -- list of Groups to remove the contact from - update -- update contact on the server - """ - self.remove_contact(jid, account, force=True) - for contact in app.contacts.get_contacts(account, jid): - for group in groups: - if group in contact.groups: - # Needed when we remove from "General" or "Observers" - contact.groups.remove(group) - if update: - con = app.connections[account] - con.get_module('Roster').update_contact( - jid, contact.name, contact.groups) - self.add_contact(jid, account) - - # Also redraw old groups - for group in groups: - self.draw_group(group, account) - - # FIXME: maybe move to app.py - def remove_newly_added(self, jid, account): - if account not in app.newly_added: - # Account has been deleted during the timeout that called us - return - if jid in app.newly_added[account]: - app.newly_added[account].remove(jid) - self.draw_contact(jid, account) - - # FIXME: maybe move to app.py - def remove_to_be_removed(self, jid, account): - if account not in app.interface.instances: - # Account has been deleted during the timeout that called us - return - if jid in app.newly_added[account]: - return - if jid in app.to_be_removed[account]: - app.to_be_removed[account].remove(jid) - family = app.contacts.get_metacontacts_family(account, jid) - if family: - # Perform delayed recalibration - self._recalibrate_metacontact_family(family, account) - self.draw_contact(jid, account) - # Hide Group if all children are hidden - contact = app.contacts.get_contact(account, jid) - if not contact: - return - for group in contact.get_shown_groups(): - self.draw_group(group, account) - - # FIXME: integrate into add_contact() - def add_to_not_in_the_roster(self, account, jid, nick='', resource='', - groupchat=False): - contact = app.contacts.create_not_in_roster_contact( - jid=jid, account=account, resource=resource, name=nick, - groupchat=groupchat) - app.contacts.add_contact(account, contact) - self.add_contact(contact.jid, account) - return contact - - -################################################################################ -### Methods for adding and removing roster window items -################################################################################ - - def _really_draw_account(self, account): - child_iter = self._get_account_iter(account, self.model) - if not child_iter: - return - - if self.regroup: - account_name = _('Merged accounts') - accounts = [] - else: - account_name = app.get_account_label(account) - accounts = [account] - - if account in self.collapsed_rows and \ - self.model.iter_has_child(child_iter): - account_name = '[%s]' % account_name - - if (app.account_is_available(account) or (self.regroup and \ - app.get_number_of_connected_accounts())) and app.settings.get( - 'show_contacts_number'): - nbr_on, nbr_total = app.contacts.get_nb_online_total_contacts( - accounts=accounts) - account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - - self.model[child_iter][Column.NAME] = GLib.markup_escape_text(account_name) - - mood_icon_name = get_account_mood_icon_name(account) - self.model[child_iter][Column.MOOD_PIXBUF] = mood_icon_name - - activity_icon_name = get_account_activity_icon_name(account) - self.model[child_iter][Column.ACTIVITY_PIXBUF] = activity_icon_name - - tune_icon_name = get_account_tune_icon_name(account) - self.model[child_iter][Column.TUNE_ICON] = tune_icon_name - - location_icon_name = get_account_location_icon_name(account) - self.model[child_iter][Column.LOCATION_ICON] = location_icon_name - - def _really_draw_accounts(self): - for acct in self.accounts_to_draw: - self._really_draw_account(acct) - self.accounts_to_draw = [] - return False - - def draw_account(self, account): - if account in self.accounts_to_draw: - return - self.accounts_to_draw.append(account) - if len(self.accounts_to_draw) == 1: - GLib.timeout_add(200, self._really_draw_accounts) - - def _really_draw_group(self, group, account): - child_iter = self._get_group_iter(group, account, model=self.model) - if not child_iter: - # Eg. We redraw groups after we removed a entity - # and its empty groups - return - if self.regroup: - accounts = [] - else: - accounts = [account] - text = GLib.markup_escape_text(group) - if app.settings.get('show_contacts_number'): - nbr_on, nbr_total = app.contacts.get_nb_online_total_contacts( - accounts=accounts, groups=[group]) - text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - - self.model[child_iter][Column.NAME] = text - - # Hide group if no more contacts - iterG = self._get_group_iter(group, account, model=self.modelfilter) - to_hide = [] - while iterG: - parent = self.modelfilter.iter_parent(iterG) - if (not self.modelfilter.iter_has_child(iterG)) or (to_hide \ - and self.modelfilter.iter_n_children(iterG) == 1): - to_hide.append(iterG) - if not parent or self.modelfilter[parent][Column.TYPE] != \ - 'group': - iterG = None - else: - iterG = parent - else: - iterG = None - for iter_ in to_hide: - self.modelfilter[iter_][Column.VISIBLE] = False - - def _really_draw_groups(self): - for ag in self.groups_to_draw.values(): - acct = ag['account'] - grp = ag['group'] - self._really_draw_group(grp, acct) - self.groups_to_draw = {} - return False - - def draw_group(self, group, account): - ag = account + group - if ag in self.groups_to_draw: - return - self.groups_to_draw[ag] = {'group': group, 'account': account} - if len(self.groups_to_draw) == 1: - GLib.timeout_add(200, self._really_draw_groups) - - def draw_parent_contact(self, jid, account): - child_iters = self._get_contact_iter(jid, account, model=self.model) - if not child_iters: - return False - parent_iter = self.model.iter_parent(child_iters[0]) - if self.model[parent_iter][Column.TYPE] != 'contact': - # parent is not a contact - return - parent_jid = self.model[parent_iter][Column.JID] - parent_account = self.model[parent_iter][Column.ACCOUNT] - self.draw_contact(parent_jid, parent_account) - return False - - def draw_contact(self, jid, account, selected=False, focus=False, - contact_instances=None, contact=None): - """ - Draw the correct state image, name BUT not avatar - """ - # focus is about if the roster window has toplevel-focus or not - # FIXME: We really need a custom cell_renderer - - if not contact_instances: - contact_instances = app.contacts.get_contacts(account, jid) - if not contact: - contact = app.contacts.get_highest_prio_contact_from_contacts( - contact_instances) - if not contact: - return False - - child_iters = self._get_contact_iter(jid, account, contact, self.model) - if not child_iters: - return False - - name = GLib.markup_escape_text(contact.get_shown_name()) - - # gets number of unread gc marked messages - if jid in app.interface.minimized_controls[account] and \ - app.interface.minimized_controls[account][jid]: - nb_unread = len(app.events.get_events(account, jid, - ['printed_marked_gc_msg'])) - nb_unread += app.interface.minimized_controls \ - [account][jid].get_nb_unread_pm() - - if nb_unread == 1: - name = '%s *' % name - elif nb_unread > 1: - name = '%s [%s]' % (name, str(nb_unread)) - - # Strike name if blocked - strike = helpers.jid_is_blocked(account, jid) - if strike: - name = '<span strikethrough="true">%s</span>' % name - - # Show resource counter - nb_connected_contact = 0 - for c in contact_instances: - if c.show not in ('error', 'offline'): - nb_connected_contact += 1 - if nb_connected_contact > 1: - # switch back to default writing direction - name += i18n.paragraph_direction_mark(name) - name += ' (%d)' % nb_connected_contact - - # add status msg, if not empty, under contact name in - # the treeview - if app.settings.get('show_status_msgs_in_roster'): - status_span = '\n<span size="small" style="italic" ' \ - 'alpha="70%">{}</span>' - if contact.is_groupchat: - disco_info = app.storage.cache.get_last_disco_info(contact.jid) - if disco_info is not None: - description = disco_info.muc_description - if description: - name += status_span.format( - GLib.markup_escape_text(description)) - elif contact.status: - status = contact.status.strip() - if status != '': - status = helpers.reduce_chars_newlines( - status, max_lines=1) - name += status_span.format( - GLib.markup_escape_text(status)) - - icon_name = helpers.get_icon_name_to_show(contact, account) - # look if another resource has awaiting events - for c in contact_instances: - c_icon_name = helpers.get_icon_name_to_show(c, account) - if c_icon_name in ('event', 'muc-active', 'muc-inactive'): - icon_name = c_icon_name - break - - # Check for events of collapsed (hidden) brothers - family = app.contacts.get_metacontacts_family(account, jid) - is_big_brother = False - have_visible_children = False - if family: - bb_jid, bb_account = \ - self._get_nearby_family_and_big_brother(family, account)[1:] - is_big_brother = (jid, account) == (bb_jid, bb_account) - iters = self._get_contact_iter(jid, account) - have_visible_children = iters and \ - self.modelfilter.iter_has_child(iters[0]) - - if have_visible_children: - # We are the big brother and have a visible family - for child_iter in child_iters: - child_path = self.model.get_path(child_iter) - path = self.modelfilter.convert_child_path_to_path(child_path) - - if not path: - continue - - if not self.tree.row_expanded(path) and icon_name != 'event': - iterC = self.model.iter_children(child_iter) - while iterC: - # a child has awaiting messages? - jidC = self.model[iterC][Column.JID] - accountC = self.model[iterC][Column.ACCOUNT] - if app.events.get_events(accountC, jidC): - icon_name = 'event' - break - iterC = self.model.iter_next(iterC) - - if self.tree.row_expanded(path): - icon_name += ':opened' - else: - icon_name += ':closed' - - theme_icon = get_icon_name(icon_name) - self.model[child_iter][Column.IMG] = theme_icon - self.model[child_iter][Column.NAME] = name - #TODO: compute visible - visible = True - self.model[child_iter][Column.VISIBLE] = visible - else: - # A normal contact or little brother - transport = app.get_transport_name_from_jid(jid) - if transport == 'jabber': - transport = None - theme_icon = get_icon_name(icon_name, transport=transport) - - visible = self.contact_is_visible(contact, account) - # All iters have the same icon (no expand/collapse) - for child_iter in child_iters: - self.model[child_iter][Column.IMG] = theme_icon - self.model[child_iter][Column.NAME] = name - self.model[child_iter][Column.VISIBLE] = visible - if visible: - parent_iter = self.model.iter_parent(child_iter) - self.model[parent_iter][Column.VISIBLE] = True - - # We are a little brother - if family and not is_big_brother and not self.starting: - self.draw_parent_contact(jid, account) - - if visible: - delimiter = app.connections[account].get_module('Delimiter').delimiter - for group in contact.get_shown_groups(): - group_splited = group.split(delimiter) - i = 1 - while i < len(group_splited) + 1: - g = delimiter.join(group_splited[:i]) - iterG = self._get_group_iter(g, account, model=self.model) - if iterG: - # it's not self contact - self.model[iterG][Column.VISIBLE] = True - i += 1 - - app.plugin_manager.gui_extension_point('roster_draw_contact', self, - jid, account, contact) - - return False - - def _is_pep_shown_in_roster(self, pep_type): - if pep_type == PEPEventType.MOOD: - return app.settings.get('show_mood_in_roster') - - if pep_type == PEPEventType.ACTIVITY: - return app.settings.get('show_activity_in_roster') - - if pep_type == PEPEventType.TUNE: - return app.settings.get('show_tunes_in_roster') - - if pep_type == PEPEventType.LOCATION: - return app.settings.get('show_location_in_roster') - - return False - - def draw_all_pep_types(self, jid, account, contact=None): - self._draw_pep(account, jid, PEPEventType.MOOD) - self._draw_pep(account, jid, PEPEventType.ACTIVITY) - self._draw_pep(account, jid, PEPEventType.TUNE) - self._draw_pep(account, jid, PEPEventType.LOCATION) - - def _draw_pep(self, account, jid, type_): - if not self._is_pep_shown_in_roster(type_): - return - - iters = self._get_contact_iter(jid, account, model=self.model) - if not iters: - return - contact = app.contacts.get_contact(account, jid) - - icon = None - data = contact.pep.get(type_) - - if type_ == PEPEventType.MOOD: - column = Column.MOOD_PIXBUF - if data is not None: - icon = 'mood-%s' % data.mood - elif type_ == PEPEventType.ACTIVITY: - column = Column.ACTIVITY_PIXBUF - if data is not None: - icon = get_activity_icon_name(data.activity, data.subactivity) - elif type_ == PEPEventType.TUNE: - column = Column.TUNE_ICON - if data is not None: - icon = 'audio-x-generic' - elif type_ == PEPEventType.LOCATION: - column = Column.LOCATION_ICON - if data is not None: - icon = 'applications-internet' - - for child_iter in iters: - self.model[child_iter][column] = icon - - def _get_avatar_image(self, account, jid): - if not app.settings.get('show_avatars_in_roster'): - return None - scale = self.window.get_scale_factor() - surface = app.contacts.get_avatar( - account, jid, AvatarSize.ROSTER, scale) - return Gtk.Image.new_from_surface(surface) - - def draw_avatar(self, jid, account): - iters = self._get_contact_iter(jid, account, model=self.model) - if not iters or not app.settings.get('show_avatars_in_roster'): - return - jid = self.model[iters[0]][Column.JID] - image = self._get_avatar_image(account, jid) - - for child_iter in iters: - self.model[child_iter][Column.AVATAR_IMG] = image - return False - - def draw_completely(self, jid, account): - contact_instances = app.contacts.get_contacts(account, jid) - contact = app.contacts.get_highest_prio_contact_from_contacts( - contact_instances) - self.draw_contact( - jid, account, - contact_instances=contact_instances, - contact=contact) - - def adjust_and_draw_contact_context(self, jid, account): - """ - Draw contact, account and groups of given jid Show contact if it has - pending events - """ - contact = app.contacts.get_first_contact_from_jid(account, jid) - if not contact: - # idle draw or just removed SelfContact - return - - family = app.contacts.get_metacontacts_family(account, jid) - if family: - # There might be a new big brother - self._recalibrate_metacontact_family(family, account) - self.draw_contact(jid, account) - self.draw_account(account) - - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self._adjust_group_expand_collapse_state(group, account) - - def _idle_draw_jids_of_account(self, jids, account): - """ - Draw given contacts and their avatars in a lazy fashion - - Keyword arguments: - jids -- a list of jids to draw - account -- the corresponding account - """ - def _draw_all_contacts(jids, account): - for jid in jids: - family = app.contacts.get_metacontacts_family(account, jid) - if family: - # For metacontacts over several accounts: - # When we connect a new account existing brothers - # must be redrawn (got removed and added again) - for data in family: - self.draw_completely(data['jid'], data['account']) - else: - self.draw_completely(jid, account) - yield True - self.refilter_shown_roster_items() - yield False - - task = _draw_all_contacts(jids, account) - GLib.idle_add(next, task) - - def _before_fill(self): - self.tree.freeze_child_notify() - self.tree.set_model(None) - # disable sorting - self.model.set_sort_column_id(-2, Gtk.SortType.ASCENDING) - self.starting = True - self.starting_filtering = True - - def _after_fill(self): - self.starting = False - accounts_list = app.settings.get_active_accounts() - for account in app.connections: - if account not in accounts_list: - continue - - jids = app.contacts.get_jid_list(account) - for jid in jids: - self.draw_completely(jid, account) - - # Draw all known groups - for group in app.groups[account]: - self.draw_group(group, account) - self.draw_account(account) - - self.model.set_sort_column_id(1, Gtk.SortType.ASCENDING) - self.tree.set_model(self.modelfilter) - self.tree.thaw_child_notify() - self.starting_filtering = False - self.refilter_shown_roster_items() - - def setup_and_draw_roster(self): - """ - Create new empty model and draw roster - """ - self.modelfilter = None - self.model = Gtk.TreeStore(*self.columns) - - self.model.set_sort_func(1, self._compareIters) - self.model.set_sort_column_id(1, Gtk.SortType.ASCENDING) - self.modelfilter = self.model.filter_new() - self.modelfilter.set_visible_func(self._visible_func) - self.modelfilter.connect('row-has-child-toggled', - self.on_modelfilter_row_has_child_toggled) - self.tree.set_model(self.modelfilter) - - self._iters = {} - # for merged mode - self._iters['MERGED'] = {'account': None, 'groups': {}} - for acct in app.settings.get_active_accounts(): - self._iters[acct] = {'account': None, 'groups': {}, 'contacts': {}} - - for acct in app.settings.get_active_accounts(): - self.add_account(acct) - self.add_account_contacts(acct, improve_speed=True, - draw_contacts=False) - - # Recalculate column width for ellipsizing - self.tree.columns_autosize() - - def update_status_selector(self): - self._status_selector.update() - - def select_contact(self, jid, account): - """ - Select contact in roster. If contact is hidden but has events, show him - """ - # Refiltering SHOULD NOT be needed: - # When a contact gets a new event he will be redrawn and his - # icon changes, so _visible_func WILL be called on him anyway - iters = self._get_contact_iter(jid, account) - if not iters: - # Not visible in roster - return - path = self.modelfilter.get_path(iters[0]) - if self.dragging or not app.settings.get( - 'scroll_roster_to_last_message'): - # do not change selection while DND'ing - return - # Expand his parent, so this path is visible, don't expand it. - path.up() - self.tree.expand_to_path(path) - self.tree.scroll_to_cell(path) - self.tree.set_cursor(path) - - def _readjust_expand_collapse_state(self): - def func(model, path, iter_, param): - type_ = model[iter_][Column.TYPE] - acct = model[iter_][Column.ACCOUNT] - jid = model[iter_][Column.JID] - key = None - if type_ == 'account': - key = acct - elif type_ == 'group': - key = acct + jid - elif type_ == 'contact': - parent_iter = model.iter_parent(iter_) - ptype = model[parent_iter][Column.TYPE] - if ptype == 'group': - grp = model[parent_iter][Column.JID] - key = acct + grp + jid - if key: - if key in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - self.modelfilter.foreach(func, None) - - def _adjust_account_expand_collapse_state(self, account): - """ - Expand/collapse account row based on self.collapsed_rows - """ - if not self.tree.get_model(): - return - iterA = self._get_account_iter(account) - if not iterA: - # thank you modelfilter - return - path = self.modelfilter.get_path(iterA) - if account in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return False - - - def _adjust_group_expand_collapse_state(self, group, account): - """ - Expand/collapse group row based on self.collapsed_rows - """ - if not self.tree.get_model(): - return - if account not in app.connections: - return - delimiter = app.connections[account].get_module('Delimiter').delimiter - group_splited = group.split(delimiter) - i = 1 - while i < len(group_splited) + 1: - g = delimiter.join(group_splited[:i]) - iterG = self._get_group_iter(g, account) - if not iterG: - # Group not visible - return - path = self.modelfilter.get_path(iterG) - if account + g in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - i += 1 - -############################################################################## -### Roster and Modelfilter handling -############################################################################## - - def refilter_shown_roster_items(self): - if self.filtering: - return - self.filtering = True - for account in app.connections: - for jid in app.contacts.get_jid_list(account): - self.adjust_and_draw_contact_context(jid, account) - self.filtering = False - - def contact_has_pending_roster_events(self, contact, account): - """ - Return True if the contact or one if it resources has pending events - """ - # jid has pending events - if app.events.get_nb_roster_events(account, contact.jid) > 0: - return True - # check events of all resources - for contact_ in app.contacts.get_contacts(account, contact.jid): - if contact_.resource and app.events.get_nb_roster_events(account, - contact_.get_full_jid()) > 0: - return True - return False - - def contact_is_visible(self, contact, account): - if self.rfilter_enabled: - return self.rfilter_string in contact.get_shown_name().lower() - if self.contact_has_pending_roster_events(contact, account): - return True - if app.settings.get('showoffline'): - return True - - if contact.show in ('offline', 'error'): - if contact.jid in app.to_be_removed[account]: - return True - return False - if app.settings.get('show_only_chat_and_online') and contact.show in ( - 'away', 'xa', 'busy'): - return False - if _('Transports') in contact.get_shown_groups(): - return app.settings.get('show_transports_group') - return True - - def _visible_func(self, model, titer, dummy): - """ - Determine whether iter should be visible in the treeview - """ - if self.starting_filtering: - return False - - visible = model[titer][Column.VISIBLE] - - type_ = model[titer][Column.TYPE] - if not type_: - return False - if type_ == 'account': - # Always show account - return True - - account = model[titer][Column.ACCOUNT] - if not account: - return False - - jid = model[titer][Column.JID] - if not jid: - return False - - if not self.rfilter_enabled: - return visible - - if type_ == 'group': - group = jid - if group == _('Transports'): - if self.regroup: - accounts = app.settings.get_active_accounts() - else: - accounts = [account] - for _acc in accounts: - for contact in app.contacts.iter_contacts(_acc): - if group in contact.get_shown_groups(): - if self.rfilter_string in \ - contact.get_shown_name().lower(): - return True - elif self.contact_has_pending_roster_events(contact, - _acc): - return True - # No transport has been found - return False - - if type_ == 'contact': - if model.iter_has_child(titer): - iter_c = model.iter_children(titer) - while iter_c: - if self.rfilter_string in model[iter_c][Column.NAME].lower(): - return True - iter_c = model.iter_next(iter_c) - return self.rfilter_string in model[titer][Column.NAME].lower() - - if type_ == 'agent': - return self.rfilter_string in model[titer][Column.NAME].lower() - - if type_ == 'groupchat': - return self.rfilter_string in model[titer][Column.NAME].lower() - - return visible - - def _compareIters(self, model, iter1, iter2, data=None): - """ - Compare two iters to sort them - """ - name1 = model[iter1][Column.NAME] - name2 = model[iter2][Column.NAME] - if not name1 or not name2: - return 0 - type1 = model[iter1][Column.TYPE] - type2 = model[iter2][Column.TYPE] - if type1 == 'self_contact': - return -1 - if type2 == 'self_contact': - return 1 - if type1 == 'group': - name1 = model[iter1][Column.JID] - name2 = model[iter2][Column.JID] - if name1 == _('Transports'): - return 1 - if name2 == _('Transports'): - return -1 - if name1 == _('Not in contact list'): - return 1 - if name2 == _('Not in contact list'): - return -1 - if name1 == _('Group chats'): - return 1 - if name2 == _('Group chats'): - return -1 - account1 = model[iter1][Column.ACCOUNT] - account2 = model[iter2][Column.ACCOUNT] - if not account1 or not account2: - return 0 - if type1 == 'account': - return locale.strcoll(account1, account2) - jid1 = model[iter1][Column.JID] - jid2 = model[iter2][Column.JID] - if type1 == 'contact': - lcontact1 = app.contacts.get_contacts(account1, jid1) - contact1 = app.contacts.get_first_contact_from_jid(account1, jid1) - if not contact1: - return 0 - name1 = contact1.get_shown_name() - if type2 == 'contact': - lcontact2 = app.contacts.get_contacts(account2, jid2) - contact2 = app.contacts.get_first_contact_from_jid(account2, jid2) - if not contact2: - return 0 - name2 = contact2.get_shown_name() - # We first compare by show if sort_by_show_in_roster is True or if it's - # a child contact - if type1 == 'contact' and type2 == 'contact' and \ - app.settings.get('sort_by_show_in_roster'): - cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'offline': 6, 'not in roster': 7, 'error': 8} - s = self.get_show(lcontact1) - show1 = cshow.get(s, 9) - s = self.get_show(lcontact2) - show2 = cshow.get(s, 9) - removing1 = False - removing2 = False - if show1 == 6 and jid1 in app.to_be_removed[account1]: - removing1 = True - if show2 == 6 and jid2 in app.to_be_removed[account2]: - removing2 = True - if removing1 and not removing2: - return 1 - if removing2 and not removing1: - return -1 - sub1 = contact1.sub - sub2 = contact2.sub - # none and from goes after - if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']: - return -1 - if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']: - return 1 - if show1 < show2: - return -1 - if show1 > show2: - return 1 - # We compare names - cmp_result = locale.strcoll(name1.lower(), name2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - if type1 == 'contact' and type2 == 'contact': - # We compare account names - cmp_result = locale.strcoll(account1.lower(), account2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - # We compare jids - cmp_result = locale.strcoll(jid1.lower(), jid2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - return 0 - -################################################################################ -### FIXME: Methods that don't belong to roster window... -### ... at least not in there current form -################################################################################ - - def fire_up_unread_messages_events(self, account): - """ - Read from db the unread messages, and fire them up, and if we find very - old unread messages, delete them from unread table - """ - results = app.storage.archive.get_unread_msgs() - for result, shown in results: - jid = result.jid - additional_data = result.additional_data - if app.contacts.get_first_contact_from_jid(account, jid) and not \ - shown: - # We have this jid in our contacts list - # XXX unread messages should probably have their session saved - # with them - session = app.connections[account].make_new_session(jid) - - tim = float(result.time) - session.roster_message(jid, result.message, tim, msg_type='chat', - msg_log_id=result.log_line_id, additional_data=additional_data) - app.storage.archive.set_shown_unread_msgs(result.log_line_id) - - elif (time.time() - result.time) > 2592000: - # ok, here we see that we have a message in unread messages - # table that is older than a month. It is probably from someone - # not in our roster for accounts we usually launch, so we will - # delete this id from unread message tables. - app.storage.archive.set_read_messages([result.log_line_id]) - - def fill_contacts_and_groups_dicts(self, array, account): - """ - Fill app.contacts and app.groups - """ - # FIXME: This function needs to be split - # Most of the logic SHOULD NOT be done at GUI level - if account not in app.settings.get_active_accounts(): - app.contacts.add_account(account) - if not account in self._iters: - self._iters[account] = {'account': None, 'groups': {}, - 'contacts': {}} - if account not in app.groups: - app.groups[account] = {} - - self_jid = str(app.connections[account].get_own_jid()) - if account != app.ZEROCONF_ACC_NAME: - array[self_jid] = {'name': app.nicks[account], - 'groups': ['self_contact'], - 'subscription': 'both', - 'ask': 'none'} - - # .keys() is needed - for jid in list(array.keys()): - # Remove the contact in roster. It might has changed - self.remove_contact(jid, account, force=True) - # Remove old Contact instances - app.contacts.remove_jid(account, jid, remove_meta=False) - jids = jid.split('/') - # get jid - ji = jids[0] - # get resource - resource = '' - if len(jids) > 1: - resource = '/'.join(jids[1:]) - # get name - name = array[jid]['name'] or '' - show = 'offline' # show is offline by default - status = '' # no status message by default - - if app.jid_is_transport(jid): - array[jid]['groups'] = [_('Transports')] - #TRANSP - potential - contact1 = app.contacts.create_contact(jid=ji, account=account, - name=name, groups=array[jid]['groups'], show=show, - status=status, sub=array[jid]['subscription'], - ask=array[jid]['ask'], resource=resource) - app.contacts.add_contact(account, contact1) - - # If we already have chat windows opened, update them with new - # contact instance - chat_control = app.window.get_control(account, ji) - if chat_control: - chat_control.contact = contact1 - - def connected_rooms(self, account): - if account in list(app.gc_connected[account].values()): - return True - return False - - def on_event_removed(self, event_list): - """ - Remove contacts on last events removed - - Only performed if removal was requested before but the contact still had - pending events - """ - - msg_log_ids = [] - for ev in event_list: - if ev.type_ != 'printed_chat': - continue - if ev.msg_log_id: - # There is a msg_log_id - msg_log_ids.append(ev.msg_log_id) - - if msg_log_ids: - app.storage.archive.set_read_messages(msg_log_ids) - - contact_list = ((event.jid.split('/')[0], event.account) for event in \ - event_list) - - for jid, account in contact_list: - self.draw_contact(jid, account) - # Remove contacts in roster if removal was requested - key = (jid, account) - if key in list(self.contacts_to_be_removed.keys()): - backend = self.contacts_to_be_removed[key]['backend'] - del self.contacts_to_be_removed[key] - # Remove contact will delay removal if there are more events - # pending - self.remove_contact(jid, account, backend=backend) - self.show_title() - - def open_event(self, account, jid, event): - """ - If an event was handled, return True, else return False - """ - ft = app.interface.instances['file_transfers'] - event = app.events.get_first_event(account, jid, event.type_) - if event.type_ == 'normal': - # TODO: Should be displayed as normal chat message - # SingleMessageWindow(account, jid, - # action='receive', from_whom=jid, subject=event.subject, - # message=event.message, resource=event.resource) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'file-request': - contact = app.contacts.get_contact_with_highest_priority(account, - jid) - ft.show_file_request(account, contact, event.file_props) - app.events.remove_events(account, jid, event) - return True - - if event.type_ in ('file-request-error', 'file-send-error'): - ft.show_send_error(event.file_props) - app.events.remove_events(account, jid, event) - return True - - if event.type_ in ('file-error', 'file-stopped'): - msg_err = '' - if event.file_props.error == -1: - msg_err = _('Remote contact stopped transfer') - elif event.file_props.error == -6: - msg_err = _('Error opening file') - ft.show_stopped(jid, event.file_props, error_msg=msg_err) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'file-hash-error': - ft.show_hash_error(jid, event.file_props, account) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'file-completed': - ft.show_completed(jid, event.file_props) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'gc-invitation': - open_window('GroupChatInvitation', - account=account, - event=event) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'subscription_request': - # open_window('SubscriptionRequest', - # account=account, - # jid=jid, - # text=event.text, - # user_nick=event.nick) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'unsubscribed': - app.interface.show_unsubscribed_dialog(account, event.contact) - app.events.remove_events(account, jid, event) - return True - - if event.type_ == 'jingle-incoming': - ctrl = app.window.get_control(account, jid) - if ctrl: - ctrl.parent_win.set_active_tab(ctrl) - else: - ctrl = app.interface.new_chat_from_jid(account, jid) - ctrl.add_call_received_message(event) - return True - - return False - -################################################################################ -### This and that... random. -################################################################################ - - def show_roster_vbox(self, active): - vb = self.xml.get_object('roster_vbox2') - if active: - vb.set_no_show_all(False) - vb.show() - else: - vb.hide() - vb.set_no_show_all(True) - - def authorize(self, widget, jid, account): - """ - Authorize a contact (by re-sending auth menuitem) - """ - app.connections[account].get_module('Presence').subscribed(jid) - InformationDialog(_('Authorization sent'), - _('"%s" will now see your status.') %jid) - - def req_sub(self, widget, jid, txt, account, groups=None, nickname=None, - auto_auth=False): - """ - Request subscription to a contact - """ - groups_list = groups or [] - app.connections[account].get_module('Presence').subscribe( - jid, txt, nickname, groups_list, auto_auth) - contact = app.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - contact = app.contacts.create_contact(jid=jid, account=account, - name=nickname, groups=groups_list, show='requested', status='', - ask='none', sub='subscribe') - app.contacts.add_contact(account, contact) - else: - if not _('Not in contact list') in contact.get_shown_groups(): - InformationDialog(_('Subscription request has been ' - 'sent'), _('If "%s" accepts this request you will know ' - 'their status.') % jid) - return - self.remove_contact(contact.jid, account, force=True) - contact.groups = groups_list - if nickname: - contact.name = nickname - self.add_contact(jid, account) - - def revoke_auth(self, widget, jid, account): - """ - Revoke a contact's authorization - """ - app.connections[account].get_module('Presence').unsubscribed(jid) - InformationDialog(_('Authorization removed'), - _('Now "%s" will always see you as offline.') %jid) - - def set_state(self, account, state): - child_iterA = self._get_account_iter(account, self.model) - if child_iterA: - self.model[child_iterA][0] = get_icon_name(state) - if app.interface.systray_enabled: - app.interface.systray.change_status(state) - - def set_connecting_state(self, account): - self.set_state(account, 'connecting') - - def send_status(self, account, status, txt): - if status != 'offline': - app.settings.set_account_setting(account, 'last_status', status) - app.settings.set_account_setting(account, 'last_status_msg', - helpers.to_one_line(txt)) - if not app.account_is_available(account): - self.set_connecting_state(account) - - if status == 'offline': - self.delete_pep(app.get_jid_from_account(account), account) - - app.connections[account].change_status(status, txt) - self._status_selector.update() - - def delete_pep(self, jid, account): - if jid == app.get_jid_from_account(account): - app.connections[account].pep = {} - self.draw_account(account) - - for contact in app.contacts.get_contacts(account, jid): - contact.pep = {} - - self.draw_all_pep_types(jid, account) - ctrl = app.window.get_control(account, jid) - if ctrl: - ctrl.update_all_pep_types() - - def chg_contact_status(self, contact, show, status_message, account): - """ - When a contact changes their status - """ - contact_instances = app.contacts.get_contacts(account, contact.jid) - contact.show = show - contact.status = status_message - # name is to show in conversation window - name = contact.get_shown_name() - fjid = contact.get_full_jid() - - # The contact has several resources - if len(contact_instances) > 1: - if contact.resource != '': - name += '/' + contact.resource - - # Remove resource when going offline - if show in ('offline', 'error') and \ - not self.contact_has_pending_roster_events(contact, account): - ctrl = app.window.get_control(account, fjid) - if ctrl: - ctrl.update_ui() - ctrl.parent_win.redraw_tab(ctrl) - # keep the contact around, since it's - # already attached to the control - else: - app.contacts.remove_contact(account, contact) - - elif contact.jid == app.get_jid_from_account(account) and \ - show in ('offline', 'error'): - self.remove_contact(contact.jid, account, backend=True) - - uf_show = helpers.get_uf_show(show) - - # print status in chat window and update status/GPG image - ctrl = app.window.get_control(account, contact.jid) - if ctrl and not ctrl.is_groupchat: - ctrl.contact = app.contacts.get_contact_with_highest_priority( - account, contact.jid) - ctrl.update_status_display(name, uf_show, status_message) - - if contact.resource: - ctrl = app.window.get_control(account, fjid) - if ctrl: - ctrl.update_status_display(name, uf_show, status_message) - - # Delete pep if needed - keep_pep = any(c.show not in ('error', 'offline') for c in - contact_instances) - if not keep_pep and contact.jid != app.get_jid_from_account(account) \ - and not contact.is_groupchat: - self.delete_pep(contact.jid, account) - - # Redraw everything and select the sender - self.adjust_and_draw_contact_context(contact.jid, account) - - - def on_status_changed(self, account, show): - """ - The core tells us that our status has changed - """ - if account not in app.settings.get_active_accounts(): - return - child_iterA = self._get_account_iter(account, self.model) - self_resource = app.connections[account].get_own_jid().resource - self_contact = app.contacts.get_contact(account, - app.get_jid_from_account(account), resource=self_resource) - if self_contact: - status_message = app.connections[account].status_message - self.chg_contact_status(self_contact, show, status_message, account) - self.set_account_status_icon(account) - if show == 'offline': - if self.quit_on_next_offline > -1: - # we want to quit, we are waiting for all accounts to be offline - self.quit_on_next_offline -= 1 - if self.quit_on_next_offline < 1: - # all accounts offline, quit - self.quit_gtkgui_interface() - else: - # No need to redraw contacts if we're quitting - if child_iterA: - self.model[child_iterA][Column.AVATAR_IMG] = None - for jid in list(app.contacts.get_jid_list(account)): - lcontact = app.contacts.get_contacts(account, jid) - ctrl = app.window.get_control(account, jid) - for contact in [c for c in lcontact if ( - (c.show != 'offline' or c.is_transport()) and not ctrl)]: - self.chg_contact_status(contact, 'offline', '', account) - if app.interface.systray_enabled: - app.interface.systray.change_status(show) - self._status_selector.update() - - def change_status(self, _widget, account, status): - app.interface.change_account_status(account, status=status) - - def get_show(self, lcontact): - prio = lcontact[0].priority - show = lcontact[0].show - for u in lcontact: - if u.priority > prio: - prio = u.priority - show = u.show - return show - - def on_message_window_delete(self, win_mgr, msg_win): - if app.settings.get('one_message_window') == 'always_with_roster': - self.show_roster_vbox(True) - resize_window(self.window, - app.settings.get('roster_width'), - app.settings.get('roster_height')) - - def close_all_from_dict(self, dic): - """ - Close all the windows in the given dictionary - """ - for w in list(dic.values()): - if isinstance(w, dict): - self.close_all_from_dict(w) - else: - try: - w.window.destroy() - except (AttributeError, RuntimeError): - w.destroy() - - def close_all(self, account, force=False): - """ - Close all the windows from an account. If force is True, do not ask - confirmation before closing chat/gc windows - """ - if account in app.interface.instances: - self.close_all_from_dict(app.interface.instances[account]) - # for ctrl in app.interface.msg_win_mgr.get_controls(acct=account): - # ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, - # force=force) - - def on_roster_window_delete_event(self, widget, event): - """ - Main window X button was clicked - """ - if not app.settings.get('quit_on_roster_x_button') and ( - (app.interface.systray_enabled and app.settings.get('trayicon') != \ - 'on_event') or app.settings.get('allow_hide_roster')): - save_roster_position(self.window) - if os.name == 'nt' or app.settings.get('hide_on_roster_x_button'): - self.window.hide() - else: - self.window.iconify() - elif app.settings.get('quit_on_roster_x_button'): - self.on_quit_request() - else: - def _on_ok(is_checked): - if is_checked: - app.settings.set('quit_on_roster_x_button', True) - self.on_quit_request() - ConfirmationCheckDialog( - _('Quit Gajim'), - _('You are about to quit Gajim'), - _('Are you sure you want to quit Gajim?'), - _('_Always quit when closing Gajim'), - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - text=_('_Quit'), - callback=_on_ok)]).show() - return True # Do NOT destroy the window - - def prepare_quit(self): - if self.save_done: - return - msgwin_width_adjust = 0 - - # in case show_roster_on_start is False and roster is never shown - # window.window is None - if self.window.get_window() is not None: - save_roster_position(self.window) - width, height = self.window.get_size() - app.settings.set('roster_width', width) - app.settings.set('roster_height', height) - if not self.xml.get_object('roster_vbox2').get_property('visible'): - # The roster vbox is hidden, so the message window is larger - # then we want to save (i.e. the window will grow every startup) - # so adjust. - msgwin_width_adjust = -1 * width - app.settings.set('last_roster_visible', - self.window.get_property('visible')) - # app.interface.msg_win_mgr.save_opened_controls() - # app.interface.msg_win_mgr.shutdown(msgwin_width_adjust) - - app.settings.set('collapsed_rows', '\t'.join(self.collapsed_rows)) - app.interface.save_config() - for account, con in app.connections.items(): - con.quit(True) - self.close_all(account) - if app.interface.systray_enabled: - app.interface.hide_systray() - self.save_done = True - - def _nec_presence_received(self, obj): - account = obj.conn.name - jid = obj.jid - - if obj.need_add_in_roster: - self.add_contact(jid, account) - - jid_list = app.contacts.get_jid_list(account) - if jid in jid_list or jid == app.get_jid_from_account(account): - if not app.jid_is_transport(jid) and len(obj.contact_list) == 1: - if obj.old_show == 0 and obj.new_show > 1: - GLib.timeout_add_seconds(5, self.remove_newly_added, jid, - account) - elif obj.old_show > 1 and obj.new_show == 0 and \ - obj.conn.state.is_available: - GLib.timeout_add_seconds(5, self.remove_to_be_removed, - jid, account) - - self.draw_contact(jid, account) - - if app.jid_is_transport(jid) and jid in jid_list: - # It must be an agent - # Update existing iter and group counting - self.draw_contact(jid, account) - self.draw_group(_('Transports'), account) - - if obj.contact: - self.chg_contact_status(obj.contact, obj.show, obj.status, account) - - if obj.popup: - ctrl = app.interface.msg_win_mgr.search_control(jid, account) - if ctrl: - GLib.idle_add(ctrl.parent_win.set_active_tab, ctrl) - else: - ctrl = app.interface.new_chat(obj.contact, account) - if app.events.get_events(account, obj.jid): - ctrl.read_queue() - - def _nec_roster_received(self, obj): - if obj.received_from_server: - self.fill_contacts_and_groups_dicts(obj.roster, obj.conn.name) - self.add_account_contacts(obj.conn.name) - self.fire_up_unread_messages_events(obj.conn.name) - else: - # add self contact - account = obj.conn.name - self_jid = app.get_jid_from_account(account) - if self_jid not in app.contacts.get_jid_list(account): - sha = app.settings.get_account_setting(account, 'avatar_sha') - contact = app.contacts.create_contact( - jid=self_jid, account=account, name=app.nicks[account], - groups=['self_contact'], show='offline', sub='both', - ask='none', avatar_sha=sha) - app.contacts.add_contact(account, contact) - self.add_contact(self_jid, account) - - if app.settings.get('remember_opened_chat_controls'): - account = obj.conn.name - controls = app.settings.get_account_setting( - account, 'opened_chat_controls') - if controls: - for jid in controls.split(','): - contact = \ - app.contacts.get_contact_with_highest_priority( - account, jid) - if not contact: - contact = self.add_to_not_in_the_roster( - account, jid) - # app.interface.on_open_chat_window( - # None, contact, account) - # app.settings.set_account_setting( - # account, 'opened_chat_controls', '') - GLib.idle_add(self.refilter_shown_roster_items) - - def _nec_anonymous_auth(self, obj): - """ - This event is raised when our JID changed (most probably because we use - anonymous account. We update contact and roster entry in this case - """ - self.rename_self_contact(obj.old_jid, obj.new_jid, obj.conn.name) - - def _nec_our_show(self, event): - if event.show == 'offline': - self.application.set_account_actions_state(event.account) - self.application.update_app_actions_state() - - self.on_status_changed(event.account, event.show) - - def _nec_connection_type(self, obj): - self.draw_account(obj.conn.name) - - def _nec_agent_removed(self, obj): - for jid in obj.jid_list: - self.remove_contact(jid, obj.conn.name, backend=True) - - def _on_mood_received(self, event): - if event.is_self_message: - self.draw_account(event.account) - self._draw_pep(event.account, event.jid, PEPEventType.MOOD) - - def _on_activity_received(self, event): - if event.is_self_message: - self.draw_account(event.account) - self._draw_pep(event.account, event.jid, PEPEventType.ACTIVITY) - - def _on_tune_received(self, event): - if event.is_self_message: - self.draw_account(event.account) - self._draw_pep(event.account, event.jid, PEPEventType.TUNE) - - def _on_location_received(self, event): - if event.is_self_message: - self.draw_account(event.account) - self._draw_pep(event.account, event.jid, PEPEventType.LOCATION) - - def _on_nickname_received(self, event): - self.draw_contact(event.jid, event.account) - - def _nec_update_avatar(self, obj): - app.log('avatar').debug('Draw roster avatar: %s', obj.jid) - self.draw_avatar(obj.jid, obj.account) - - def _nec_muc_subject_received(self, event): - self.draw_contact(event.room_jid, event.account) - - def _on_muc_disco_update(self, event): - self.draw_contact(str(event.room_jid), event.account) - - def _on_bookmarks_received(self, event): - con = app.connections[event.account] - for bookmark in con.get_module('Bookmarks').bookmarks: - self.draw_contact(str(bookmark.jid), event.account) - - def _nec_metacontacts_received(self, obj): - self.redraw_metacontacts(obj.conn.name) - - def _nec_signed_in(self, obj): - self.application.set_account_actions_state(obj.conn.name, True) - self.application.update_app_actions_state() - self.draw_account(obj.conn.name) - - def _nec_decrypted_message_received(self, obj): - if not obj.msgtxt: - return True - if obj.properties.type.value not in ('normal', 'chat'): - return - - if obj.popup and not obj.session.control: - contact = app.contacts.get_contact(obj.conn.name, obj.jid) - obj.session.control = app.interface.new_chat(contact, - obj.conn.name, session=obj.session) - if app.events.get_events(obj.conn.name, obj.fjid): - obj.session.control.read_queue() - - if not obj.properties.is_muc_pm and obj.show_in_roster: - self.draw_contact(obj.jid, obj.conn.name) - self.show_title() # we show the * or [n] - # Select the big brother contact in roster, it's visible because it - # has events. - family = app.contacts.get_metacontacts_family(obj.conn.name, - obj.jid) - if family: - _nearby_family, bb_jid, bb_account = \ - app.contacts.get_nearby_family_and_big_brother(family, - obj.conn.name) - else: - bb_jid, bb_account = obj.jid, obj.conn.name - self.select_contact(bb_jid, bb_account) - -################################################################################ -### Menu and GUI callbacks -### FIXME: order callbacks in itself... -################################################################################ - - def on_info(self, widget, contact, account): - """ - Call vcard_information_window class to display contact's information - """ - if app.connections[account].is_zeroconf: - self.on_info_zeroconf(widget, contact, account) - return - - info = app.interface.instances[account]['infos'] - if contact.jid in info: - info[contact.jid].window.present() - else: - info[contact.jid] = vcard.VcardWindow(contact, account) - - def on_info_zeroconf(self, widget, contact, account): - info = app.interface.instances[account]['infos'] - if contact.jid in info: - info[contact.jid].window.present() - else: - contact = app.contacts.get_first_contact_from_jid(account, - contact.jid) - if contact.show in ('offline', 'error'): - # don't show info on offline contacts - return - info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) - - def on_edit_agent(self, widget, contact, account): - """ - When we want to modify the agent registration - """ - ServiceRegistration(account, contact.jid) - - def on_remove_agent(self, widget, list_): - """ - When an agent is requested to be removed. list_ is a list of (contact, - account) tuple - """ - for (contact, account) in list_: - if app.settings.get_account_setting(account, 'hostname') == \ - contact.jid: - # We remove the server contact - # remove it from treeview - app.connections[account].get_module('Presence').unsubscribe(contact.jid) - self.remove_contact(contact.jid, account, backend=True) - return - - def remove(): - for (contact, account) in list_: - full_jid = contact.get_full_jid() - app.connections[account].get_module('Gateway').unsubscribe(full_jid) - # remove transport from treeview - self.remove_contact(contact.jid, account, backend=True) - - # Check if there are unread events from some contacts - has_unread_events = False - for (contact, account) in list_: - for jid in app.events.get_events(account): - if jid.endswith(contact.jid): - has_unread_events = True - break - if has_unread_events: - ErrorDialog( - _('You have unread messages'), - _('You must read them before removing this transport.')) - return - if len(list_) == 1: - pritext = _('Transport \'%s\' will be removed') % list_[0][0].jid - sectext = _('You will no longer be able to send and receive ' - 'messages from and to contacts using this transport.') - else: - pritext = _('Transports will be removed') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ',' - jids = jids[:-1] + '.' - sectext = _('You will no longer be able to send and receive ' - 'messages from and to contacts using these ' - 'transports:\n%s') % jids - ConfirmationDialog( - _('Remove Transport'), - pritext, - sectext, - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=remove)], - transient_for=self.window).show() - - def _nec_blocking(self, obj): - for jid in obj.changed: - self.draw_contact(str(jid), obj.conn.name) - - def on_block(self, widget, list_): - """ - When clicked on the 'block' button in context menu. list_ is a list of - (contact, account) - """ - def _block_it(is_checked=None, report=None): - if is_checked is not None: # Dialog has been shown - if is_checked: - app.settings.set('confirm_block', 'no') - else: - app.settings.set('confirm_block', 'yes') - - accounts = [] - for _, account in list_: - con = app.connections[account] - if con.get_module('Blocking').supported: - accounts.append(account) - - for acct in accounts: - l_ = [i[0] for i in list_ if i[1] == acct] - con = app.connections[acct] - jid_list = [contact.jid for contact in l_] - con.get_module('Blocking').block(jid_list, report) - for contact in l_: - app.events.remove_events(acct, contact.jid) - ctrl = app.interface.msg_win_mgr.get_control( - contact.jid, acct) - if ctrl: - ctrl.parent_win.remove_tab( - ctrl, ctrl.parent_win.CLOSE_COMMAND, force=True) - if contact.show == 'not in roster': - self.remove_contact(contact.jid, acct, force=True, - backend=True) - return - self.draw_contact(contact.jid, acct) - - # Check if confirmation is needed for blocking - confirm_block = app.settings.get('confirm_block') - if confirm_block == 'no': - _block_it() - return - - ConfirmationCheckDialog( - _('Block Contact'), - _('Really block this contact?'), - _('You will appear offline for this contact and you ' - 'will not receive further messages.'), - _('_Do not ask again'), - [DialogButton.make('Cancel'), - DialogButton.make('OK', - text=_('_Report Spam'), - callback=_block_it, - kwargs={'report': 'spam'}), - DialogButton.make('Remove', - text=_('_Block'), - callback=_block_it)], - modal=False).show() - - def on_unblock(self, widget, list_): - """ - When clicked on the 'unblock' button in context menu. - """ - accounts = [] - for _, account in list_: - con = app.connections[account] - if con.get_module('Blocking').supported: - accounts.append(account) - - for acct in accounts: - l_ = [i[0] for i in list_ if i[1] == acct] - con = app.connections[acct] - jid_list = [contact.jid for contact in l_] - con.get_module('Blocking').unblock(jid_list) - for contact in l_: - self.draw_contact(contact.jid, acct) - - def on_rename(self, widget, row_type, jid, account): - # This function is called either by F2 or by Rename menuitem - if 'rename' in app.interface.instances: - app.interface.instances['rename'].dialog.present() - return - - # Account is offline, don't allow to rename - if not app.account_is_available(account): - return - if row_type in ('contact', 'agent'): - # It's jid - title = _('Rename Contact') - text = _('Rename contact %s?') % jid - sec_text = _('Please enter a new nickname') - old_text = app.contacts.get_contact_with_highest_priority(account, - jid).name - elif row_type == 'group': - if jid in helpers.special_groups + (_('General'),): - return - old_text = jid - title = _('Rename Group') - text = _('Rename group %s?') % GLib.markup_escape_text(jid) - sec_text = _('Please enter a new name') - - def _on_renamed(new_text, account, row_type, jid, old_text): - if row_type in ('contact', 'agent'): - if old_text == new_text: - return - contacts = app.contacts.get_contacts(account, jid) - for contact in contacts: - contact.name = new_text - con = app.connections[account] - con.get_module('Roster').update_contact( - jid, new_text, contacts[0].groups) - self.draw_contact(jid, account) - # Update opened chats - for ctrl in app.interface.msg_win_mgr.get_controls(jid, - account): - ctrl.update_ui() - win = app.interface.msg_win_mgr.get_window(jid, account) - win.redraw_tab(ctrl) - win.show_title() - elif row_type == 'group': - # In Column.JID column, we hold the group name (which is not escaped) - self.rename_group(old_text, new_text, account) - - InputDialog( - title, - text, - sec_text, - [DialogButton.make('Cancel'), - DialogButton.make('Accept', - text=_('_Rename'), - callback=_on_renamed, - args=[account, - row_type, - jid, - old_text])], - input_str=old_text, - transient_for=self.window).show() - - def on_remove_group_item_activated(self, widget, group, account): - def _on_ok(is_checked): - for contact in app.contacts.get_contacts_from_group(account, - group): - if not is_checked: - self.remove_contact_from_groups(contact.jid, account, - [group]) - else: - app.connections[account].get_module( - 'Presence').unsubscribe(contact.jid) - self.remove_contact(contact.jid, account, backend=True) - - ConfirmationCheckDialog( - _('Remove Group'), - _('Remove Group'), - _('Do you want to remove %s from the contact list?') % group, - _('_Also remove all contacts of this group from contact list'), - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=_on_ok)]).show() - - def on_edit_groups(self, widget, list_): - dialogs.EditGroupsDialog(list_) - - def on_disconnect(self, widget, jid, account): - """ - When disconnect menuitem is activated: disconnect from room - """ - if jid in app.interface.minimized_controls[account]: - ctrl = app.interface.minimized_controls[account][jid] - ctrl.leave() - self.remove_groupchat(jid, account) - - def on_send_single_message_menuitem_activate(self, _widget, account, - contact=None): - if contact is None: - open_window('SingleMessageWindow', account=account) - elif isinstance(contact, list): - open_window('SingleMessageWindow', account=account, - recipients=contact) - else: - jid = contact.jid - open_window('SingleMessageWindow', account=account, - recipients=jid) - - def on_send_file_menuitem_activate(self, widget, contact, account, - resource=None): - app.interface.instances['file_transfers'].show_file_send_request( - account, contact) - - def on_invite_to_room(self, - _widget, - list_, - room_jid, - room_account, - resource=None): - """ - Resource parameter MUST NOT be used if more than one contact in list - """ - gc_control = app.get_groupchat_control(room_account, room_jid) - if gc_control is None: - return - - for contact, _ in list_: - contact_jid = contact.jid - if resource: # we MUST have one contact only in list_ - contact_jid += '/' + resource - gc_control.invite(contact_jid) - - def on_all_groupchat_maximized(self, widget, group_list): - for (contact, account) in group_list: - self.on_groupchat_maximized(widget, contact.jid, account) - - def on_groupchat_maximized(self, widget, jid, account): - """ - When a groupchat is maximized - """ - if not jid in app.interface.minimized_controls[account]: - # Already opened? - gc_control = app.interface.msg_win_mgr.get_gc_control(jid, - account) - if gc_control: - mw = app.interface.msg_win_mgr.get_window(jid, account) - mw.set_active_tab(gc_control) - return - ctrl = app.interface.minimized_controls[account][jid] - mw = app.interface.msg_win_mgr.get_window(jid, account) - if not mw: - mw = app.interface.msg_win_mgr.create_window( - ctrl.contact, ctrl.account, ctrl.type) - id_ = mw.window.connect('motion-notify-event', - ctrl._on_window_motion_notify) - ctrl.handlers[id_] = mw.window - ctrl.parent_win = mw - ctrl.on_groupchat_maximize() - mw.new_tab(ctrl) - mw.set_active_tab(ctrl) - self.remove_groupchat(jid, account, maximize=True) - - def on_groupchat_rename(self, _widget, jid, account): - def _on_rename(new_name): - con = app.connections[account] - con.get_module('Bookmarks').modify(jid, name=new_name) - - contact = app.contacts.get_first_contact_from_jid(account, jid) - name = contact.get_shown_name() - - InputDialog( - _('Rename Group Chat'), - _('Rename Group Chat'), - _('Please enter a new name for this group chat'), - [DialogButton.make('Cancel'), - DialogButton.make('Accept', - text=_('_Rename'), - callback=_on_rename)], - input_str=name, - transient_for=self.window).show() - - def on_change_status_message_activate(self, _widget, account): - app.interface.change_account_status(account) - - def on_add_to_roster(self, widget, contact, account): - AddNewContactWindow(account, contact.jid, contact.name) - - def on_roster_treeview_key_press_event(self, widget, event): - """ - When a key is pressed in the treeviews - """ - if event.keyval == Gdk.KEY_Escape: - if self.rfilter_enabled: - self.disable_rfilter() - else: - self.tree.get_selection().unselect_all() - elif event.keyval == Gdk.KEY_F2: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][Column.TYPE] - if type_ in ('contact', 'group', 'agent'): - jid = model[path][Column.JID] - account = model[path][Column.ACCOUNT] - self.on_rename(widget, type_, jid, account) - - elif event.keyval == Gdk.KEY_Delete: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if not list_of_paths: - return - type_ = model[list_of_paths[0]][Column.TYPE] - account = model[list_of_paths[0]][Column.ACCOUNT] - if type_ in ('account', 'group', 'self_contact') or \ - account == app.ZEROCONF_ACC_NAME: - return - list_ = [] - for path in list_of_paths: - if model[path][Column.TYPE] != type_: - return - jid = model[path][Column.JID] - account = model[path][Column.ACCOUNT] - if not app.account_is_available(account): - continue - contact = app.contacts.get_contact_with_highest_priority( - account, jid) - list_.append((contact, account)) - if not list_: - return - if type_ == 'contact': - self.on_req_usub(widget, list_) - elif type_ == 'agent': - self.on_remove_agent(widget, list_) - - elif not (event.get_state() & - (Gdk.ModifierType.CONTROL_MASK | - Gdk.ModifierType.MOD1_MASK)): - num = Gdk.keyval_to_unicode(event.keyval) - if num and num > 31: - # if we got unicode symbol without ctrl / alt - self.enable_rfilter(chr(num)) - - elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and \ - event.get_state() & Gdk.ModifierType.SHIFT_MASK and \ - event.keyval == Gdk.KEY_U: - self.enable_rfilter('') - self.rfilter_entry.event(event) - - elif event.keyval == Gdk.KEY_Left: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - iter_ = model.get_iter(path) - if model.iter_has_child(iter_) and self.tree.row_expanded(path): - self.tree.collapse_row(path) - return True - if path.get_depth() > 1: - self.tree.set_cursor(path[:-1]) - return True - elif event.keyval == Gdk.KEY_Right: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - iter_ = model.get_iter(path) - if model.iter_has_child(iter_): - self.tree.expand_row(path, False) - return True - - def accel_group_func(self, accel_group, acceleratable, keyval, modifier): - # CTRL mask - if modifier & Gdk.ModifierType.CONTROL_MASK: - if keyval == Gdk.KEY_s: # CTRL + s - app.interface.change_status() - return True - if keyval == Gdk.KEY_k: # CTRL + k - self.enable_rfilter('') - - def on_roster_treeview_button_press_event(self, widget, event): - try: - pos = self.tree.get_path_at_pos(int(event.x), int(event.y)) - path, x = pos[0], pos[2] - except TypeError: - self.tree.get_selection().unselect_all() - return False - - if event.button == 3: # Right click - try: - model, list_of_paths = self.tree.get_selection().\ - get_selected_rows() - except TypeError: - list_of_paths = [] - if path not in list_of_paths: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - return self.show_treeview_menu(event) - - if event.button == 2: # Middle click - try: - model, list_of_paths = self.tree.get_selection().\ - get_selected_rows() - except TypeError: - list_of_paths = [] - if list_of_paths != [path]: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - type_ = model[path][Column.TYPE] - if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): - self.on_row_activated(widget, path) - elif type_ == 'account': - account = model[path][Column.ACCOUNT] - if account != 'all': - if app.account_is_available(account): - app.interface.change_account_status(account) - return True - - show = helpers.get_global_show() - if show == 'offline': - return True - app.interface.change_status() - return True - - if event.button == 1: # Left click - model = self.modelfilter - type_ = model[path][Column.TYPE] - # x_min is the x start position of status icon column - if app.settings.get('avatar_position_in_roster') == 'left': - x_min = AvatarSize.ROSTER - else: - x_min = 0 - - if type_ == 'group' and x < 27: - # first cell in 1st column (the arrow SINGLE clicked) - if self.tree.row_expanded(path): - self.tree.collapse_row(path) - else: - self.expand_group_row(path) - - elif type_ == 'contact' and x_min < x < x_min + 27: - if self.tree.row_expanded(path): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - def expand_group_row(self, path): - self.tree.expand_row(path, False) - iter_ = self.modelfilter.get_iter(path) - child_iter = self.modelfilter.iter_children(iter_) - while child_iter: - type_ = self.modelfilter[child_iter][Column.TYPE] - account = self.modelfilter[child_iter][Column.ACCOUNT] - group = self.modelfilter[child_iter][Column.JID] - if type_ == 'group' and account + group not in self.collapsed_rows: - self.expand_group_row(self.modelfilter.get_path(child_iter)) - child_iter = self.modelfilter.iter_next(child_iter) - - def on_req_usub(self, widget, list_): - """ - Remove a contact. list_ is a list of (contact, account) tuples - """ - def on_ok(is_checked): - remove_auth = True - if len(list_) == 1: - contact = list_[0][0] - if contact.sub != 'to' and is_checked: - remove_auth = False - for (contact, account) in list_: - if _('Not in contact list') not in contact.get_shown_groups(): - app.connections[account].get_module('Presence').unsubscribe(contact.jid, - remove_auth) - self.remove_contact(contact.jid, account, backend=True) - if not remove_auth and contact.sub == 'both': - contact.name = '' - contact.groups = [] - contact.sub = 'from' - # we can't see him, but have to set it manually in contact - contact.show = 'offline' - app.contacts.add_contact(account, contact) - self.add_contact(contact.jid, account) - def on_ok2(): - on_ok(False) - - if len(list_) == 1: - contact = list_[0][0] - title = _('Remove Contact') - pritext = _('Remove contact from contact list') - sectext = _('You are about to remove %(name)s (%(jid)s) from ' - 'your contact list.\n') % { - 'name': contact.get_shown_name(), - 'jid': contact.jid} - if contact.sub == 'to': - ConfirmationDialog( - title, - pritext, - sectext + \ - _('By removing this contact you also remove authorization. ' - 'This means the contact will see you as offline.'), - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=on_ok2)]).show() - elif _('Not in contact list') in contact.get_shown_groups(): - # Contact is not in roster - ConfirmationDialog( - title, - pritext, - sectext + \ - _('Do you want to continue?'), - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=on_ok2)]).show() - else: - ConfirmationCheckDialog( - title, - pritext, - sectext + \ - _('By removing this contact you also remove authorization. ' - 'This means the contact will see you as offline.'), - _('_I want this contact to know my status after removal'), - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=on_ok)], - modal=False).show() - else: - # several contact to remove at the same time - pritext = _('Remove contacts from contact list') - jids = '' - for contact, _account in list_: - jids += '%(name)s (%(jid)s)\n' % { - 'name': contact.get_shown_name(), - 'jid': contact.jid} - sectext = _('By removing the following contacts, you will also ' - 'remove authorization. This means they will see you ' - 'as offline:\n\n%s') % jids - ConfirmationDialog( - _('Remove Contacts'), - pritext, - sectext, - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=on_ok2)]).show() - - def on_publish_tune_toggled(self, widget, account): - active = widget.get_active() - client = app.get_client(account) - client.get_module('UserTune').set_enabled(active) - - def on_publish_location_toggled(self, widget, account): - active = widget.get_active() - client = app.get_client(account) - app.settings.set_account_setting(account, 'publish_location', active) - - if active: - location.enable() - else: - client = app.get_client(account) - client.set_user_location(None) - - client.get_module('Caps').update_caps() - - def on_add_new_contact(self, widget, account): - AddNewContactWindow(account) - - def on_create_gc_activate(self, widget, account): - """ - When the create gc menuitem is clicked, show the create gc window - """ - app.app.activate_action('create-groupchat', - GLib.Variant('s', account)) - - def on_show_transports_action(self, action, param): - app.settings.set('show_transports_group', param.get_boolean()) - action.set_state(param) - self.refilter_shown_roster_items() - - def on_execute_command(self, widget, contact, account, resource=None): - """ - Execute command. Full JID needed; if it is other contact, resource is - necessary. Widget is unnecessary, only to be able to make this a - callback - """ - jid = contact.jid - if resource: - jid = jid + '/' + resource - AdHocCommand(account, jid) - - def on_view_server_info(self, _widget, account): - app.app.activate_action('%s-server-info' % account, - GLib.Variant('s', account)) - - def on_roster_window_focus_in_event(self, widget, event): - # roster received focus, so if we had urgency REMOVE IT - # NOTE: we do not have to read the message to remove urgency - # so this functions does that - set_urgency_hint(widget, False) - - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if self._last_selected_contact: - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected=True, focus=True) - - def on_roster_window_focus_out_event(self, widget, event): - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if self._last_selected_contact: - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected=True, focus=False) - - def on_roster_window_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - if self.rfilter_enabled: - self.disable_rfilter() - return True - if app.interface.msg_win_mgr.mode == \ - MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ - app.interface.msg_win_mgr.one_window_opened(): - # let message window close the tab - return - list_of_paths = self.tree.get_selection().get_selected_rows()[1] - if not list_of_paths and not app.settings.get( - 'quit_on_roster_x_button') and ((app.interface.systray_enabled and\ - app.settings.get('trayicon') == 'always') or app.settings.get( - 'allow_hide_roster')): - if os.name == 'nt' or app.settings.get('hide_on_roster_x_button'): - self.window.hide() - else: - self.window.iconify() - elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == \ - Gdk.KEY_i: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - for path in list_of_paths: - type_ = model[path][Column.TYPE] - if type_ in ('contact', 'agent'): - jid = model[path][Column.JID] - account = model[path][Column.ACCOUNT] - contact = app.contacts.get_first_contact_from_jid(account, - jid) - self.on_info(widget, contact, account) - elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == \ - Gdk.KEY_h: - if app.settings.get('one_message_window') == 'always_with_roster': - # Let MessageWindow handle this - return - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][Column.TYPE] - if type_ in ('contact', 'agent'): - jid = model[path][Column.JID] - account = model[path][Column.ACCOUNT] - contact = app.contacts.get_first_contact_from_jid(account, - jid) - dict_ = {'jid': GLib.Variant('s', jid), - 'account': GLib.Variant('s', account)} - app.app.activate_action('browse-history', - GLib.Variant('a{sv}', dict_)) - - def on_roster_window_popup_menu(self, widget): - event = Gdk.Event.new(Gdk.EventType.KEY_PRESS) - self.show_treeview_menu(event) - - def on_row_activated(self, widget, path): - """ - When an iter is activated (double-click or single click if gnome is set - this way) - """ - model = self.modelfilter - account = model[path][Column.ACCOUNT] - type_ = model[path][Column.TYPE] - if type_ in ('group', 'account'): - if self.tree.row_expanded(path): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return - if self.rfilter_enabled: - GObject.idle_add(self.disable_rfilter) - jid = model[path][Column.JID] - resource = None - contact = app.contacts.get_contact_with_highest_priority(account, jid) - titer = model.get_iter(path) - if contact.is_groupchat: - first_ev = app.events.get_first_event(account, jid) - if first_ev and self.open_event(account, jid, first_ev): - # We are invited to a GC - # open event cares about connecting to it - self.remove_groupchat(jid, account) - else: - self.on_groupchat_maximized(None, jid, account) - return - - # else - first_ev = app.events.get_first_event(account, jid) - if not first_ev: - # look in other resources - for c in app.contacts.get_contacts(account, jid): - fjid = c.get_full_jid() - first_ev = app.events.get_first_event(account, fjid) - if first_ev: - resource = c.resource - break - if not first_ev and model.iter_has_child(titer): - child_iter = model.iter_children(titer) - while not first_ev and child_iter: - child_jid = model[child_iter][Column.JID] - first_ev = app.events.get_first_event(account, child_jid) - if first_ev: - jid = child_jid - else: - child_iter = model.iter_next(child_iter) - session = None - if first_ev: - if first_ev.type_ in ('chat', 'normal'): - session = first_ev.session - fjid = jid - if resource: - fjid += '/' + resource - if self.open_event(account, fjid, first_ev): - return - # else - contact = app.contacts.get_contact(account, jid, resource) - if not contact or isinstance(contact, list): - contact = app.contacts.get_contact_with_highest_priority(account, - jid) - if jid == app.get_jid_from_account(account): - resource = None - - app.interface.on_open_chat_window(None, contact, account, \ - resource=resource, session=session) - - def on_roster_treeview_row_activated(self, widget, path, col=0): - """ - When an iter is double clicked: open the first event window - """ - self.on_row_activated(widget, path) - - def on_roster_treeview_row_expanded(self, widget, titer, path): - """ - When a row is expanded change the icon of the arrow - """ - self._toggeling_row = True - model = widget.get_model() - child_model = model.get_model() - child_iter = model.convert_iter_to_child_iter(titer) - - if self.regroup: # merged accounts - accounts = list(app.connections.keys()) - else: - accounts = [model[titer][Column.ACCOUNT]] - - type_ = model[titer][Column.TYPE] - if type_ == 'group': - group = model[titer][Column.JID] - child_model[child_iter][Column.IMG] = get_icon_name('opened') - if self.rfilter_enabled: - return - for account in accounts: - if group in app.groups[account]: # This account has this group - app.groups[account][group]['expand'] = True - if account + group in self.collapsed_rows: - self.collapsed_rows.remove(account + group) - for contact in app.contacts.iter_contacts(account): - jid = contact.jid - if group in contact.groups and \ - app.contacts.is_big_brother(account, jid, accounts) and \ - account + group + jid not in self.collapsed_rows: - titers = self._get_contact_iter(jid, account) - for titer_ in titers: - path = model.get_path(titer_) - self.tree.expand_row(path, False) - elif type_ == 'account': - account = list(accounts)[0] # There is only one cause we don't use merge - if account in self.collapsed_rows: - self.collapsed_rows.remove(account) - self.draw_account(account) - # When we expand, groups are collapsed. Restore expand state - for group in app.groups[account]: - if app.groups[account][group]['expand']: - titer = self._get_group_iter(group, account) - if titer: - path = model.get_path(titer) - self.tree.expand_row(path, False) - elif type_ == 'contact': - # Metacontact got toggled, update icon - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - contact = app.contacts.get_contact(account, jid) - for group in contact.groups: - if account + group + jid in self.collapsed_rows: - self.collapsed_rows.remove(account + group + jid) - family = app.contacts.get_metacontacts_family(account, jid) - nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] - # Redraw all brothers to show pending events - for data in nearby_family: - self.draw_contact(data['jid'], data['account']) - - self._toggeling_row = False - - def on_roster_treeview_row_collapsed(self, widget, titer, path): - """ - When a row is collapsed change the icon of the arrow - """ - self._toggeling_row = True - model = widget.get_model() - child_model = model.get_model() - child_iter = model.convert_iter_to_child_iter(titer) - - if self.regroup: # merged accounts - accounts = list(app.connections.keys()) - else: - accounts = [model[titer][Column.ACCOUNT]] - - type_ = model[titer][Column.TYPE] - if type_ == 'group': - child_model[child_iter][Column.IMG] = get_icon_name('closed') - if self.rfilter_enabled: - return - group = model[titer][Column.JID] - for account in accounts: - if group in app.groups[account]: # This account has this group - app.groups[account][group]['expand'] = False - if account + group not in self.collapsed_rows: - self.collapsed_rows.append(account + group) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if account not in self.collapsed_rows: - self.collapsed_rows.append(account) - self.draw_account(account) - elif type_ == 'contact': - # Metacontact got toggled, update icon - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - contact = app.contacts.get_contact(account, jid) - groups = contact.groups - if not groups: - groups = [_('General')] - for group in groups: - if account + group + jid not in self.collapsed_rows: - self.collapsed_rows.append(account + group + jid) - family = app.contacts.get_metacontacts_family(account, jid) - nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] - # Redraw all brothers to show pending events - for data in nearby_family: - self.draw_contact(data['jid'], data['account']) - - self._toggeling_row = False - - def on_modelfilter_row_has_child_toggled(self, model, path, titer): - """ - Called when a row has gotten the first or lost its last child row - - Expand Parent if necessary. - """ - if self._toggeling_row: - # Signal is emitted when we write to our model - return - - type_ = model[titer][Column.TYPE] - account = model[titer][Column.ACCOUNT] - if not account: - return - - if type_ == 'contact': - child_iter = model.convert_iter_to_child_iter(titer) - if self.model.iter_has_child(child_iter): - # we are a bigbrother metacontact - # redraw us to show/hide expand icon - if self.filtering: - # Prevent endless loops - jid = model[titer][Column.JID] - GLib.idle_add(self.draw_contact, jid, account) - elif type_ == 'group': - group = model[titer][Column.JID] - GLib.idle_add(self._adjust_group_expand_collapse_state, group, account) - elif type_ == 'account': - GLib.idle_add(self._adjust_account_expand_collapse_state, account) - -# Selection can change when the model is filtered -# Only write to the model when filtering is finished! -# -# FIXME: When we are filtering our custom colors are somehow lost -# -# def on_treeview_selection_changed(self, selection): -# '''Called when selection in TreeView has changed. -# -# Redraw unselected rows to make status message readable -# on all possible backgrounds. -# ''' -# model, list_of_paths = selection.get_selected_rows() -# if len(self._last_selected_contact): -# # update unselected rows -# for (jid, account) in self._last_selected_contact: -# GLib.idle_add(self.draw_contact, jid, -# account) -# self._last_selected_contact = [] -# if len(list_of_paths) == 0: -# return -# for path in list_of_paths: -# row = model[path] -# if row[Column.TYPE] != 'contact': -# self._last_selected_contact = [] -# return -# jid = row[Column.JID] -# account = row[Column.ACCOUNT] -# self._last_selected_contact.append((jid, account)) -# GLib.idle_add(self.draw_contact, jid, account, True) - - - def on_service_disco_menuitem_activate(self, widget, account): - server_jid = app.settings.get_account_setting(account, 'hostname') - if server_jid in app.interface.instances[account]['disco']: - app.interface.instances[account]['disco'][server_jid].\ - window.present() - else: - try: - # Object will add itself to the window dict - ServiceDiscoveryWindow(account, address_entry=True) - except GajimGeneralException: - pass - - def on_show_offline_contacts_action(self, action, param): - """ - When show offline option is changed: redraw the treeview - """ - action.set_state(param) - app.settings.set('showoffline', param.get_boolean()) - self.refilter_shown_roster_items() - self.window.lookup_action('show-active').set_enabled( - not param.get_boolean()) - - def on_show_active_contacts_action(self, action, param): - """ - When show only active contact option is changed: redraw the treeview - """ - action.set_state(param) - app.settings.set('show_only_chat_and_online', param.get_boolean()) - self.refilter_shown_roster_items() - self.window.lookup_action('show-offline').set_enabled( - not param.get_boolean()) - - def on_show_roster_action(self, action, param): - # when num controls is 0 this menuitem is hidden, but still need to - # disable keybinding - action.set_state(param) - if self.hpaned.get_child2() is not None: - self.show_roster_vbox(param.get_boolean()) - - def on_rfilter_entry_changed(self, widget): - """ When we update the content of the filter """ - self.rfilter_string = widget.get_text().lower() - if self.rfilter_string == '': - self.disable_rfilter() - self.refilter_shown_roster_items() - # select first row - self.tree.get_selection().unselect_all() - def _func(model, path, iter_, param): - if model[iter_][Column.TYPE] == 'contact' and self.rfilter_string in \ - model[iter_][Column.NAME].lower(): - col = self.tree.get_column(0) - self.tree.set_cursor_on_cell(path, col, None, False) - return True - self.modelfilter.foreach(_func, None) - - def on_rfilter_entry_icon_press(self, widget, icon, event): - """ - Disable the roster filtering by clicking the icon in the textEntry - """ - self.disable_rfilter() - - def on_rfilter_entry_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - self.disable_rfilter() - elif event.keyval == Gdk.KEY_Return: - self.tree.grab_focus() - self.tree.event(event) - self.disable_rfilter() - elif event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down): - self.tree.grab_focus() - self.tree.event(event) - elif event.keyval == Gdk.KEY_BackSpace: - if widget.get_text() == '': - self.disable_rfilter() - - def enable_rfilter(self, search_string): - self.rfilter_entry.set_visible(True) - self.rfilter_entry.set_editable(True) - self.rfilter_entry.grab_focus() - if self.rfilter_enabled: - self.rfilter_entry.set_text(self.rfilter_entry.get_text() + \ - search_string) - else: - self.rfilter_enabled = True - self.rfilter_entry.set_text(search_string) - self.tree.expand_all() - self.rfilter_entry.set_position(-1) - - # If roster is hidden, let's temporarily show it. This can happen if user - # enables rfilter via keyboard shortcut. - self.show_roster_vbox(True) - - def disable_rfilter(self): - self.rfilter_enabled = False - self.rfilter_entry.set_text('') - self.rfilter_entry.set_visible(False) - self.rfilter_entry.set_editable(False) - self.refilter_shown_roster_items() - self.tree.grab_focus() - self._readjust_expand_collapse_state() - - # If roster was hidden before enable_rfilter was called, hide it back. - state = self.window.lookup_action('show-roster').get_state().get_boolean() - if state is False and self.hpaned.get_child2() is not None: - self.show_roster_vbox(False) - - def on_roster_hpaned_notify(self, pane, gparamspec): - """ - Keep changing the width of the roster - (when a Gtk.Paned widget handle is dragged) - """ - if gparamspec and gparamspec.name == 'position': - roster_width = pane.get_child1().get_allocation().width - app.settings.set('roster_width', roster_width) - app.settings.set('roster_hpaned_position', pane.get_position()) - -################################################################################ -### Drag and Drop handling -################################################################################ - - def drag_data_get_data(self, treeview, context, selection, target_id, - etime): - model, list_of_paths = self.tree.get_selection().get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - data = '' - if path.get_depth() >= 2: - data = model[path][Column.JID] - selection.set_text(data, -1) - - def drag_begin(self, treeview, context): - self.dragging = True - - def drag_end(self, treeview, context): - self.dragging = False - - def on_drop_rosterx(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): - type_ = 'message' - if (c_dest.show not in ('offline', 'error') and - c_dest.supports(Namespace.ROSTERX)): - type_ = 'iq' - con = app.connections[account_dest] - con.get_module('RosterItemExchange').send_contacts( - [c_source], c_dest.get_full_jid(), type_=type_) - - def on_drop_in_contact(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): - con_source = app.connections[account_source] - con_dest = app.connections[account_dest] - if (not con_source.get_module('MetaContacts').available or - not con_dest.get_module('MetaContacts').available): - return - - def merge_contacts(is_checked=None): - contacts = 0 - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - app.settings.set('confirm_metacontacts', 'no') - else: - app.settings.set('confirm_metacontacts', 'yes') - - # We might have dropped on a metacontact. - # Remove it and add it again later with updated family info - dest_family = app.contacts.get_metacontacts_family(account_dest, - c_dest.jid) - if dest_family: - self._remove_metacontact_family(dest_family, account_dest) - source_family = app.contacts.get_metacontacts_family( - account_source, c_source.jid) - if dest_family == source_family: - n = contacts = len(dest_family) - for tag in source_family: - if tag['jid'] == c_source.jid: - tag['order'] = contacts - continue - if 'order' in tag: - n -= 1 - tag['order'] = n - else: - self._remove_entity(c_dest, account_dest) - - old_family = app.contacts.get_metacontacts_family(account_source, - c_source.jid) - old_groups = c_source.groups - - # Remove old source contact(s) - if was_big_brother: - # We have got little brothers. Add them all back - self._remove_metacontact_family(old_family, account_source) - else: - # We are only a little brother. Simply remove us from our big - # brother - if self._get_contact_iter(c_source.jid, account_source): - # When we have been in the group before. - # Do not try to remove us again - self._remove_entity(c_source, account_source) - - own_data = {} - own_data['jid'] = c_source.jid - own_data['account'] = account_source - # Don't touch the rest of the family - old_family = [own_data] - - # Apply new tag and update contact - for data in old_family: - if account_source != data['account'] and not self.regroup: - continue - - _account = data['account'] - _jid = data['jid'] - _contact = app.contacts.get_first_contact_from_jid(_account, - _jid) - if not _contact: - # One of the metacontacts may be not connected. - continue - - _contact.groups = c_dest.groups[:] - app.contacts.add_metacontact(account_dest, c_dest.jid, - _account, _contact.jid, contacts) - con = app.connections[account_source] - con.get_module('Roster').update_contact( - _contact.jid, _contact.name, _contact.groups) - - # Re-add all and update GUI - new_family = app.contacts.get_metacontacts_family(account_source, - c_source.jid) - brothers = self._add_metacontact_family(new_family, account_source) - - for c, acc in brothers: - self.draw_completely(c.jid, acc) - - old_groups.extend(c_dest.groups) - for g in old_groups: - self.draw_group(g, account_source) - - self.draw_account(account_source) - context.finish(True, True, etime) - - dest_family = app.contacts.get_metacontacts_family(account_dest, - c_dest.jid) - source_family = app.contacts.get_metacontacts_family(account_source, - c_source.jid) - confirm_metacontacts = app.settings.get('confirm_metacontacts') - if confirm_metacontacts == 'no' or dest_family == source_family: - merge_contacts() - return - pritext = _('You are about to create a metacontact') - sectext = _('Metacontacts are a way to regroup several contacts in ' - 'one single contact. Generally it is used when the same ' - 'person has several XMPP- or Transport-Accounts.') - ConfirmationCheckDialog( - _('Create Metacontact'), - pritext, - sectext, - _('_Do not ask me again'), - [DialogButton.make('Cancel'), - DialogButton.make('Accept', - text=_('_Create'), - callback=merge_contacts)]).show() - - def on_drop_in_group(self, widget, account, c_source, grp_dest, - is_big_brother, context, etime, grp_source=None): - if is_big_brother: - # add whole metacontact to new group - self.add_contact_to_groups(c_source.jid, account, [grp_dest, ]) - # remove afterwards so the contact is not moved to General in the - # meantime - if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, - [grp_source]) - else: - # Normal contact or little brother - family = app.contacts.get_metacontacts_family(account, - c_source.jid) - if family: - # Little brother - # Remove whole family. Remove us from the family. - # Then re-add other family members. - self._remove_metacontact_family(family, account) - app.contacts.remove_metacontact(account, c_source.jid) - for data in family: - if account != data['account'] and not self.regroup: - continue - if data['jid'] == c_source.jid and\ - data['account'] == account: - continue - self.add_contact(data['jid'], data['account']) - break - - self.add_contact_to_groups(c_source.jid, account, [grp_dest, ]) - - else: - # Normal contact - self.add_contact_to_groups(c_source.jid, account, [grp_dest, ]) - # remove afterwards so the contact is not moved to General in - # the meantime - if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, - [grp_source]) - - if context.get_actions() in (Gdk.DragAction.MOVE, Gdk.DragAction.COPY): - context.finish(True, True, etime) - - def drag_drop(self, treeview, context, x, y, timestamp): - treeview.stop_emission_by_name('drag-drop') - target_list = treeview.drag_dest_get_target_list() - target = treeview.drag_dest_find_target(context, target_list) - treeview.drag_get_data(context, target, timestamp) - return True - - def move_group(self, old_name, new_name, account): - for group in list(app.groups[account].keys()): - if group.startswith(old_name): - self.rename_group(group, group.replace(old_name, new_name), - account) - - def drag_data_received_data(self, treeview, context, x, y, selection, info, - etime): - treeview.stop_emission_by_name('drag-data-received') - drop_info = treeview.get_dest_row_at_pos(x, y) - if not drop_info: - return - data = selection.get_data().decode() - if not data: - return # prevents tb when several entries are dragged - model = treeview.get_model() - - path_dest, position = drop_info - - if position == Gtk.TreeViewDropPosition.BEFORE and len(path_dest) == 2 \ - and path_dest[1] == 0: # dropped before the first group - return - if position == Gtk.TreeViewDropPosition.BEFORE and len(path_dest) == 2: - # dropped before a group: we drop it in the previous group every - # time - path_dest = (path_dest[0], path_dest[1]-1) - # destination: the row something got dropped on - iter_dest = model.get_iter(path_dest) - type_dest = model[iter_dest][Column.TYPE] - jid_dest = model[iter_dest][Column.JID] - account_dest = model[iter_dest][Column.ACCOUNT] - - # drop on account row in merged mode, we cannot know the desired account - if account_dest == 'all': - return - # nothing can be done, if destination account is offline - if not app.account_is_available(account_dest): - return - - # A file got dropped on the roster - if info == self.TARGET_TYPE_URI_LIST: - if len(path_dest) < 3: - return - if type_dest != 'contact': - return - c_dest = app.contacts.get_contact_with_highest_priority( - account_dest, jid_dest) - if not c_dest.supports(Namespace.JINGLE_FILE_TRANSFER_5): - return - uri = data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - try: - # This is always the last element in windows - uri_splitted.remove('\0') - except ValueError: - pass - nb_uri = len(uri_splitted) - # Check the URIs - bad_uris = [] - for a_uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) - if not os.path.isfile(path): - bad_uris.append(a_uri) - if bad_uris: - ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) - return - def _on_send_files(account, jid, uris): - c = app.contacts.get_contact_with_highest_priority(account, - jid) - for uri in uris: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - app.interface.instances['file_transfers'].send_file( - account, c, path) - # Popup dialog to confirm sending - text = i18n.ngettext( - 'Send this file to %s:\n', - 'Send these files to %s:\n', - nb_uri) % c_dest.get_shown_name() - - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - text += '\n' + os.path.basename(path) - ConfirmationDialog( - _('File Transfer'), - _('File Transfer'), - text, - [DialogButton.make('Cancel'), - DialogButton.make('Accept', - text=_('_Send'), - callback=_on_send_files, - args=(account_dest, jid_dest, uri_splitted))], - transient_for=self.window).show() - return - - # Check if something is selected - if treeview.get_selection().count_selected_rows() == 0: - return - - # a roster entry was dragged and dropped somewhere in the roster - - # source: the row that was dragged - path_source = treeview.get_selection().get_selected_rows()[1][0] - iter_source = model.get_iter(path_source) - type_source = model[iter_source][Column.TYPE] - account_source = model[iter_source][Column.ACCOUNT] - - if app.settings.get_account_setting(account_source, 'is_zeroconf'): - return - - if type_dest == 'self_contact': - # drop on self contact row - return - - if type_dest == 'groupchat': - # Drop on a minimized groupchat - if type_source != 'contact': - return - contact_jid = data - gc_control = app.get_groupchat_control(account_dest, jid_dest) - if gc_control is not None: - gc_control.invite(contact_jid) - return - - if type_source == 'group': - if account_source != account_dest: - # drop on another account - return - grp_source = model[iter_source][Column.JID] - delimiter = app.connections[account_source].get_module('Delimiter').delimiter - grp_source_list = grp_source.split(delimiter) - new_grp = None - if type_dest == 'account': - new_grp = grp_source_list[-1] - elif type_dest == 'group': - grp_dest = model[iter_dest][Column.JID] - # Don't allow to drop on e.g. Groupchats group - if grp_dest in helpers.special_groups: - return - grp_dest_list = grp_dest.split(delimiter) - # Do not allow to drop on a subgroup of source group - if grp_source_list[0] != grp_dest_list[0]: - new_grp = model[iter_dest][Column.JID] + delimiter + \ - grp_source_list[-1] - if new_grp: - self.move_group(grp_source, new_grp, account_source) - - # Only normal contacts and group can be dragged - if type_source != 'contact': - return - - # A contact was dropped - if app.settings.get_account_setting(account_dest, 'is_zeroconf'): - # drop on zeroconf account, adding not possible - return - - if type_dest == 'account' and account_source == account_dest: - # drop on the account it was dragged from - return - - # Get valid source group, jid and contact - it = iter_source - while model[it][Column.TYPE] == 'contact': - it = model.iter_parent(it) - grp_source = model[it][Column.JID] - if grp_source in (_('Transports'), _('Group chats')): - # a transport or a minimized groupchat was dragged - # we can add it to other accounts but not move it to another group, - # see below - return - jid_source = data - c_source = app.contacts.get_contact_with_highest_priority( - account_source, jid_source) - - # Get destination group - grp_dest = None - if type_dest == 'group': - grp_dest = model[iter_dest][Column.JID] - elif type_dest in ('contact', 'agent'): - it = iter_dest - while model[it][Column.TYPE] != 'group': - it = model.iter_parent(it) - grp_dest = model[it][Column.JID] - if grp_dest in helpers.special_groups: - return - - if jid_source == jid_dest: - if grp_source == grp_dest and account_source == account_dest: - # Drop on self - return - - # contact drop somewhere in or on a foreign account - if (type_dest == 'account' or not self.regroup) and \ - account_source != account_dest: - # add to account in specified group - AddNewContactWindow(account=account_dest, contact_jid=jid_source, - user_nick=c_source.name, group=grp_dest) - return - - # we may not add contacts from special_groups - if grp_source in helpers.special_groups: - if grp_source == _('Not in contact list'): - AddNewContactWindow( - account=account_dest, - contact_jid=jid_source, - user_nick=c_source.name, - group=grp_dest) - return - return - - # Is the contact we drag a meta contact? - accounts = account_source - if self.regroup: - accounts = app.settings.get_active_accounts() or account_source - is_big_brother = app.contacts.is_big_brother(account_source, - jid_source, accounts) - - drop_in_middle_of_meta = False - if type_dest == 'contact': - if position == Gtk.TreeViewDropPosition.BEFORE and len(path_dest) == 4: - drop_in_middle_of_meta = True - if position == Gtk.TreeViewDropPosition.AFTER and (len(path_dest) == 4 or\ - self.modelfilter.iter_has_child(iter_dest)): - drop_in_middle_of_meta = True - # Contact drop on group row or between two contacts that are - # not metacontacts - if (type_dest == 'group' or position in (Gtk.TreeViewDropPosition.BEFORE, - Gtk.TreeViewDropPosition.AFTER)) and not drop_in_middle_of_meta: - self.on_drop_in_group(None, account_source, c_source, grp_dest, - is_big_brother, context, etime, grp_source) - return - - # Contact drop on another contact, make meta contacts - if position == Gtk.TreeViewDropPosition.INTO_OR_AFTER or \ - position == Gtk.TreeViewDropPosition.INTO_OR_BEFORE or drop_in_middle_of_meta: - c_dest = app.contacts.get_contact_with_highest_priority( - account_dest, jid_dest) - if not c_dest: - # c_dest is None if jid_dest doesn't belong to account - return - menu = Gtk.Menu() - #from and to are the names of contacts - item = Gtk.MenuItem.new_with_label(_('Send %(from)s to %(to)s') % { - 'from': c_source.get_shown_name(), 'to': c_dest.get_shown_name()}) - item.set_use_underline(False) - item.connect('activate', self.on_drop_rosterx, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) - menu.append(item) - - dest_family = app.contacts.get_metacontacts_family(account_dest, - c_dest.jid) - source_family = app.contacts.get_metacontacts_family( - account_source, c_source.jid) - if dest_family == source_family and dest_family: - item = Gtk.MenuItem.new_with_label( - _('Make %s first contact') % ( - c_source.get_shown_name())) - item.set_use_underline(False) - else: - item = Gtk.MenuItem.new_with_label( - _('Make %(contact1)s and %(contact2)s metacontacts') % { - 'contact1': c_source.get_shown_name(), 'contact2': c_dest.get_shown_name()}) - item.set_use_underline(False) - - item.connect('activate', self.on_drop_in_contact, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) - - menu.append(item) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup_at_pointer(None) - -################################################################################ -### Everything about images and icons.... -### Cleanup assigned to Jim++ :-) -################################################################################ - - def update_icons(self): - # Update the roster - self.setup_and_draw_roster() - - # Update the systray - if app.interface.systray_enabled: - app.interface.systray.set_img() - app.interface.systray.change_status(helpers.get_global_show()) - - for win in app.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - ctrl.update_ui() - win.redraw_tab(ctrl) - - self._status_selector.update() - - - def set_account_status_icon(self, account): - child_iterA = self._get_account_iter(account, self.model) - if not child_iterA: - return - if not self.regroup: - status = helpers.get_connection_status(account) - else: # accounts merged - status = helpers.get_global_show() - self.model[child_iterA][Column.IMG] = get_icon_name(status) - -################################################################################ -### Style and theme related methods -################################################################################ - - def show_title(self): - change_title_allowed = app.settings.get('change_roster_title') - if not change_title_allowed: - return - - nb_unread = 0 - for account in app.connections: - # Count events in roster title only if we don't auto open them - if not helpers.allow_popup_window(account): - nb_unread += app.events.get_nb_events(['chat', 'normal', - 'file-request', 'file-error', 'file-completed', - 'file-request-error', 'file-send-error', 'file-stopped', - 'printed_chat'], account) - - - if app.settings.get('one_message_window') == 'always_with_roster': - # always_with_roster mode defers to the MessageWindow - if not app.interface.msg_win_mgr.one_window_opened(): - # No MessageWindow to defer to - self.window.set_title('Gajim') - set_urgency_hint(self.window, nb_unread > 0) - return - - start = '' - if nb_unread > 1: - start = '[' + str(nb_unread) + '] ' - elif nb_unread == 1: - start = '* ' - - self.window.set_title(start + 'Gajim') - set_urgency_hint(self.window, nb_unread > 0) - - def _nec_chatstate_received(self, event): - if event.contact.is_gc_contact or event.contact.is_pm_contact: - return - self.draw_contact(event.contact.jid, event.account) - - def _style_changed(self, *args): - self.change_roster_style(None) - - def _change_style(self, model, path, titer, option): - if option is None or model[titer][Column.TYPE] == option: - # We changed style for this type of row - model[titer][Column.NAME] = model[titer][Column.NAME] - - def change_roster_style(self, option): - self.model.foreach(self._change_style, option) - for win in app.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() - - def repaint_themed_widgets(self): - """ - Notify windows that contain themed widgets to repaint them - """ - for win in app.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() - for account in app.connections: - for ctrl in list(app.interface.minimized_controls[account].values()): - ctrl.repaint_themed_widgets() - - def _iconCellDataFunc(self, column, renderer, model, titer, data=None): - """ - When a row is added, set properties for icon renderer - """ - icon_name = model[titer][Column.IMG] - if ':' in icon_name: - icon_name, expanded = icon_name.split(':') - surface = get_metacontact_surface( - icon_name, expanded == 'opened', self.scale_factor) - renderer.set_property('icon_name', None) - renderer.set_property('surface', surface) - else: - renderer.set_property('surface', None) - renderer.set_property('icon_name', icon_name) - - try: - type_ = model[titer][Column.TYPE] - except TypeError: - return - if type_ == 'account': - self._set_account_row_background_color(renderer) - renderer.set_property('xalign', 0) - elif type_ == 'group': - self._set_group_row_background_color(renderer) - parent_iter = model.iter_parent(titer) - if model[parent_iter][Column.TYPE] == 'group': - renderer.set_property('xalign', 0.4) - else: - renderer.set_property('xalign', 0.6) - elif type_: - # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - self._set_contact_row_background_color(renderer, jid, account) - parent_iter = model.iter_parent(titer) - if model[parent_iter][Column.TYPE] == 'contact': - renderer.set_property('xalign', 1) - else: - renderer.set_property('xalign', 0.6) - renderer.set_property('width', 26) - - def _nameCellDataFunc(self, column, renderer, model, titer, data=None): - """ - When a row is added, set properties for name renderer - """ - try: - type_ = model[titer][Column.TYPE] - except TypeError: - return - - if type_ == 'account': - color = app.css_config.get_value('.gajim-account-row', StyleAttr.COLOR) - renderer.set_property('foreground', color) - desc = app.css_config.get_font('.gajim-account-row') - renderer.set_property('font-desc', desc) - renderer.set_property('xpad', 0) - renderer.set_property('width', 3) - self._set_account_row_background_color(renderer) - elif type_ == 'group': - color = app.css_config.get_value('.gajim-group-row', StyleAttr.COLOR) - renderer.set_property('foreground', color) - desc = app.css_config.get_font('.gajim-group-row') - renderer.set_property('font-desc', desc) - parent_iter = model.iter_parent(titer) - if model[parent_iter][Column.TYPE] == 'group': - renderer.set_property('xpad', 8) - else: - renderer.set_property('xpad', 4) - self._set_group_row_background_color(renderer) - elif type_: - # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - - color = None - if type_ == 'groupchat': - ctrl = app.interface.minimized_controls[account].get(jid, None) - if ctrl and ctrl.attention_flag: - color = app.css_config.get_value( - '.state_muc_directed_msg_color', StyleAttr.COLOR) - elif app.settings.get('show_chatstate_in_roster'): - chatstate = app.contacts.get_combined_chatstate(account, jid) - if chatstate not in (None, 'active'): - color = app.css_config.get_value( - '.gajim-state-%s' % chatstate, StyleAttr.COLOR) - else: - color = app.css_config.get_value( - '.gajim-contact-row', StyleAttr.COLOR) - renderer.set_property('foreground', color) - - self._set_contact_row_background_color(renderer, jid, account) - desc = app.css_config.get_font('.gajim-contact-row') - renderer.set_property('font-desc', desc) - parent_iter = model.iter_parent(titer) - if model[parent_iter][Column.TYPE] == 'contact': - renderer.set_property('xpad', 16) - else: - renderer.set_property('xpad', 12) - - def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer, - data=None): - """ - When a row is added, draw the respective pep icon - """ - try: - type_ = model[titer][Column.TYPE] - except TypeError: - return - - # allocate space for the icon only if needed - if model[titer][data] is None: - renderer.set_property('visible', False) - else: - renderer.set_property('visible', True) - - if type_ == 'account': - self._set_account_row_background_color(renderer) - renderer.set_property('xalign', 1) - elif type_: - if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - self._set_contact_row_background_color(renderer, jid, account) - - def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, - data=None): - """ - When a row is added, set properties for avatar renderer - """ - try: - type_ = model[titer][Column.TYPE] - except TypeError: - return - - if type_ in ('group', 'account'): - renderer.set_property('visible', False) - return - - image = model[titer][Column.AVATAR_IMG] - if image is not None: - surface = image.get_property('surface') - renderer.set_property('surface', surface) - # allocate space for the icon only if needed - if model[titer][Column.AVATAR_IMG] or \ - app.settings.get('avatar_position_in_roster') == 'left': - renderer.set_property('visible', True) - if type_: - # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - self._set_contact_row_background_color(renderer, jid, account) - else: - renderer.set_property('visible', False) - if model[titer][Column.AVATAR_IMG] is None and \ - app.settings.get('avatar_position_in_roster') != 'left': - renderer.set_property('visible', False) - - renderer.set_property('width', AvatarSize.ROSTER) - renderer.set_property('xalign', 0.5) - - def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, - data=None): - """ - When a row is added, set properties for padlock renderer - """ - try: - type_ = model[titer][Column.TYPE] - except TypeError: - return - - # allocate space for the icon only if needed - if type_ == 'account' and model[titer][Column.PADLOCK_PIXBUF]: - renderer.set_property('visible', True) - self._set_account_row_background_color(renderer) - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('visible', False) - - def _set_account_row_background_color(self, renderer): - color = app.css_config.get_value('.gajim-account-row', StyleAttr.BACKGROUND) - renderer.set_property('cell-background', color) - - def _set_contact_row_background_color(self, renderer, jid, account): - if jid in app.newly_added[account]: - renderer.set_property('cell-background', app.css_config.get_value( - '.gajim-roster-connected', StyleAttr.BACKGROUND)) - elif jid in app.to_be_removed[account]: - renderer.set_property('cell-background', app.css_config.get_value( - '.gajim-roster-disconnected', StyleAttr.BACKGROUND)) - else: - color = app.css_config.get_value('.gajim-contact-row', StyleAttr.BACKGROUND) - renderer.set_property('cell-background', color) - - def _set_group_row_background_color(self, renderer): - color = app.css_config.get_value('.gajim-group-row', 'background') - renderer.set_property('cell-background', color) - -################################################################################ -### Everything about building menus -### FIXME: We really need to make it simpler! 1465 lines are a few to much.... -################################################################################ - - def build_account_menu(self, account): - # we have to create our own set of icons for the menu - # using self.jabber_status_images is poopoo - if not app.settings.get_account_setting(account, 'is_zeroconf'): - xml = get_builder('account_context_menu.ui') - account_context_menu = xml.get_object('account_context_menu') - - status_menuitem = xml.get_object('status_menuitem') - add_contact_menuitem = xml.get_object('add_contact_menuitem') - service_discovery_menuitem = xml.get_object( - 'service_discovery_menuitem') - execute_command_menuitem = xml.get_object( - 'execute_command_menuitem') - view_server_info_menuitem = xml.get_object( - 'view_server_info_menuitem') - edit_account_menuitem = xml.get_object('edit_account_menuitem') - sub_menu = Gtk.Menu() - status_menuitem.set_submenu(sub_menu) - - for show in ('online', 'away', 'xa', 'dnd'): - uf_show = helpers.get_uf_show(show, use_mnemonic=True) - item = Gtk.MenuItem.new_with_mnemonic(uf_show) - sub_menu.append(item) - item.connect('activate', self.change_status, account, show) - - item = Gtk.SeparatorMenuItem.new() - sub_menu.append(item) - - item = Gtk.MenuItem.new_with_mnemonic(_('_Change Status Message')) - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate, - account) - if not app.account_is_available(account): - item.set_sensitive(False) - - item = Gtk.SeparatorMenuItem.new() - sub_menu.append(item) - - uf_show = helpers.get_uf_show('offline', use_mnemonic=True) - item = Gtk.MenuItem.new_with_mnemonic(uf_show) - sub_menu.append(item) - item.connect('activate', self.change_status, account, 'offline') - - pep_menuitem = xml.get_object('pep_menuitem') - if app.connections[account].get_module('PEP').supported: - pep_submenu = Gtk.Menu() - pep_menuitem.set_submenu(pep_submenu) - - item = Gtk.CheckMenuItem(label=_('Publish Tune')) - pep_submenu.append(item) - if sys.platform in ('win32', 'darwin'): - item.set_sensitive(False) - else: - active = app.settings.get_account_setting(account, - 'publish_tune') - item.set_active(active) - item.connect('toggled', self.on_publish_tune_toggled, - account) - - item = Gtk.CheckMenuItem(label=_('Publish Location')) - pep_submenu.append(item) - if not app.is_installed('GEOCLUE'): - item.set_sensitive(False) - else: - active = app.settings.get_account_setting( - account, 'publish_location') - item.set_active(active) - item.connect('toggled', self.on_publish_location_toggled, - account) - - else: - pep_menuitem.set_sensitive(False) - - edit_account_menuitem.set_detailed_action_name( - 'app.accounts::%s' % account) - if app.connections[account].roster_supported: - add_contact_menuitem.connect('activate', - self.on_add_new_contact, account) - else: - add_contact_menuitem.set_sensitive(False) - service_discovery_menuitem.connect('activate', - self.on_service_disco_menuitem_activate, account) - hostname = app.settings.get_account_setting(account, 'hostname') - contact = app.contacts.create_contact(jid=hostname, - account=account) # Fake contact - execute_command_menuitem.connect('activate', - self.on_execute_command, contact, account) - view_server_info_menuitem.connect('activate', - self.on_view_server_info, account) - - # make some items insensitive if account is offline - if not app.account_is_available(account): - for widget in (add_contact_menuitem, service_discovery_menuitem, - execute_command_menuitem, view_server_info_menuitem, - pep_menuitem): - widget.set_sensitive(False) - else: - xml = get_builder('zeroconf_context_menu.ui') - account_context_menu = xml.get_object('zeroconf_context_menu') - - status_menuitem = xml.get_object('status_menuitem') - zeroconf_properties_menuitem = xml.get_object( - 'zeroconf_properties_menuitem') - sub_menu = Gtk.Menu() - status_menuitem.set_submenu(sub_menu) - - for show in ('online', 'away', 'dnd'): - uf_show = helpers.get_uf_show(show, use_mnemonic=True) - item = Gtk.MenuItem.new_with_mnemonic(uf_show) - sub_menu.append(item) - item.connect('activate', self.change_status, account, show) - - item = Gtk.SeparatorMenuItem.new() - sub_menu.append(item) - - item = Gtk.MenuItem.new_with_mnemonic(_('_Change Status Message')) - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate, - account) - if not app.account_is_available(account): - item.set_sensitive(False) - - uf_show = helpers.get_uf_show('offline', use_mnemonic=True) - item = Gtk.MenuItem.new_with_mnemonic(uf_show) - sub_menu.append(item) - item.connect('activate', self.change_status, account, 'offline') - - zeroconf_properties_menuitem.set_detailed_action_name( - 'app.accounts::%s' % account) - - return account_context_menu - - def make_account_menu(self, event, titer): - """ - Make account's popup menu - """ - model = self.modelfilter - account = model[titer][Column.ACCOUNT] - - if account != 'all': # not in merged mode - menu = self.build_account_menu(account) - else: - menu = Gtk.Menu() - accounts = [] # Put accounts in a list to sort them - for account in app.connections: - accounts.append(account) - accounts.sort() - for account in accounts: - label = app.get_account_label(account) - item = Gtk.MenuItem.new_with_label(label) - account_menu = self.build_account_menu(account) - item.set_submenu(account_menu) - menu.append(item) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, None, event_button, event.time) - - def make_group_menu(self, event, iters): - """ - Make group's popup menu - """ - model = self.modelfilter - groups = [] - accounts = [] - - list_ = [] # list of (contact, account) tuples - list_online = [] # list of (contact, account) tuples - - for titer in iters: - groups.append(model[titer][Column.JID]) - accounts.append(model[titer][Column.ACCOUNT]) - # Don't show menu if groups of more than one account are selected - if accounts[0] != model[titer][Column.ACCOUNT]: - return - account = accounts[0] - - show_bookmarked = True - for jid in app.contacts.get_jid_list(account): - contact = app.contacts.get_contact_with_highest_priority(account, - jid) - for group in groups: - if group in contact.get_shown_groups(): - if contact.show not in ('offline', 'error'): - list_online.append((contact, account)) - # Check that all contacts support direct NUC invite - if not contact.supports(Namespace.CONFERENCE): - show_bookmarked = False - list_.append((contact, account)) - menu = Gtk.Menu() - - # Make special context menu if group is Groupchats - if _('Group chats') in groups: - if len(groups) == 1: - maximize_menuitem = Gtk.MenuItem.new_with_mnemonic( - _('_Maximize All')) - maximize_menuitem.connect('activate', - self.on_all_groupchat_maximized, list_) - menu.append(maximize_menuitem) - else: - return - else: - # Send Group Message - send_group_message_item = Gtk.MenuItem.new_with_mnemonic( - _('Send Group M_essage')) - - send_group_message_submenu = Gtk.Menu() - send_group_message_item.set_submenu(send_group_message_submenu) - menu.append(send_group_message_item) - - group_message_to_all_item = Gtk.MenuItem.new_with_label(_( - 'To all users')) - send_group_message_submenu.append(group_message_to_all_item) - - group_message_to_all_online_item = Gtk.MenuItem.new_with_label( - _('To all online users')) - send_group_message_submenu.append(group_message_to_all_online_item) - - group_message_to_all_online_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, - list_online) - group_message_to_all_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to - invite_menuitem = Gtk.MenuItem.new_with_mnemonic( - _('In_vite to')) - if _('Transports') not in groups: - gui_menu_builder.build_invite_submenu(invite_menuitem, - list_online, show_bookmarked=show_bookmarked) - menu.append(invite_menuitem) - - # there is no singlemessage and custom status for zeroconf - if app.settings.get_account_setting(account, 'is_zeroconf'): - send_group_message_item.set_sensitive(False) - - if not app.account_is_available(account): - send_group_message_item.set_sensitive(False) - invite_menuitem.set_sensitive(False) - - special_group = False - for group in groups: - if group in helpers.special_groups: - special_group = True - break - - if not special_group and len(groups) == 1: - group = groups[0] - item = Gtk.SeparatorMenuItem.new() # separator - menu.append(item) - - # Rename - rename_item = Gtk.MenuItem.new_with_mnemonic(_('_Rename…')) - menu.append(rename_item) - rename_item.connect('activate', self.on_rename, 'group', group, - account) - - # Remove group - remove_item = Gtk.MenuItem.new_with_mnemonic(_('Remo_ve')) - menu.append(remove_item) - remove_item.connect('activate', self.on_remove_group_item_activated, - group, account) - - # unsensitive if account is not connected - if not app.account_is_available(account): - rename_item.set_sensitive(False) - - # General group cannot be changed - if group == _('General'): - rename_item.set_sensitive(False) - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, None, event_button, event.time) - - def make_contact_menu(self, event, titer): - """ - Make contact's popup menu - """ - model = self.modelfilter - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - contact = app.contacts.get_contact_with_highest_priority(account, jid) - menu = gui_menu_builder.get_contact_menu(contact, account) - event_button = gtkgui_helpers.get_possible_button_event(event) - menu.attach_to_widget(self.tree, None) - menu.popup(None, None, None, None, event_button, event.time) - - def make_multiple_contact_menu(self, event, iters): - """ - Make group's popup menu - """ - model = self.modelfilter - list_ = [] # list of (jid, account) tuples - one_account_offline = False - is_blocked = True - blocking_supported = True - for titer in iters: - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - if not app.account_is_available(account): - one_account_offline = True - - con = app.connections[account] - if not con.get_module('Blocking').supported: - blocking_supported = False - contact = app.contacts.get_contact_with_highest_priority( - account, jid) - if not helpers.jid_is_blocked(account, jid): - is_blocked = False - list_.append((contact, account)) - - menu = Gtk.Menu() - account = None - for (contact, current_account) in list_: - # check that we use the same account for every sender - if account is not None and account != current_account: - account = None - break - account = current_account - show_bookmarked = True - for (contact, current_account) in list_: - # Check that all contacts support direct NUC invite - if not contact.supports(Namespace.CONFERENCE): - show_bookmarked = False - break - if account is not None: - send_group_message_item = Gtk.MenuItem.new_with_mnemonic( - _('Send Group M_essage')) - menu.append(send_group_message_item) - send_group_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to Groupchat - invite_item = Gtk.MenuItem.new_with_mnemonic(_('In_vite to')) - - gui_menu_builder.build_invite_submenu(invite_item, list_, - show_bookmarked=show_bookmarked) - menu.append(invite_item) - - item = Gtk.SeparatorMenuItem.new() # separator - menu.append(item) - - # Manage Transport submenu - item = Gtk.MenuItem.new_with_mnemonic(_('_Manage Contacts')) - manage_contacts_submenu = Gtk.Menu() - item.set_submenu(manage_contacts_submenu) - menu.append(item) - - # Edit Groups - edit_groups_item = Gtk.MenuItem.new_with_mnemonic(_('Edit _Groups…')) - manage_contacts_submenu.append(edit_groups_item) - edit_groups_item.connect('activate', self.on_edit_groups, list_) - - item = Gtk.SeparatorMenuItem.new() # separator - manage_contacts_submenu.append(item) - - # Block - if is_blocked and blocking_supported: - unblock_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Unblock')) - unblock_menuitem.connect('activate', self.on_unblock, list_) - manage_contacts_submenu.append(unblock_menuitem) - else: - block_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Block')) - block_menuitem.connect('activate', self.on_block, list_) - manage_contacts_submenu.append(block_menuitem) - - if not blocking_supported: - block_menuitem.set_sensitive(False) - - # Remove - remove_item = Gtk.MenuItem.new_with_mnemonic(_('_Remove')) - manage_contacts_submenu.append(remove_item) - remove_item.connect('activate', self.on_req_usub, list_) - # unsensitive remove if one account is not connected - if one_account_offline: - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, None, event_button, event.time) - - def make_transport_menu(self, event, titer): - """ - Make transport's popup menu - """ - model = self.modelfilter - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - contact = app.contacts.get_contact_with_highest_priority(account, jid) - menu = gui_menu_builder.get_transport_menu(contact, account) - event_button = gtkgui_helpers.get_possible_button_event(event) - menu.attach_to_widget(self.tree, None) - menu.popup(None, None, None, None, event_button, event.time) - - def make_groupchat_menu(self, event, titer): - model = self.modelfilter - - jid = model[titer][Column.JID] - account = model[titer][Column.ACCOUNT] - contact = app.contacts.get_contact_with_highest_priority(account, jid) - menu = Gtk.Menu() - - if jid in app.interface.minimized_controls[account]: - maximize_menuitem = Gtk.MenuItem.new_with_mnemonic(_( - '_Maximize')) - maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ - jid, account) - menu.append(maximize_menuitem) - - rename_menuitem = Gtk.MenuItem.new_with_mnemonic(_('Re_name')) - rename_menuitem.connect('activate', - self.on_groupchat_rename, - jid, - account) - menu.append(rename_menuitem) - - disconnect_menuitem = Gtk.MenuItem.new_with_mnemonic(_( - '_Leave')) - disconnect_menuitem.connect('activate', self.on_disconnect, jid, - account) - menu.append(disconnect_menuitem) - - item = Gtk.SeparatorMenuItem.new() # separator - menu.append(item) - - adhoc_menuitem = Gtk.MenuItem.new_with_mnemonic(_('Execute command')) - adhoc_menuitem.connect('activate', self.on_execute_command, contact, - account) - menu.append(adhoc_menuitem) - - item = Gtk.SeparatorMenuItem.new() # separator - menu.append(item) - - history_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_History')) - history_menuitem.set_action_name('app.browse-history') - dict_ = {'jid': GLib.Variant('s', contact.jid), - 'account': GLib.Variant('s', account)} - variant = GLib.Variant('a{sv}', dict_) - history_menuitem.set_action_target_value(variant) - - menu.append(history_menuitem) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, None, event_button, event.time) - - def show_appropriate_context_menu(self, event, iters): - # iters must be all of the same type - model = self.modelfilter - type_ = model[iters[0]][Column.TYPE] - for titer in iters[1:]: - if model[titer][Column.TYPE] != type_: - return - if type_ == 'group': - self.make_group_menu(event, iters) - if type_ == 'groupchat' and len(iters) == 1: - self.make_groupchat_menu(event, iters[0]) - elif type_ == 'agent' and len(iters) == 1: - self.make_transport_menu(event, iters[0]) - elif type_ in ('contact', 'self_contact') and len(iters) == 1: - self.make_contact_menu(event, iters[0]) - elif type_ == 'contact': - self.make_multiple_contact_menu(event, iters) - elif type_ == 'account' and len(iters) == 1: - self.make_account_menu(event, iters[0]) - - def show_treeview_menu(self, event): - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - self.tree.get_selection().unselect_all() - return - if not list_of_paths: - # no row is selected - return - if len(list_of_paths) > 1: - iters = [] - for path in list_of_paths: - iters.append(model.get_iter(path)) - else: - path = list_of_paths[0] - iters = [model.get_iter(path)] - self.show_appropriate_context_menu(event, iters) - - return True - - def fill_column(self, col): - for rend in self.renderers_list: - col.pack_start(rend[1], rend[2]) - if rend[0] != 'avatar': - col.add_attribute(rend[1], rend[3], rend[4]) - col.set_cell_data_func(rend[1], rend[5], rend[6]) - # set renderers properties - for renderer, props in self.renderers_propertys.items(): - renderer.set_property(props[0], props[1]) - - def query_tooltip(self, widget, x_pos, y_pos, _keyboard_mode, tooltip): - try: - path = widget.get_path_at_pos(x_pos, y_pos) - row = path[0] - col = path[1] - except TypeError: - self._roster_tooltip.clear_tooltip() - return False - if not row: - self._roster_tooltip.clear_tooltip() - return False - - iter_ = None - try: - model = widget.get_model() - iter_ = model.get_iter(row) - except Exception: - self._roster_tooltip.clear_tooltip() - return False - - typ = model[iter_][Column.TYPE] - account = model[iter_][Column.ACCOUNT] - jid = model[iter_][Column.JID] - connected_contacts = [] - - if typ == 'group': - if jid == _('Observers'): - widget.set_tooltip_cell(tooltip, row, col, None) - tooltip.set_text( - _('Observers can see your status, but you ' - 'are not allowed to see theirs')) - return True - return False - - if typ in ('contact', 'self_contact'): - contacts = app.contacts.get_contacts(account, jid) - - for contact in contacts: - if contact.show not in ('offline', 'error'): - connected_contacts.append(contact) - if not connected_contacts: - # no connected contacts, show the offline one - connected_contacts = contacts - elif typ == 'groupchat': - connected_contacts = app.contacts.get_contacts(account, jid) - elif typ != 'account': - return False - - value, widget = self._roster_tooltip.get_tooltip( - row, connected_contacts, account, typ) - tooltip.set_custom(widget) - return value - - def add_actions(self): - - actions = [ - ('show-roster', - not self.xml.get_object('roster_vbox2').get_no_show_all(), - self.on_show_roster_action), - - ('show-offline', - app.settings.get('showoffline'), - self.on_show_offline_contacts_action), - - ('show-active', - app.settings.get('show_only_chat_and_online'), - self.on_show_active_contacts_action), - - ('show-transports', - app.settings.get('show_transports_group'), - self.on_show_transports_action), - ] - - for action in actions: - action_name, variant, func = action - act = Gio.SimpleAction.new_stateful( - action_name, None, GLib.Variant.new_boolean(variant)) - act.connect('change-state', func) - self.window.add_action(act) - -################################################################################ -### -################################################################################ - - def __init__(self, application): - self.application = application - self.filtering = False - self.starting = False - self.starting_filtering = False - # Number of renderers plugins added - self.nb_ext_renderers = 0 - # When we quit, remember if we already saved config once - self.save_done = False - - # [icon, name, type, jid, account, editable, mood_pixbuf, - # activity_pixbuf, TUNE_ICON, LOCATION_ICON, avatar_img, - # padlock_pixbuf, visible] - self.columns = [str, str, str, str, str, str, str, str, str, - Gtk.Image, str, bool] - - self.xml = get_builder('roster_window.ui') - self.window = self.xml.get_object('roster_window') - application.add_window(self.window) - self.add_actions() - self.hpaned = self.xml.get_object('roster_hpaned') - - # app.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) - # app.interface.msg_win_mgr.connect('window-delete', - # self.on_message_window_delete) - - self.advanced_menus = [] # We keep them to destroy them - if app.settings.get('roster_window_skip_taskbar'): - self.window.set_property('skip-taskbar-hint', True) - self.tree = self.xml.get_object('roster_treeview') - sel = self.tree.get_selection() - sel.set_mode(Gtk.SelectionMode.MULTIPLE) - # sel.connect('changed', - # self.on_treeview_selection_changed) - - self._iters = {} - # for merged mode - self._iters['MERGED'] = {'account': None, 'groups': {}} - # holds a list of (jid, account) tuples - self._last_selected_contact = [] - self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} - - self.last_save_dir = None - self.editing_path = None # path of row with cell in edit mode - self.add_new_contact_handler_id = False - self.service_disco_handler_id = False - self.new_chat_menuitem_handler_id = False - self.single_message_menuitem_handler_id = False - self.profile_avatar_menuitem_handler_id = False - #FIXME: When list_accel_closures will be wrapped in pygtk - # no need of this variable - self.have_new_chat_accel = False # Is the "Ctrl+N" shown ? - self.regroup = app.settings.get('mergeaccounts') - self.clicked_path = None # Used remember on which row we clicked - if len(app.connections) < 2: - # Do not merge accounts if only one exists - self.regroup = False - resize_window(self.window, - app.settings.get('roster_width'), - app.settings.get('roster_height')) - restore_roster_position(self.window) - - # Remove contact from roster when last event opened - # { (contact, account): { backend: boolean } - self.contacts_to_be_removed = {} - app.events.event_removed_subscribe(self.on_event_removed) - - # when this value become 0 we quit main application. If it's more than 0 - # it means we are waiting for this number of accounts to disconnect - # before quitting - self.quit_on_next_offline = -1 - - # groups to draw next time we draw groups. - self.groups_to_draw = {} - # accounts to draw next time we draw accounts. - self.accounts_to_draw = [] - - # Status selector - self._status_selector = StatusSelector() - self.xml.roster_vbox2.add(self._status_selector) - - # Enable/Disable checkboxes at start - if app.settings.get('showoffline'): - self.window.lookup_action('show-active').set_enabled(False) - - if app.settings.get('show_only_chat_and_online'): - self.window.lookup_action('show-offline').set_enabled(False) - - if self.hpaned.get_child2() is None: - self.window.lookup_action('show-roster').set_enabled(False) - - # columns - col = Gtk.TreeViewColumn() - # list of renderers with attributes / properties in the form: - # (name, renderer_object, expand?, attribute_name, attribute_value, - # cell_data_func, func_arg) - self.renderers_list = [] - self.renderers_propertys = {} - - renderer_text = Gtk.CellRendererText() - self.renderers_propertys[renderer_text] = ('ellipsize', - Pango.EllipsizeMode.END) - - def add_avatar_renderer(): - self.renderers_list.append(('avatar', Gtk.CellRendererPixbuf(), - False, None, Column.AVATAR_IMG, - self._fill_avatar_pixbuf_renderer, None)) - - if app.settings.get('avatar_position_in_roster') == 'left': - add_avatar_renderer() - - self.renderers_list += ( - ('icon', Gtk.CellRendererPixbuf(), False, - 'icon_name', Column.IMG, self._iconCellDataFunc, None), - - ('name', renderer_text, True, - 'markup', Column.NAME, self._nameCellDataFunc, None), - - ('mood', Gtk.CellRendererPixbuf(), False, - 'icon_name', Column.MOOD_PIXBUF, - self._fill_pep_pixbuf_renderer, Column.MOOD_PIXBUF), - - ('activity', Gtk.CellRendererPixbuf(), False, - 'icon_name', Column.ACTIVITY_PIXBUF, - self._fill_pep_pixbuf_renderer, Column.ACTIVITY_PIXBUF), - - ('tune', Gtk.CellRendererPixbuf(), False, - 'icon_name', Column.TUNE_ICON, - self._fill_pep_pixbuf_renderer, Column.TUNE_ICON), - - ('geoloc', Gtk.CellRendererPixbuf(), False, - 'icon_name', Column.LOCATION_ICON, - self._fill_pep_pixbuf_renderer, Column.LOCATION_ICON)) - - if app.settings.get('avatar_position_in_roster') == 'right': - add_avatar_renderer() - - self.renderers_list.append(('padlock', Gtk.CellRendererPixbuf(), False, - 'icon_name', Column.PADLOCK_PIXBUF, - self._fill_padlock_pixbuf_renderer, None)) - - # fill and append column - self.fill_column(col) - self.tree.append_column(col) - - # do not show gtk arrows workaround - col = Gtk.TreeViewColumn() - render_pixbuf = Gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, False) - self.tree.append_column(col) - col.set_visible(False) - self.tree.set_expander_column(col) - - # Signals - # Drag - self.tree.enable_model_drag_source( - Gdk.ModifierType.BUTTON1_MASK, - [], - Gdk.DragAction.DEFAULT | - Gdk.DragAction.MOVE | - Gdk.DragAction.COPY) - self.tree.drag_source_add_text_targets() - - # Drop - self.tree.enable_model_drag_dest([], Gdk.DragAction.DEFAULT) - self.TARGET_TYPE_URI_LIST = 80 - uri_entry = Gtk.TargetEntry.new( - 'text/uri-list', - Gtk.TargetFlags.OTHER_APP, - self.TARGET_TYPE_URI_LIST) - dst_targets = Gtk.TargetList.new([uri_entry]) - dst_targets.add_text_targets(0) - self.tree.drag_dest_set_target_list(dst_targets) - - # Connect - self.tree.connect('drag-begin', self.drag_begin) - self.tree.connect('drag-end', self.drag_end) - self.tree.connect('drag-drop', self.drag_drop) - self.tree.connect('drag-data-get', self.drag_data_get_data) - self.tree.connect('drag-data-received', self.drag_data_received_data) - self.dragging = False - self.xml.connect_signals(self) - self.combobox_callback_active = True - - self.collapsed_rows = app.settings.get('collapsed_rows').split('\t') - self.tree.set_has_tooltip(True) - self._roster_tooltip = RosterTooltip() - self.tree.connect('query-tooltip', self.query_tooltip) - # Workaround: For strange reasons signal is behaving like row-changed - self._toggeling_row = False - self.setup_and_draw_roster() - - if app.settings.get('show_roster_on_startup') == 'always': - self.window.show_all() - elif app.settings.get('show_roster_on_startup') == 'never': - if app.settings.get('trayicon') != 'always': - # Without trayicon, user should see the roster! - self.window.show_all() - app.settings.set('last_roster_visible', True) - else: - if app.settings.get('last_roster_visible') or \ - app.settings.get('trayicon') != 'always': - self.window.show_all() - - self.scale_factor = self.window.get_scale_factor() - - accounts = app.settings.get_accounts() - - if (not accounts or - accounts == ['Local'] and - not app.settings.get_account_setting('Local', 'active')): - # if we have no account configured or only Local account but not enabled - def _open_wizard(): - open_window('AccountWizard') - - # Open wizard only after roster is created, so we can make it - # transient for the roster window - GLib.idle_add(_open_wizard) - - # Setting CTRL+S to be the shortcut to change status message - accel_group = Gtk.AccelGroup() - keyval, mod = Gtk.accelerator_parse('<Control>s') - accel_group.connect(keyval, mod, Gtk.AccelFlags.VISIBLE, - self.accel_group_func) - - # Setting CTRL+k to focus rfilter_entry - keyval, mod = Gtk.accelerator_parse('<Control>k') - accel_group.connect(keyval, mod, Gtk.AccelFlags.VISIBLE, - self.accel_group_func) - self.window.add_accel_group(accel_group) - - # Setting the search stuff - self.rfilter_entry = self.xml.get_object('rfilter_entry') - self.rfilter_string = '' - self.rfilter_enabled = False - self.rfilter_entry.connect('key-press-event', - self.on_rfilter_entry_key_press_event) - - app.ged.register_event_handler('presence-received', ged.GUI1, - self._nec_presence_received) - app.ged.register_event_handler('roster-received', ged.GUI1, - self._nec_roster_received) - app.ged.register_event_handler('anonymous-auth', ged.GUI1, - self._nec_anonymous_auth) - app.ged.register_event_handler('our-show', ged.GUI2, - self._nec_our_show) - app.ged.register_event_handler('connection-type', ged.GUI1, - self._nec_connection_type) - app.ged.register_event_handler('agent-removed', ged.GUI1, - self._nec_agent_removed) - app.ged.register_event_handler('nickname-received', ged.GUI1, - self._on_nickname_received) - app.ged.register_event_handler('mood-received', ged.GUI1, - self._on_mood_received) - app.ged.register_event_handler('activity-received', ged.GUI1, - self._on_activity_received) - app.ged.register_event_handler('tune-received', ged.GUI1, - self._on_tune_received) - app.ged.register_event_handler('location-received', ged.GUI1, - self._on_location_received) - app.ged.register_event_handler('update-roster-avatar', ged.GUI1, - self._nec_update_avatar) - app.ged.register_event_handler('update-room-avatar', ged.GUI1, - self._nec_update_avatar) - app.ged.register_event_handler('muc-subject', ged.GUI1, - self._nec_muc_subject_received) - app.ged.register_event_handler('metacontacts-received', ged.GUI2, - self._nec_metacontacts_received) - app.ged.register_event_handler('signed-in', ged.GUI1, - self._nec_signed_in) - app.ged.register_event_handler('decrypted-message-received', ged.GUI2, - self._nec_decrypted_message_received) - app.ged.register_event_handler('blocking', ged.GUI1, - self._nec_blocking) - app.ged.register_event_handler('style-changed', ged.GUI1, - self._style_changed) - app.ged.register_event_handler('chatstate-received', ged.GUI1, - self._nec_chatstate_received) - app.ged.register_event_handler('muc-disco-update', ged.GUI1, - self._on_muc_disco_update) - app.ged.register_event_handler('bookmarks-received', ged.GUI2, - self._on_bookmarks_received)