## vcard.py (has VcardWindow class) ## ## Copyright (C) 2003-2006 Yann Le Boulanger ## Copyright (C) 2005-2006 Nikos Kouremenos ## ## This program 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 2 only. ## ## This program 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. ## import gtk import gtk.glade import gobject import urllib import base64 import mimetypes import os import sys import time import gtkgui_helpers import dialogs import locale from common import helpers from common import gajim from common import i18n _ = i18n._ Q_ = i18n.Q_ APP = i18n.APP gtk.glade.bindtextdomain (APP, i18n.DIR) gtk.glade.textdomain (APP) GTKGUI_GLADE = 'gtkgui.glade' def get_avatar_pixbuf_encoded_mime(photo): '''return the pixbuf of the image photo is a dictionary containing PHOTO information''' if not isinstance(photo, dict): return None, None, None img_decoded = None avatar_encoded = None avatar_mime_type = None if photo.has_key('BINVAL') and photo.has_key('TYPE'): img_encoded = photo['BINVAL'] avatar_encoded = img_encoded avatar_mime_type = photo['TYPE'] try: img_decoded = base64.decodestring(img_encoded) except: pass elif photo.has_key('EXTVAL'): url = photo['EXTVAL'] try: fd = urllib.urlopen(url) img_decoded = fd.read() except: pass if img_decoded: pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded) else: pixbuf = None return pixbuf, avatar_encoded, avatar_mime_type class VcardWindow: '''Class for contact's information window''' def __init__(self, contact, account, vcard = False): # the contact variable is the jid if vcard is true self.xml = gtk.glade.XML(GTKGUI_GLADE, 'vcard_information_window', APP) self.window = self.xml.get_widget('vcard_information_window') self.publish_button = self.xml.get_widget('publish_button') self.retrieve_button = self.xml.get_widget('retrieve_button') self.nickname_entry = self.xml.get_widget('nickname_entry') self.publish_button.set_no_show_all(True) self.retrieve_button.set_no_show_all(True) self.xml.get_widget('photo_vbuttonbox').set_no_show_all(True) self.contact = contact # don't use it if vcard is true self.account = account self.vcard = vcard self.avatar_mime_type = None self.avatar_encoded = None if vcard: # we view/edit our own vcard self.jid = contact # remove Jabber tab & show publish/retrieve/close/set_avatar buttons # and make entries and textview editable self.change_to_vcard() else: # we see someone else's vcard self.publish_button.hide() self.retrieve_button.hide() self.jid = contact.jid self.fill_jabber_page() # if we are editing our own vcard publish button should publish # vcard data we have typed including nickname, it's why we connect only # here (when we see someone else's vcard) self.nickname_entry.connect('focus-out-event', self.on_nickname_entry_focus_out_event) self.xml.signal_autoconnect(self) self.window.show_all() def on_vcard_information_window_destroy(self, widget): del gajim.interface.instances[self.account]['infos'][self.jid] def on_vcard_information_window_key_press_event(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.window.destroy() def on_log_history_checkbutton_toggled(self, widget): #log conversation history? oldlog = True no_log_for = gajim.config.get_per('accounts', self.account, 'no_log_for').split() if self.contact.jid in no_log_for: oldlog = False log = widget.get_active() if not log and not self.contact.jid in no_log_for: no_log_for.append(self.contact.jid) if log and self.contact.jid in no_log_for: no_log_for.remove(self.contact.jid) if oldlog != log: gajim.config.set_per('accounts', self.account, 'no_log_for', ' '.join(no_log_for)) def on_nickname_entry_focus_out_event(self, widget, event): '''Save contact information and update the roster item on the Jabber server''' new_name = self.nickname_entry.get_text().decode('utf-8') # update contact.name with new nickname if that is not '' if new_name != self.contact.name and new_name != '': self.contact.name = new_name # update roster model model = gajim.interface.roster.tree.get_model() for iter_ in gajim.interface.roster.get_contact_iter(self.contact.jid, self.account): model[iter_][1] = new_name gajim.connections[self.account].update_contact(self.contact.jid, self.contact.name, self.contact.groups) # update opened chat window ctrl = gajim.interface.msg_win_mgr.get_control(self.contact.jid, self.account) if ctrl: ctrl.update_ui() win = gajim.interface.msg_win_mgr.get_window(self.contact.jid, self.account) win.redraw_tab(ctrl) win.show_title() def on_close_button_clicked(self, widget): self.window.destroy() def on_clear_button_clicked(self, widget): # empty the image self.xml.get_widget('PHOTO_image').set_from_pixbuf(None) self.avatar_encoded = None def update_preview(self, widget): path_to_file = widget.get_preview_filename() if path_to_file is None or os.path.isdir(path_to_file): # nothing to preview or directory # make sure you clean image do show nothing widget.get_preview_widget().set_from_file(None) return try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100) except gobject.GError: return widget.get_preview_widget().set_from_pixbuf(pixbuf) def on_set_avatar_button_clicked(self, widget): f = None dialog = gtk.FileChooserDialog(_('Choose Avatar'), None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) try: if os.name == 'nt': path = helpers.get_my_pictures_path() else: path = os.environ['HOME'] except: path = '' if path: dialog.set_current_folder(path) dialog.set_default_response(gtk.RESPONSE_OK) filtr = gtk.FileFilter() filtr.set_name(_('All files')) filtr.add_pattern('*') dialog.add_filter(filtr) filtr = gtk.FileFilter() filtr.set_name(_('Images')) filtr.add_mime_type('image/png') filtr.add_mime_type('image/jpeg') filtr.add_mime_type('image/gif') filtr.add_mime_type('image/tiff') filtr.add_mime_type('image/x-xpixmap') # xpm dialog.add_filter(filtr) dialog.set_filter(filtr) dialog.set_use_preview_label(False) dialog.set_preview_widget(gtk.Image()) dialog.connect('selection-changed', self.update_preview) done = False while not done: response = dialog.run() if response == gtk.RESPONSE_OK: path_to_file = dialog.get_filename() path_to_file = gtkgui_helpers.decode_filechooser_file_paths( (path_to_file,))[0] filesize = os.path.getsize(path_to_file) # in bytes if filesize > 16384: # 16 kb try: pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) # get the image at 'notification size' # and use that hoping size is okay scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') except gobject.GError, msg: # unknown format dialogs.ErrorDialog(_('Could not load image'), msg).get_response() continue else: path_to_file = os.path.join(gajim.TMP, 'avatar_scaled.png') scaled_pixbuf.save(path_to_file, 'png') done = True else: done = True else: # Cancel or WM X button done = True dialog.destroy() if response == gtk.RESPONSE_OK: fd = open(path_to_file, 'rb') data = fd.read() pixbuf = gtkgui_helpers.get_pixbuf_from_data(data) # rescale it pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') image = self.xml.get_widget('PHOTO_image') image.set_from_pixbuf(pixbuf) self.avatar_encoded = base64.encodestring(data) # returns None if unknown type self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0] def set_value(self, entry_name, value): try: self.xml.get_widget(entry_name).set_text(value) except AttributeError: pass def set_values(self, vcard): for i in vcard.keys(): if i == 'PHOTO': pixbuf, self.avatar_encoded, self.avatar_mime_type = \ get_avatar_pixbuf_encoded_mime(vcard[i]) if not pixbuf: continue image = self.xml.get_widget('PHOTO_image') pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') image.set_from_pixbuf(pixbuf) continue if i == 'ADR' or i == 'TEL' or i == '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 + '_entry', entry[j]) if isinstance(vcard[i], dict): for j in vcard[i].keys(): self.set_value(i + '_' + j + '_entry', vcard[i][j]) else: if i == 'DESC': self.xml.get_widget('DESC_textview').get_buffer().set_text( vcard[i], 0) else: self.set_value(i + '_entry', vcard[i]) def set_last_status_time(self): self.fill_status_label() def set_os_info(self, resource, client_info, os_info): i = 0 client = '' os = '' while self.os_info.has_key(i): if not self.os_info[i]['resource'] or \ self.os_info[i]['resource'] == resource: self.os_info[i]['client'] = client_info self.os_info[i]['os'] = os_info if i > 0: client += '\n' os += '\n' client += self.os_info[i]['client'] os += self.os_info[i]['os'] i += 1 if client == '': client = Q_('?Client:Unknown') if os == '': os = Q_('?OS:Unknown') self.xml.get_widget('client_name_version_label').set_text(client) self.xml.get_widget('os_label').set_text(os) def fill_status_label(self): contact_list = gajim.contacts.get_contact(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 if c.last_status_time: stats += '\n' + _('since %s') % time.strftime('%c', c.last_status_time).decode(locale.getpreferredencoding()) one = False status_label = self.xml.get_widget('status_label') status_label.set_max_width_chars(15) status_label.set_text(stats) tip = gtk.Tooltips() status_label_eventbox = self.xml.get_widget('status_label_eventbox') tip.set_tip(status_label_eventbox, stats) def fill_jabber_page(self): tooltips = gtk.Tooltips() self.xml.get_widget('nickname_label').set_text( self.contact.get_shown_name()) self.xml.get_widget('jid_label').set_text(self.contact.jid) uf_sub = helpers.get_uf_sub(self.contact.sub) self.xml.get_widget('subscription_label').set_text(uf_sub) eb = self.xml.get_widget('subscription_label_eventbox') if self.contact.sub == 'from': tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") elif self.contact.sub == 'to': tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") elif self.contact.sub == 'both': tt_text = _("You and the contact are interested in each other's presence information") else: # None tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") tooltips.set_tip(eb, tt_text) label = self.xml.get_widget('ask_label') uf_ask = helpers.get_uf_ask(self.contact.ask) label.set_text(uf_ask) eb = self.xml.get_widget('ask_label_eventbox') if self.contact.ask == 'subscribe': tooltips.set_tip(eb, _("You are waiting contact's answer about your subscription request")) self.nickname_entry.set_text(self.contact.name) log = True if self.contact.jid in gajim.config.get_per('accounts', self.account, 'no_log_for').split(' '): log = False checkbutton = self.xml.get_widget('log_history_checkbutton') checkbutton.set_active(log) checkbutton.connect('toggled', self.on_log_history_checkbutton_toggled) resources = '%s (%s)' % (self.contact.resource, unicode( self.contact.priority)) uf_resources = self.contact.resource + _(' resource with priority ')\ + unicode(self.contact.priority) if not self.contact.status: self.contact.status = '' # Request list time status gajim.connections[self.account].request_last_status_time(self.contact.jid, self.contact.resource) # Request os info in contact is connected if self.contact.show not in ('offline', 'error'): gajim.connections[self.account].request_os_info(self.contact.jid, self.contact.resource) self.os_info = {0: {'resource': self.contact.resource, 'client': '', 'os': ''}} i = 1 contact_list = gajim.contacts.get_contact(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, unicode(c.priority)) uf_resources += '\n' + c.resource + \ _(' resource with priority ') + unicode(c.priority) if c.show not in ('offline', 'error'): gajim.connections[self.account].request_os_info(c.jid, c.resource) gajim.connections[self.account].request_last_status_time(c.jid, c.resource) self.os_info[i] = {'resource': c.resource, 'client': '', 'os': ''} i += 1 self.xml.get_widget('resource_prio_label').set_text(resources) resource_prio_label_eventbox = self.xml.get_widget( 'resource_prio_label_eventbox') tooltips.set_tip(resource_prio_label_eventbox, uf_resources) self.fill_status_label() is_fake = False if gajim.contacts.is_pm_from_jid(self.account, self.contact.jid): is_fake = True gajim.connections[self.account].request_vcard(self.contact.jid, is_fake) def add_to_vcard(self, vcard, entry, txt): '''Add an information to the vCard dictionary''' entries = entry.split('_') loc = vcard if len(entries) == 3: # We need to use lists if not loc.has_key(entries[0]): loc[entries[0]] = [] found = False for e in loc[entries[0]]: if entries[1] in e: found = True break if found: e[entries[2]] = txt else: loc[entries[0]].append({entries[1]: '', entries[2]: txt}) return vcard while len(entries) > 1: if not loc.has_key(entries[0]): loc[entries[0]] = {} loc = loc[entries[0]] del entries[0] loc[entries[0]] = txt return vcard def make_vcard(self): '''make the vCard dictionary''' entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] vcard = {} for e in entries: txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8') if txt != '': vcard = self.add_to_vcard(vcard, e, txt) # DESC textview buff = self.xml.get_widget('DESC_textview').get_buffer() start_iter = buff.get_start_iter() end_iter = buff.get_end_iter() txt = buff.get_text(start_iter, end_iter, 0) if txt != '': vcard['DESC'] = txt.decode('utf-8') # Avatar if self.avatar_encoded: vcard['PHOTO'] = {'BINVAL': self.avatar_encoded} if self.avatar_mime_type: vcard['PHOTO']['TYPE'] = self.avatar_mime_type return vcard def on_publish_button_clicked(self, widget): if gajim.connections[self.account].connected < 2: dialogs.ErrorDialog(_('You are not connected to the server'), _('Without a connection you can not publish your contact ' 'information.')).get_response() return vcard = self.make_vcard() nick = '' if vcard.has_key('NICKNAME'): nick = vcard['NICKNAME'] if nick == '': nick = gajim.config.get_per('accounts', self.account, 'name') gajim.nicks[self.account] = nick gajim.connections[self.account].send_vcard(vcard) def on_retrieve_button_clicked(self, widget): entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] if gajim.connections[self.account].connected > 1: # clear all entries for e in entries: self.xml.get_widget(e + '_entry').set_text('') self.xml.get_widget('DESC_textview').get_buffer().set_text('') self.xml.get_widget('PHOTO_image').set_from_pixbuf(None) gajim.connections[self.account].request_vcard(self.jid) else: ErrorDialog(_('You are not connected to the server'), _('Without a connection, you can not get your contact information.')).get_response() def change_to_vcard(self): self.xml.get_widget('information_notebook').remove_page(0) self.xml.get_widget('nickname_label').set_text(_('Personal details')) self.publish_button.show() self.retrieve_button.show() #photo_vbuttonbox visible self.xml.get_widget('photo_vbuttonbox').show() #make all entries editable entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] for e in entries: self.xml.get_widget(e + '_entry').set_property('editable', True) description_textview = self.xml.get_widget('DESC_textview') description_textview.set_editable(True) description_textview.set_cursor_visible(True)