4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
6 ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
8 ## Dimitur Kirov <dkirov AT gmail.com>
9 ## Travis Shirk <travis AT pobox.com>
10 ## Stefan Bethge <stefan AT lanpartei.de>
11 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
12 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
13 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
14 ## Jonathan Schleifer <js-gajim AT webkeks.org>
15 ## Stephan Erb <steve-e AT h3c.de>
17 ## This file is part of Gajim.
19 ## Gajim is free software; you can redistribute it and/or modify
20 ## it under the terms of the GNU General Public License as published
21 ## by the Free Software Foundation; version 3 only.
23 ## Gajim is distributed in the hope that it will be useful,
24 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
25 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 ## GNU General Public License for more details.
28 ## You should have received a copy of the GNU General Public License
29 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
32 # THIS FILE IS FOR **OTHERS'** PROFILE (when we VIEW their INFO)
43 from common
import helpers
44 from common
import gajim
45 from common
import ged
46 from common
.i18n
import Q_
48 def get_avatar_pixbuf_encoded_mime(photo
):
50 Return the pixbuf of the image
52 Photo is a dictionary containing PHOTO information.
54 if not isinstance(photo
, dict):
55 return None, None, None
58 avatar_mime_type
= None
60 img_encoded
= photo
['BINVAL']
61 avatar_encoded
= img_encoded
63 img_decoded
= base64
.decodestring(img_encoded
)
68 avatar_mime_type
= photo
['TYPE']
69 pixbuf
= gtkgui_helpers
.get_pixbuf_from_data(img_decoded
)
71 pixbuf
, avatar_mime_type
= gtkgui_helpers
.get_pixbuf_from_data(
72 img_decoded
, want_type
=True)
75 return pixbuf
, avatar_encoded
, avatar_mime_type
79 Class for contact's information window
82 def __init__(self
, contact
, account
, gc_contact
= None):
83 # the contact variable is the jid if vcard is true
84 self
.xml
= gtkgui_helpers
.get_gtk_builder('vcard_information_window.ui')
85 self
.window
= self
.xml
.get_object('vcard_information_window')
86 self
.progressbar
= self
.xml
.get_object('progressbar')
88 self
.contact
= contact
89 self
.account
= account
90 self
.gc_contact
= gc_contact
94 # Don't use real jid if room is (semi-)anonymous
95 gc_control
= gajim
.interface
.msg_win_mgr
.get_gc_control(
96 gc_contact
.room_jid
, account
)
97 if gc_contact
.jid
and not gc_control
.is_anonymous
:
98 self
.real_jid
= gc_contact
.jid
99 self
.real_jid_for_vcard
= gc_contact
.jid
100 if gc_contact
.resource
:
101 self
.real_jid
+= '/' + gc_contact
.resource
103 self
.real_jid
= gc_contact
.get_full_jid()
104 self
.real_jid_for_vcard
= self
.real_jid
105 self
.real_resource
= gc_contact
.name
107 self
.real_jid
= contact
.get_full_jid()
108 self
.real_resource
= contact
.resource
110 puny_jid
= helpers
.sanitize_filename(contact
.jid
)
111 local_avatar_basepath
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
) + \
113 for extension
in ('.png', '.jpeg'):
114 local_avatar_path
= local_avatar_basepath
+ extension
115 if os
.path
.isfile(local_avatar_path
):
116 image
= self
.xml
.get_object('custom_avatar_image')
117 image
.set_from_file(local_avatar_path
)
119 self
.xml
.get_object('custom_avatar_label').show()
121 self
.avatar_mime_type
= None
122 self
.avatar_encoded
= None
123 self
.vcard_arrived
= False
124 self
.os_info_arrived
= False
125 self
.entity_time_arrived
= False
126 self
.update_progressbar_timeout_id
= gobject
.timeout_add(100,
127 self
.update_progressbar
)
129 gajim
.ged
.register_event_handler('version-result-received', ged
.GUI1
,
131 gajim
.ged
.register_event_handler('last-result-received', ged
.GUI2
,
132 self
.set_last_status_time
)
133 gajim
.ged
.register_event_handler('time-result-received', ged
.GUI1
,
134 self
.set_entity_time
)
135 gajim
.ged
.register_event_handler('vcard-received', ged
.GUI1
,
136 self
._nec
_vcard
_received
)
138 self
.fill_jabber_page()
139 annotations
= gajim
.connections
[self
.account
].annotations
140 if self
.contact
.jid
in annotations
:
141 buffer_
= self
.xml
.get_object('textview_annotation').get_buffer()
142 buffer_
.set_text(annotations
[self
.contact
.jid
])
144 self
.xml
.connect_signals(self
)
145 self
.window
.show_all()
146 self
.xml
.get_object('close_button').grab_focus()
148 def update_progressbar(self
):
149 self
.progressbar
.pulse()
150 return True # loop forever
152 def on_vcard_information_window_destroy(self
, widget
):
153 if self
.update_progressbar_timeout_id
is not None:
154 gobject
.source_remove(self
.update_progressbar_timeout_id
)
155 del gajim
.interface
.instances
[self
.account
]['infos'][self
.contact
.jid
]
156 buffer_
= self
.xml
.get_object('textview_annotation').get_buffer()
157 annotation
= buffer_
.get_text(buffer_
.get_start_iter(),
158 buffer_
.get_end_iter())
159 connection
= gajim
.connections
[self
.account
]
160 if annotation
!= connection
.annotations
.get(self
.contact
.jid
, ''):
161 connection
.annotations
[self
.contact
.jid
] = annotation
162 connection
.store_annotations()
163 gajim
.ged
.remove_event_handler('version-result-received', ged
.GUI1
,
165 gajim
.ged
.remove_event_handler('last-result-received', ged
.GUI2
,
166 self
.set_last_status_time
)
167 gajim
.ged
.remove_event_handler('time-result-received', ged
.GUI1
,
168 self
.set_entity_time
)
169 gajim
.ged
.remove_event_handler('vcard-received', ged
.GUI1
,
170 self
._nec
_vcard
_received
)
172 def on_vcard_information_window_key_press_event(self
, widget
, event
):
173 if event
.keyval
== gtk
.keysyms
.Escape
:
174 self
.window
.destroy()
176 def on_PHOTO_eventbox_button_press_event(self
, widget
, event
):
178 If right-clicked, show popup
180 if event
.button
== 3: # right click
182 menuitem
= gtk
.ImageMenuItem(gtk
.STOCK_SAVE_AS
)
183 menuitem
.connect('activate',
184 gtkgui_helpers
.on_avatar_save_as_menuitem_activate
,
185 self
.contact
.jid
, self
.contact
.get_shown_name())
186 menu
.append(menuitem
)
187 menu
.connect('selection-done', lambda w
:w
.destroy())
190 menu
.popup(None, None, None, event
.button
, event
.time
)
192 def set_value(self
, entry_name
, value
):
194 if value
and entry_name
== 'URL_label':
195 widget
= gtk
.LinkButton(value
, value
)
196 widget
.set_alignment(0, 0)
198 table
= self
.xml
.get_object('personal_info_table')
199 table
.attach(widget
, 1, 4, 3, 4, yoptions
= 0)
201 self
.xml
.get_object(entry_name
).set_text(value
)
202 except AttributeError:
205 def set_values(self
, vcard
):
206 for i
in vcard
.keys():
207 if i
== 'PHOTO' and self
.xml
.get_object('information_notebook').\
209 pixbuf
, self
.avatar_encoded
, self
.avatar_mime_type
= \
210 get_avatar_pixbuf_encoded_mime(vcard
[i
])
211 image
= self
.xml
.get_object('PHOTO_image')
213 self
.xml
.get_object('user_avatar_label').show()
215 image
.set_from_icon_name('stock_person',
216 gtk
.ICON_SIZE_DIALOG
)
218 pixbuf
= gtkgui_helpers
.get_scaled_pixbuf(pixbuf
, 'vcard')
219 image
.set_from_pixbuf(pixbuf
)
221 if i
in ('ADR', 'TEL', 'EMAIL'):
222 for entry
in vcard
[i
]:
226 for j
in entry
.keys():
227 self
.set_value(i
+ add_on
+ '_' + j
+ '_label', entry
[j
])
228 if isinstance(vcard
[i
], dict):
229 for j
in vcard
[i
].keys():
230 self
.set_value(i
+ '_' + j
+ '_label', vcard
[i
][j
])
233 self
.xml
.get_object('DESC_textview').get_buffer().set_text(
235 elif i
!= 'jid': # Do not override jid_label
236 self
.set_value(i
+ '_label', vcard
[i
])
237 self
.vcard_arrived
= True
238 self
.test_remove_progressbar()
240 def _nec_vcard_received(self
, obj
):
241 if obj
.conn
.name
!= self
.account
:
244 # It's a muc occupant vcard
245 if obj
.fjid
!= self
.real_jid
:
248 if obj
.jid
!= self
.contact
.jid
:
250 self
.set_values(obj
.vcard_dict
)
252 def test_remove_progressbar(self
):
253 if self
.update_progressbar_timeout_id
is not None and \
254 self
.vcard_arrived
and self
.os_info_arrived
and self
.entity_time_arrived
:
255 gobject
.source_remove(self
.update_progressbar_timeout_id
)
256 self
.progressbar
.hide()
257 self
.update_progressbar_timeout_id
= None
259 def set_last_status_time(self
, obj
):
260 if obj
.fjid
!= self
.real_jid
:
262 self
.fill_status_label()
264 def set_os_info(self
, obj
):
265 if self
.xml
.get_object('information_notebook').get_n_pages() < 5:
267 if obj
.fjid
!= self
.real_jid
:
272 while i
in self
.os_info
:
273 if not self
.os_info
[i
]['resource'] or \
274 self
.os_info
[i
]['resource'] == obj
.resource
:
275 self
.os_info
[i
]['client'] = obj
.client_info
276 self
.os_info
[i
]['os'] = obj
.os_info
280 client
+= self
.os_info
[i
]['client']
281 os
+= self
.os_info
[i
]['os']
285 client
= Q_('?Client:Unknown')
287 os
= Q_('?OS:Unknown')
288 self
.xml
.get_object('client_name_version_label').set_text(client
)
289 self
.xml
.get_object('os_label').set_text(os
)
290 self
.os_info_arrived
= True
291 self
.test_remove_progressbar()
293 def set_entity_time(self
, obj
):
294 if self
.xml
.get_object('information_notebook').get_n_pages() < 5:
296 if obj
.fjid
!= self
.real_jid
:
300 while i
in self
.time_info
:
301 if not self
.time_info
[i
]['resource'] or \
302 self
.time_info
[i
]['resource'] == obj
.resource
:
303 self
.time_info
[i
]['time'] = obj
.time_info
306 time_s
+= self
.time_info
[i
]['time']
310 time_s
= Q_('?Time:Unknown')
311 self
.xml
.get_object('time_label').set_text(time_s
)
312 self
.entity_time_arrived
= True
313 self
.test_remove_progressbar()
315 def fill_status_label(self
):
316 if self
.xml
.get_object('information_notebook').get_n_pages() < 5:
318 contact_list
= gajim
.contacts
.get_contacts(self
.account
, self
.contact
.jid
)
319 connected_contact_list
= []
320 for c
in contact_list
:
321 if c
.show
not in ('offline', 'error'):
322 connected_contact_list
.append(c
)
323 if not connected_contact_list
:
324 # no connected contact, get the offline one
325 connected_contact_list
= contact_list
326 # stats holds show and status message
328 if connected_contact_list
:
329 # Start with self.contact, as with resources
330 stats
= helpers
.get_uf_show(self
.contact
.show
)
331 if self
.contact
.status
:
332 stats
+= ': ' + self
.contact
.status
333 if self
.contact
.last_status_time
:
334 stats
+= '\n' + _('since %s') % time
.strftime('%c',
335 self
.contact
.last_status_time
).decode(
336 locale
.getpreferredencoding())
337 for c
in connected_contact_list
:
338 if c
.resource
!= self
.contact
.resource
:
340 stats
+= helpers
.get_uf_show(c
.show
)
342 stats
+= ': ' + c
.status
343 if c
.last_status_time
:
344 stats
+= '\n' + _('since %s') % time
.strftime('%c',
345 c
.last_status_time
).decode(locale
.getpreferredencoding())
346 else: # Maybe gc_vcard ?
347 stats
= helpers
.get_uf_show(self
.contact
.show
)
348 if self
.contact
.status
:
349 stats
+= ': ' + self
.contact
.status
350 status_label
= self
.xml
.get_object('status_label')
351 status_label
.set_max_width_chars(15)
352 status_label
.set_text(stats
)
354 status_label_eventbox
= self
.xml
.get_object('status_label_eventbox')
355 status_label_eventbox
.set_tooltip_text(stats
)
357 def fill_jabber_page(self
):
358 self
.xml
.get_object('nickname_label').set_markup(
359 '<b><span size="x-large">' +
360 self
.contact
.get_shown_name() +
362 self
.xml
.get_object('jid_label').set_text(self
.contact
.jid
)
364 subscription_label
= self
.xml
.get_object('subscription_label')
365 ask_label
= self
.xml
.get_object('ask_label')
367 self
.xml
.get_object('subscription_title_label').set_markup(Q_("?Role in Group Chat:<b>Role:</b>"))
368 uf_role
= helpers
.get_uf_role(self
.gc_contact
.role
)
369 subscription_label
.set_text(uf_role
)
371 self
.xml
.get_object('ask_title_label').set_markup(_("<b>Affiliation:</b>"))
372 uf_affiliation
= helpers
.get_uf_affiliation(self
.gc_contact
.affiliation
)
373 ask_label
.set_text(uf_affiliation
)
375 uf_sub
= helpers
.get_uf_sub(self
.contact
.sub
)
376 subscription_label
.set_text(uf_sub
)
377 eb
= self
.xml
.get_object('subscription_label_eventbox')
378 if self
.contact
.sub
== 'from':
379 tt_text
= _("This contact is interested in your presence information, but you are not interested in his/her presence")
380 elif self
.contact
.sub
== 'to':
381 tt_text
= _("You are interested in the contact's presence information, but he/she is not interested in yours")
382 elif self
.contact
.sub
== 'both':
383 tt_text
= _("You and the contact are interested in each other's presence information")
385 tt_text
= _("You are not interested in the contact's presence, and neither he/she is interested in yours")
386 eb
.set_tooltip_text(tt_text
)
388 uf_ask
= helpers
.get_uf_ask(self
.contact
.ask
)
389 ask_label
.set_text(uf_ask
)
390 eb
= self
.xml
.get_object('ask_label_eventbox')
391 if self
.contact
.ask
== 'subscribe':
392 tt_text
= _("You are waiting contact's answer about your subscription request")
394 tt_text
= _("There is no pending subscription request.")
395 eb
.set_tooltip_text(tt_text
)
397 resources
= '%s (%s)' % (self
.contact
.resource
, unicode(
398 self
.contact
.priority
))
399 uf_resources
= self
.contact
.resource
+ _(' resource with priority ')\
400 + unicode(self
.contact
.priority
)
401 if not self
.contact
.status
:
402 self
.contact
.status
= ''
404 # Request list time status only if contact is offline
405 if self
.contact
.show
== 'offline':
407 j
, r
= gajim
.get_room_and_nick_from_fjid(self
.real_jid
)
408 gajim
.connections
[self
.account
].request_last_status_time(j
, r
,
411 gajim
.connections
[self
.account
].request_last_status_time(
412 self
.contact
.jid
, self
.contact
.resource
)
414 # do not wait for os_info if contact is not connected or has error
415 # additional check for observer is needed, as show is offline for him
416 if self
.contact
.show
in ('offline', 'error')\
417 and not self
.contact
.is_observer():
418 self
.os_info_arrived
= True
419 else: # Request os info if contact is connected
421 j
, r
= gajim
.get_room_and_nick_from_fjid(self
.real_jid
)
422 gobject
.idle_add(gajim
.connections
[self
.account
].request_os_info
,
423 j
, r
, self
.contact
.jid
)
425 gobject
.idle_add(gajim
.connections
[self
.account
].request_os_info
,
426 self
.contact
.jid
, self
.contact
.resource
)
428 # do not wait for entity_time if contact is not connected or has error
429 # additional check for observer is needed, as show is offline for him
430 if self
.contact
.show
in ('offline', 'error')\
431 and not self
.contact
.is_observer():
432 self
.entity_time_arrived
= True
433 else: # Request entity time if contact is connected
435 j
, r
= gajim
.get_room_and_nick_from_fjid(self
.real_jid
)
436 gobject
.idle_add(gajim
.connections
[self
.account
].\
437 request_entity_time
, j
, r
, self
.contact
.jid
)
439 gobject
.idle_add(gajim
.connections
[self
.account
].\
440 request_entity_time
, self
.contact
.jid
, self
.contact
.resource
)
442 self
.os_info
= {0: {'resource': self
.real_resource
, 'client': '',
444 self
.time_info
= {0: {'resource': self
.real_resource
, 'time': ''}}
446 contact_list
= gajim
.contacts
.get_contacts(self
.account
, self
.contact
.jid
)
448 for c
in contact_list
:
449 if c
.resource
!= self
.contact
.resource
:
450 resources
+= '\n%s (%s)' % (c
.resource
,
452 uf_resources
+= '\n' + c
.resource
+ \
453 _(' resource with priority ') + unicode(c
.priority
)
454 if c
.show
not in ('offline', 'error'):
456 gajim
.connections
[self
.account
].request_os_info
, c
.jid
,
458 gobject
.idle_add(gajim
.connections
[self
.account
].\
459 request_entity_time
, c
.jid
, c
.resource
)
460 self
.os_info
[i
] = {'resource': c
.resource
, 'client': '',
462 self
.time_info
[i
] = {'resource': c
.resource
, 'time': ''}
465 self
.xml
.get_object('resource_prio_label').set_text(resources
)
466 resource_prio_label_eventbox
= self
.xml
.get_object(
467 'resource_prio_label_eventbox')
468 resource_prio_label_eventbox
.set_tooltip_text(uf_resources
)
470 self
.fill_status_label()
473 # If we know the real jid, remove the resource from vcard request
474 gajim
.connections
[self
.account
].request_vcard(self
.real_jid_for_vcard
,
475 self
.gc_contact
.get_full_jid())
477 gajim
.connections
[self
.account
].request_vcard(self
.contact
.jid
)
479 def on_close_button_clicked(self
, widget
):
480 self
.window
.destroy()
483 class ZeroconfVcardWindow
:
484 def __init__(self
, contact
, account
, is_fake
= False):
485 # the contact variable is the jid if vcard is true
486 self
.xml
= gtkgui_helpers
.get_gtk_builder('zeroconf_information_window.ui')
487 self
.window
= self
.xml
.get_object('zeroconf_information_window')
489 self
.contact
= contact
490 self
.account
= account
491 self
.is_fake
= is_fake
493 # self.avatar_mime_type = None
494 # self.avatar_encoded = None
496 self
.fill_contact_page()
497 self
.fill_personal_page()
499 self
.xml
.connect_signals(self
)
500 self
.window
.show_all()
502 def on_zeroconf_information_window_destroy(self
, widget
):
503 del gajim
.interface
.instances
[self
.account
]['infos'][self
.contact
.jid
]
505 def on_zeroconf_information_window_key_press_event(self
, widget
, event
):
506 if event
.keyval
== gtk
.keysyms
.Escape
:
507 self
.window
.destroy()
509 def on_PHOTO_eventbox_button_press_event(self
, widget
, event
):
511 If right-clicked, show popup
513 if event
.button
== 3: # right click
515 menuitem
= gtk
.ImageMenuItem(gtk
.STOCK_SAVE_AS
)
516 menuitem
.connect('activate',
517 gtkgui_helpers
.on_avatar_save_as_menuitem_activate
,
518 self
.contact
.jid
, self
.contact
.get_shown_name())
519 menu
.append(menuitem
)
520 menu
.connect('selection-done', lambda w
:w
.destroy())
523 menu
.popup(None, None, None, event
.button
, event
.time
)
525 def set_value(self
, entry_name
, value
):
527 if value
and entry_name
== 'URL_label':
528 widget
= gtk
.LinkButton(value
, value
)
529 widget
.set_alignment(0, 0)
530 table
= self
.xml
.get_object('personal_info_table')
531 table
.attach(widget
, 1, 4, 3, 4, yoptions
= 0)
533 self
.xml
.get_object(entry_name
).set_text(value
)
534 except AttributeError:
537 def fill_status_label(self
):
538 if self
.xml
.get_object('information_notebook').get_n_pages() < 2:
540 contact_list
= gajim
.contacts
.get_contacts(self
.account
, self
.contact
.jid
)
541 # stats holds show and status message
543 one
= True # Are we adding the first line ?
545 for c
in contact_list
:
548 stats
+= helpers
.get_uf_show(c
.show
)
550 stats
+= ': ' + c
.status
551 if c
.last_status_time
:
552 stats
+= '\n' + _('since %s') % time
.strftime('%c',
553 c
.last_status_time
).decode(locale
.getpreferredencoding())
555 else: # Maybe gc_vcard ?
556 stats
= helpers
.get_uf_show(self
.contact
.show
)
557 if self
.contact
.status
:
558 stats
+= ': ' + self
.contact
.status
559 status_label
= self
.xml
.get_object('status_label')
560 status_label
.set_max_width_chars(15)
561 status_label
.set_text(stats
)
563 status_label_eventbox
= self
.xml
.get_object('status_label_eventbox')
564 status_label_eventbox
.set_tooltip_text(stats
)
566 def fill_contact_page(self
):
567 self
.xml
.get_object('nickname_label').set_markup(
568 '<b><span size="x-large">' +
569 self
.contact
.get_shown_name() +
571 self
.xml
.get_object('local_jid_label').set_text(self
.contact
.jid
)
573 resources
= '%s (%s)' % (self
.contact
.resource
, unicode(
574 self
.contact
.priority
))
575 uf_resources
= self
.contact
.resource
+ _(' resource with priority ')\
576 + unicode(self
.contact
.priority
)
577 if not self
.contact
.status
:
578 self
.contact
.status
= ''
580 self
.xml
.get_object('resource_prio_label').set_text(resources
)
581 resource_prio_label_eventbox
= self
.xml
.get_object(
582 'resource_prio_label_eventbox')
583 resource_prio_label_eventbox
.set_tooltip_text(uf_resources
)
585 self
.fill_status_label()
587 def fill_personal_page(self
):
588 contact
= gajim
.connections
[gajim
.ZEROCONF_ACC_NAME
].roster
.getItem(self
.contact
.jid
)
589 for key
in ('1st', 'last', 'jid', 'email'):
590 if key
not in contact
['txt_dict']:
591 contact
['txt_dict'][key
] = ''
592 self
.xml
.get_object('first_name_label').set_text(contact
['txt_dict']['1st'])
593 self
.xml
.get_object('last_name_label').set_text(contact
['txt_dict']['last'])
594 self
.xml
.get_object('jabber_id_label').set_text(contact
['txt_dict']['jid'])
595 self
.xml
.get_object('email_label').set_text(contact
['txt_dict']['email'])
597 def on_close_button_clicked(self
, widget
):
598 self
.window
.destroy()