# -*- coding:utf-8 -*- ## src/vcard.py ## ## Copyright (C) 2003-2014 Yann Leboulanger ## Copyright (C) 2005 Vincent Hanquez ## Copyright (C) 2005-2006 Nikos Kouremenos ## Copyright (C) 2006 Junglecow J ## Dimitur Kirov ## Travis Shirk ## Stefan Bethge ## Copyright (C) 2006-2008 Jean-Marie Traissard ## Copyright (C) 2007 Lukas Petrovicky ## Copyright (C) 2008 Brendan Taylor ## Jonathan Schleifer ## Stephan Erb ## ## 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 . ## # THIS FILE IS FOR **OTHERS'** PROFILE (when we VIEW their INFO) from gi.repository import Gtk from gi.repository import GLib from gi.repository import Gdk from gi.repository import GdkPixbuf import base64 import binascii import os from gajim import gtkgui_helpers from gajim.common import helpers from gajim.common import app from gajim.common import ged from gajim.common import configpaths from gajim.common.i18n import Q_ from gajim.common.const import AvatarSize # log = logging.getLogger('gajim.vcard') class VcardWindow: """ Class for contact's information window """ def __init__(self, contact, account, gc_contact = None): # the contact variable is the jid if vcard is true self.xml = gtkgui_helpers.get_gtk_builder('vcard_information_window.ui') self.window = self.xml.get_object('vcard_information_window') self.progressbar = self.xml.get_object('progressbar') self.contact = contact self.account = account self.gc_contact = gc_contact self.avatar = None # Get real jid if gc_contact: # Don't use real jid if room is (semi-)anonymous gc_control = app.interface.msg_win_mgr.get_gc_control( gc_contact.room_jid, account) if gc_contact.jid and not gc_control.is_anonymous: self.real_jid = gc_contact.jid self.real_jid_for_vcard = gc_contact.jid if gc_contact.resource: self.real_jid += '/' + gc_contact.resource else: self.real_jid = gc_contact.get_full_jid() self.real_jid_for_vcard = self.real_jid self.real_resource = gc_contact.name else: self.real_jid = contact.get_full_jid() self.real_resource = contact.resource puny_jid = helpers.sanitize_filename(contact.jid) local_avatar_basepath = os.path.join(configpaths.get('AVATAR'), puny_jid) + \ '_local' for extension in ('.png', '.jpeg'): local_avatar_path = local_avatar_basepath + extension if os.path.isfile(local_avatar_path): image = self.xml.get_object('custom_avatar_image') image.set_from_file(local_avatar_path) image.show() self.xml.get_object('custom_avatar_label').show() break self.vcard_arrived = False self.os_info_arrived = False self.entity_time_arrived = False self.time = 0 self.update_intervall = 100 # Milliseconds self.update_progressbar_timeout_id = GLib.timeout_add(self.update_intervall, self.update_progressbar) app.ged.register_event_handler('version-result-received', ged.GUI1, self.set_os_info) app.ged.register_event_handler('time-result-received', ged.GUI1, self.set_entity_time) self.fill_jabber_page() con = app.connections[self.account] annotations = con.get_module('Annotations').annotations if self.contact.jid in annotations: buffer_ = self.xml.get_object('textview_annotation').get_buffer() buffer_.set_text(annotations[self.contact.jid]) for widget_name in ('URL_label', 'EMAIL_WORK_USERID_label', 'EMAIL_HOME_USERID_label'): widget = self.xml.get_object(widget_name) widget.hide() self.xml.connect_signals(self) self.xml.get_object('close_button').grab_focus() self.window.show_all() def update_progressbar(self): self.progressbar.pulse() self.time += self.update_intervall # Timeout in Milliseconds if (self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived) or self.time == 10000: self.progressbar.hide() self.update_progressbar_timeout_id = None return False return True def on_vcard_information_window_destroy(self, widget): if self.update_progressbar_timeout_id is not None: GLib.source_remove(self.update_progressbar_timeout_id) del app.interface.instances[self.account]['infos'][self.contact.jid] buffer_ = self.xml.get_object('textview_annotation').get_buffer() new_annotation = buffer_.get_text(buffer_.get_start_iter(), buffer_.get_end_iter(), True) con = app.connections[self.account] annotations = con.get_module('Annotations').annotations if new_annotation != annotations.get(self.contact.jid, ''): annotations[self.contact.jid] = new_annotation con.get_module('Annotations').store_annotations() app.ged.remove_event_handler('version-result-received', ged.GUI1, self.set_os_info) app.ged.remove_event_handler('time-result-received', ged.GUI1, self.set_entity_time) def on_vcard_information_window_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: self.window.destroy() def on_information_notebook_switch_page(self, widget, page, page_num): GLib.idle_add(self.xml.get_object('close_button').grab_focus) def on_PHOTO_eventbox_button_press_event(self, widget, event): """ If right-clicked, show popup """ if event.button == 3: # right click menu = Gtk.Menu() menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As')) if self.gc_contact: sha = self.gc_contact.avatar_sha name = self.gc_contact.get_shown_name() else: sha = app.contacts.get_avatar_sha( self.account, self.contact.jid) name = self.contact.get_shown_name() if sha is None: sha = self.avatar menuitem.connect('activate', gtkgui_helpers.on_avatar_save_as_menuitem_activate, sha, name) menu.append(menuitem) menu.connect('selection-done', lambda w:w.destroy()) # show the menu menu.show_all() menu.attach_to_widget(widget, None) menu.popup(None, None, None, None, event.button, event.time) def set_value(self, entry_name, value): try: widget = self.xml.get_object(entry_name) if entry_name in ('URL_label', 'EMAIL_WORK_USERID_label', 'EMAIL_HOME_USERID_label'): if entry_name == 'URL_label': widget.set_uri(value) else: widget.set_uri('mailto:' + value) widget.set_label(value) self.xml.get_object(entry_name).show() else: val = widget.get_text() if val: value = val + ' / ' + value widget.set_text(value) except AttributeError: pass def _set_values(self, vcard, jid): for i in vcard.keys(): if i == 'PHOTO' and self.xml.get_object('information_notebook').\ get_n_pages() > 4: if 'BINVAL' not in vcard[i]: continue photo_encoded = vcard[i]['BINVAL'] if photo_encoded == '': continue try: photo_decoded = base64.b64decode( photo_encoded.encode('utf-8')) except binascii.Error as error: app.log('avatar').warning('Invalid avatar for %s: %s', jid, error) continue pixbuf = gtkgui_helpers.get_pixbuf_from_data(photo_decoded) if pixbuf is None: continue self.avatar = pixbuf pixbuf = gtkgui_helpers.scale_pixbuf(pixbuf, AvatarSize.VCARD) surface = Gdk.cairo_surface_create_from_pixbuf( pixbuf, self.window.get_scale_factor()) image = self.xml.get_object('PHOTO_image') image.set_from_surface(surface) image.show() self.xml.get_object('user_avatar_label').show() continue if i in ('ADR', 'TEL', 'EMAIL'): for entry in vcard[i]: add_on = '_HOME' if 'WORK' in entry: add_on = '_WORK' for j in entry.keys(): self.set_value(i + add_on + '_' + j + '_label', entry[j]) if isinstance(vcard[i], dict): for j in vcard[i].keys(): self.set_value(i + '_' + j + '_label', vcard[i][j]) else: if i == 'DESC': self.xml.get_object('DESC_textview').get_buffer().set_text( vcard[i], len(vcard[i].encode('utf-8'))) elif i != 'jid': # Do not override jid_label self.set_value(i + '_label', vcard[i]) self.vcard_arrived = True def clear_values(self): for l in ('FN', 'NICKNAME', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', 'N_SUFFIX', 'EMAIL_HOME_USERID', 'TEL_HOME_NUMBER', 'BDAY', 'ORG_ORGNAME', 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'EMAIL_WORK_USERID', 'TEL_WORK_NUMBER', 'URL'): widget = self.xml.get_object(l + '_label') if l in ('EMAIL_HOME_USERID', 'EMAIL_WORK_USERID', 'URL'): widget.hide() else: widget.set_text('') for pref in ('ADR_HOME', 'ADR_WORK'): for l in ('STREET', 'EXTADR', 'LOCALITY', 'PCODE', 'REGION', 'CTRY'): widget = self.xml.get_object(pref + '_' + l + '_label') widget.set_text('') self.xml.get_object('DESC_textview').get_buffer().set_text('') def _nec_vcard_received(self, jid, resource, room, vcard, *args): self.clear_values() self._set_values(vcard, jid) def set_os_info(self, obj): if obj.conn.name != self.account: return if self.xml.get_object('information_notebook').get_n_pages() < 5: return if self.gc_contact: if obj.jid != self.contact.jid: return elif obj.jid.getStripped() != self.contact.jid: return i = 0 client = '' os = '' while i in self.os_info: if self.os_info[i]['resource'] == obj.jid.getResource(): if obj.client_info: self.os_info[i]['client'] = obj.client_info else: self.os_info[i]['client'] = Q_('?Client:Unknown') if obj.os_info: self.os_info[i]['os'] = obj.os_info else: self.os_info[i]['os'] = Q_('?OS:Unknown') else: if not self.os_info[i]['client']: self.os_info[i]['client'] = Q_('?Client:Unknown') if not self.os_info[i]['os']: self.os_info[i]['os'] = Q_('?OS:Unknown') if i > 0: client += '\n' os += '\n' client += self.os_info[i]['client'] os += self.os_info[i]['os'] i += 1 self.xml.get_object('client_name_version_label').set_text(client) self.xml.get_object('os_label').set_text(os) self.os_info_arrived = True def set_entity_time(self, obj): if obj.conn.name != self.account: return if self.xml.get_object('information_notebook').get_n_pages() < 5: return if self.gc_contact: if obj.jid != self.contact.jid: return elif obj.jid.getStripped() != self.contact.jid: return i = 0 time_s = '' while i in self.time_info: if self.time_info[i]['resource'] == obj.jid.getResource(): if obj.time_info: self.time_info[i]['time'] = obj.time_info else: self.time_info[i]['time'] = Q_('?Time:Unknown') else: if not self.time_info[i]['time']: self.time_info[i]['time'] = Q_('?Time:Unknown') if i > 0: time_s += '\n' time_s += self.time_info[i]['time'] i += 1 self.xml.get_object('time_label').set_text(time_s) self.entity_time_arrived = True def fill_status_label(self): if self.xml.get_object('information_notebook').get_n_pages() < 5: return contact_list = app.contacts.get_contacts(self.account, self.contact.jid) connected_contact_list = [] for c in contact_list: if c.show not in ('offline', 'error'): connected_contact_list.append(c) if not connected_contact_list: # no connected contact, get the offline one connected_contact_list = contact_list # stats holds show and status message stats = '' if connected_contact_list: # Start with self.contact, as with resources stats = helpers.get_uf_show(self.contact.show) if self.contact.status: stats += ': ' + self.contact.status for c in connected_contact_list: if c.resource != self.contact.resource: stats += '\n' stats += helpers.get_uf_show(c.show) if c.status: stats += ': ' + c.status else: # Maybe gc_vcard ? stats = helpers.get_uf_show(self.contact.show) if self.contact.status: stats += ': ' + self.contact.status status_label = self.xml.get_object('status_label') status_label.set_text(stats) status_label.set_tooltip_text(stats) def fill_jabber_page(self): self.xml.get_object('nickname_label').set_markup( '' + self.contact.get_shown_name() + '') self.xml.get_object('jid_label').set_text(self.contact.jid) subscription_label = self.xml.get_object('subscription_label') ask_label = self.xml.get_object('ask_label') if self.gc_contact: self.xml.get_object('subscription_title_label').set_markup(Q_("?Role in Group Chat:Role:")) uf_role = helpers.get_uf_role(self.gc_contact.role) subscription_label.set_text(uf_role) self.xml.get_object('ask_title_label').set_markup(_("Affiliation:")) uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) ask_label.set_text(uf_affiliation) else: uf_sub = helpers.get_uf_sub(self.contact.sub) subscription_label.set_text(uf_sub) if self.contact.sub == 'from': tt_text = _("This contact is interested in your presence information, but you are not interested in their presence") elif self.contact.sub == 'to': tt_text = _("You are interested in the contact's presence information, but it is not mutual") elif self.contact.sub == 'both': tt_text = _("The contact and you want to exchange presence information") else: # None tt_text = _("You and the contact have a mutual disinterest in each-others presence information") subscription_label.set_tooltip_text(tt_text) uf_ask = helpers.get_uf_ask(self.contact.ask) ask_label.set_text(uf_ask) if self.contact.ask == 'subscribe': tt_text = _("You are waiting contact's answer about your subscription request") else: tt_text = _("There is no pending subscription request.") ask_label.set_tooltip_text(tt_text) resources = '%s (%s)' % (self.contact.resource, str( self.contact.priority)) uf_resources = self.contact.resource + _(' resource with priority ')\ + str(self.contact.priority) if not self.contact.status: self.contact.status = '' con = app.connections[self.account] # do not wait for os_info if contact is not connected or has error # additional check for observer is needed, as show is offline for him if self.contact.show in ('offline', 'error')\ and not self.contact.is_observer(): self.os_info_arrived = True else: # Request os info if contact is connected if self.gc_contact: j, r = app.get_room_and_nick_from_fjid(self.real_jid) GLib.idle_add(con.get_module('SoftwareVersion').request_os_info, j, r) else: GLib.idle_add(con.get_module('SoftwareVersion').request_os_info, self.contact.jid, self.contact.resource) # do not wait for entity_time if contact is not connected or has error # additional check for observer is needed, as show is offline for him if self.contact.show in ('offline', 'error')\ and not self.contact.is_observer(): self.entity_time_arrived = True else: # Request entity time if contact is connected if self.gc_contact: j, r = app.get_room_and_nick_from_fjid(self.real_jid) GLib.idle_add(con.get_module('EntityTime').request_entity_time, j, r) else: GLib.idle_add(con.get_module('EntityTime').request_entity_time, self.contact.jid, self.contact.resource) self.os_info = {0: {'resource': self.real_resource, 'client': '', 'os': ''}} self.time_info = {0: {'resource': self.real_resource, 'time': ''}} i = 1 contact_list = app.contacts.get_contacts(self.account, self.contact.jid) if contact_list: for c in contact_list: if c.resource != self.contact.resource: resources += '\n%s (%s)' % (c.resource, str(c.priority)) uf_resources += '\n' + c.resource + \ _(' resource with priority ') + str(c.priority) if c.show not in ('offline', 'error'): GLib.idle_add(con.get_module('SoftwareVersion').request_os_info, c.jid, c.resource) GLib.idle_add(con.get_module('EntityTime').request_entity_time, c.jid, c.resource) self.os_info[i] = {'resource': c.resource, 'client': '', 'os': ''} self.time_info[i] = {'resource': c.resource, 'time': ''} i += 1 self.xml.get_object('resource_prio_label').set_text(resources) resource_prio_label_eventbox = self.xml.get_object( 'resource_prio_label_eventbox') resource_prio_label_eventbox.set_tooltip_text(uf_resources) self.fill_status_label() if self.gc_contact: con.get_module('VCardTemp').request_vcard( self._nec_vcard_received, self.gc_contact.get_full_jid(), room=True) else: con.get_module('VCardTemp').request_vcard( self._nec_vcard_received, self.contact.jid) def on_close_button_clicked(self, widget): self.window.destroy() class ZeroconfVcardWindow: def __init__(self, contact, account, is_fake = False): # the contact variable is the jid if vcard is true self.xml = gtkgui_helpers.get_gtk_builder('zeroconf_information_window.ui') self.window = self.xml.get_object('zeroconf_information_window') self.contact = contact self.account = account self.is_fake = is_fake self.fill_contact_page() self.fill_personal_page() self.xml.connect_signals(self) self.window.show_all() def on_zeroconf_information_window_destroy(self, widget): del app.interface.instances[self.account]['infos'][self.contact.jid] def on_zeroconf_information_window_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: self.window.destroy() def on_PHOTO_eventbox_button_press_event(self, widget, event): """ If right-clicked, show popup """ if event.button == 3: # right click menu = Gtk.Menu() menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As')) menuitem.connect('activate', gtkgui_helpers.on_avatar_save_as_menuitem_activate, self.contact.avatar_sha, self.contact.get_shown_name()) menu.append(menuitem) menu.connect('selection-done', lambda w:w.destroy()) # show the menu menu.show_all() menu.attach_to_widget(widget, None) menu.popup(None, None, None, None, event.button, event.time) def set_value(self, entry_name, value): try: if value and entry_name == 'URL_label': widget = Gtk.LinkButton(uri=value, label=value) widget.set_alignment(0, 0) table = self.xml.get_object('personal_info_table') table.attach(widget, 1, 3, 2, 1) else: self.xml.get_object(entry_name).set_text(value) except AttributeError: pass def fill_status_label(self): if self.xml.get_object('information_notebook').get_n_pages() < 2: return contact_list = app.contacts.get_contacts(self.account, self.contact.jid) # stats holds show and status message stats = '' one = True # Are we adding the first line ? if contact_list: for c in contact_list: if not one: stats += '\n' stats += helpers.get_uf_show(c.show) if c.status: stats += ': ' + c.status one = False else: # Maybe gc_vcard ? stats = helpers.get_uf_show(self.contact.show) if self.contact.status: stats += ': ' + self.contact.status status_label = self.xml.get_object('status_label') status_label.set_text(stats) status_label.set_tooltip_text(stats) def fill_contact_page(self): self.xml.get_object('nickname_label').set_markup( '' + self.contact.get_shown_name() + '') self.xml.get_object('local_jid_label').set_text(self.contact.jid) resources = '%s (%s)' % (self.contact.resource, str( self.contact.priority)) uf_resources = self.contact.resource + _(' resource with priority ')\ + str(self.contact.priority) if not self.contact.status: self.contact.status = '' self.xml.get_object('resource_prio_label').set_text(resources) resource_prio_label_eventbox = self.xml.get_object( 'resource_prio_label_eventbox') resource_prio_label_eventbox.set_tooltip_text(uf_resources) self.fill_status_label() def fill_personal_page(self): contact = app.connections[app.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid) for key in ('1st', 'last', 'jid', 'email'): if key not in contact['txt_dict']: contact['txt_dict'][key] = '' self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st']) self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last']) self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid']) self.xml.get_object('email_label').set_text(contact['txt_dict']['email']) def on_close_button_clicked(self, widget): self.window.destroy()