Use GtkFileChooserDialog only when we need previews, default to NativeFileChooser otherwise. GtkFileChooserDialogs have a long list of issues, so lets only use it if we gain something from it. Flatpak should only run NativeFileChoosers because its sandboxed and this is needed for security purposes. As a result of that, Flatpak Users dont have image previews in the FileOpenDialogs Refactor all FileChoosers for a more simple approach when we use them Add a new SendFileDialog, so we dont have to put widgets into the FileChooser which forces non-native Dialogs.
377 lines
15 KiB
Python
377 lines
15 KiB
Python
# -*- coding:utf-8 -*-
|
|
## src/profile_window.py
|
|
##
|
|
## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
|
|
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
|
|
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.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/>.
|
|
##
|
|
|
|
# THIS FILE IS FOR **OUR** PROFILE (when we edit our INFO)
|
|
|
|
from gi.repository import Gtk
|
|
from gi.repository import Gdk
|
|
from gi.repository import GLib
|
|
from gi.repository import GdkPixbuf
|
|
import base64
|
|
import time
|
|
import logging
|
|
import hashlib
|
|
|
|
from gajim import gtkgui_helpers
|
|
from gajim import dialogs
|
|
from gajim.filechoosers import AvatarChooserDialog
|
|
from gajim.common.const import AvatarSize
|
|
|
|
from gajim.common import app
|
|
from gajim.common import ged
|
|
|
|
log = logging.getLogger('gajim.profile')
|
|
|
|
class ProfileWindow:
|
|
"""
|
|
Class for our information window
|
|
"""
|
|
|
|
def __init__(self, account, transient_for=None):
|
|
self.xml = gtkgui_helpers.get_gtk_builder('profile_window.ui')
|
|
self.window = self.xml.get_object('profile_window')
|
|
self.window.set_transient_for(transient_for)
|
|
self.progressbar = self.xml.get_object('progressbar')
|
|
self.statusbar = self.xml.get_object('statusbar')
|
|
self.context_id = self.statusbar.get_context_id('profile')
|
|
|
|
self.account = account
|
|
self.jid = app.get_jid_from_account(account)
|
|
|
|
self.dialog = None
|
|
self.avatar_mime_type = None
|
|
self.avatar_encoded = None
|
|
self.avatar_sha = None
|
|
self.message_id = self.statusbar.push(self.context_id,
|
|
_('Retrieving profile…'))
|
|
self.update_progressbar_timeout_id = GLib.timeout_add(100,
|
|
self.update_progressbar)
|
|
self.remove_statusbar_timeout_id = None
|
|
|
|
# Create Image for avatar button
|
|
image = Gtk.Image()
|
|
self.xml.get_object('PHOTO_button').set_image(image)
|
|
self.xml.connect_signals(self)
|
|
app.ged.register_event_handler('vcard-published', ged.GUI1,
|
|
self._nec_vcard_published)
|
|
app.ged.register_event_handler('vcard-not-published', ged.GUI1,
|
|
self._nec_vcard_not_published)
|
|
self.window.show_all()
|
|
self.xml.get_object('ok_button').grab_focus()
|
|
app.connections[account].request_vcard(
|
|
self._nec_vcard_received, self.jid)
|
|
|
|
def on_information_notebook_switch_page(self, widget, page, page_num):
|
|
GLib.idle_add(self.xml.get_object('ok_button').grab_focus)
|
|
|
|
def update_progressbar(self):
|
|
self.progressbar.pulse()
|
|
return True # loop forever
|
|
|
|
def remove_statusbar(self, message_id):
|
|
self.statusbar.remove(self.context_id, message_id)
|
|
self.remove_statusbar_timeout_id = None
|
|
|
|
def on_profile_window_destroy(self, widget):
|
|
if self.update_progressbar_timeout_id is not None:
|
|
GLib.source_remove(self.update_progressbar_timeout_id)
|
|
if self.remove_statusbar_timeout_id is not None:
|
|
GLib.source_remove(self.remove_statusbar_timeout_id)
|
|
app.ged.remove_event_handler('vcard-published', ged.GUI1,
|
|
self._nec_vcard_published)
|
|
app.ged.remove_event_handler('vcard-not-published', ged.GUI1,
|
|
self._nec_vcard_not_published)
|
|
del app.interface.instances[self.account]['profile']
|
|
if self.dialog: # Image chooser dialog
|
|
self.dialog.destroy()
|
|
|
|
def on_profile_window_key_press_event(self, widget, event):
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
self.window.destroy()
|
|
|
|
def _clear_photo(self, widget):
|
|
# empty the image
|
|
button = self.xml.get_object('PHOTO_button')
|
|
image = button.get_image()
|
|
image.set_from_pixbuf(None)
|
|
button.hide()
|
|
text_button = self.xml.get_object('NOPHOTO_button')
|
|
text_button.show()
|
|
remove_avatar = self.xml.get_object('remove_avatar')
|
|
remove_avatar.hide()
|
|
self.avatar_encoded = None
|
|
self.avatar_sha = None
|
|
self.avatar_mime_type = None
|
|
|
|
def on_set_avatar_button_clicked(self, widget):
|
|
def on_ok(path_to_file):
|
|
with open(path_to_file, 'rb') as file:
|
|
data = file.read()
|
|
sha = app.interface.save_avatar(data, publish=True)
|
|
if sha is None:
|
|
dialogs.ErrorDialog(
|
|
_('Could not load image'), transient_for=self.window)
|
|
return
|
|
|
|
scale = self.window.get_scale_factor()
|
|
surface = app.interface.get_avatar(sha, AvatarSize.VCARD, scale)
|
|
|
|
button = self.xml.get_object('PHOTO_button')
|
|
image = button.get_image()
|
|
image.set_from_surface(surface)
|
|
button.show()
|
|
text_button = self.xml.get_object('NOPHOTO_button')
|
|
text_button.hide()
|
|
|
|
remove_avatar = self.xml.get_object('remove_avatar')
|
|
remove_avatar.show()
|
|
|
|
self.avatar_sha = sha
|
|
publish = app.interface.get_avatar(sha, publish=True)
|
|
self.avatar_encoded = base64.b64encode(publish).decode('utf-8')
|
|
self.avatar_mime_type = 'image/png'
|
|
|
|
AvatarChooserDialog(on_ok, transient_for=self.window)
|
|
|
|
def on_PHOTO_button_press_event(self, widget, event):
|
|
"""
|
|
If right-clicked, show popup
|
|
"""
|
|
pixbuf = self.xml.get_object('PHOTO_button').get_image().get_pixbuf()
|
|
if event.button == 3 and pixbuf: # right click
|
|
menu = Gtk.Menu()
|
|
|
|
nick = app.config.get_per('accounts', self.account, 'name')
|
|
sha = app.contacts.get_avatar_sha(self.account, self.jid)
|
|
menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As'))
|
|
menuitem.connect('activate',
|
|
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
|
sha, nick)
|
|
menu.append(menuitem)
|
|
# show clear
|
|
menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
|
|
menuitem.connect('activate', self.on_clear_button_clicked)
|
|
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)
|
|
elif event.button == 1: # left click
|
|
self.on_set_avatar_button_clicked(widget)
|
|
|
|
def on_BDAY_entry_focus_out_event(self, widget, event):
|
|
txt = widget.get_text()
|
|
if not txt:
|
|
return
|
|
try:
|
|
time.strptime(txt, '%Y-%m-%d')
|
|
except ValueError:
|
|
if not widget.is_focus():
|
|
pritext = _('Wrong date format')
|
|
dialogs.ErrorDialog(pritext, _('Format of the date must be '
|
|
'YYYY-MM-DD'), transient_for=self.window)
|
|
GLib.idle_add(lambda: widget.grab_focus())
|
|
return True
|
|
|
|
def set_value(self, entry_name, value):
|
|
try:
|
|
widget = self.xml.get_object(entry_name)
|
|
val = widget.get_text()
|
|
if val:
|
|
value = val + ' / ' + value
|
|
widget.set_text(value)
|
|
except AttributeError:
|
|
pass
|
|
|
|
def set_values(self, vcard_):
|
|
button = self.xml.get_object('PHOTO_button')
|
|
image = button.get_image()
|
|
text_button = self.xml.get_object('NOPHOTO_button')
|
|
remove_avatar = self.xml.get_object('remove_avatar')
|
|
if not 'PHOTO' in vcard_:
|
|
# set default image
|
|
image.set_from_pixbuf(None)
|
|
button.hide()
|
|
text_button.show()
|
|
remove_avatar.hide()
|
|
for i in vcard_.keys():
|
|
if i == 'PHOTO':
|
|
photo_encoded = vcard_[i]['BINVAL']
|
|
if photo_encoded == '':
|
|
continue
|
|
self.avatar_encoded = photo_encoded
|
|
photo_decoded = base64.b64decode(photo_encoded.encode('utf-8'))
|
|
self.avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
|
|
if 'TYPE' in vcard_[i]:
|
|
self.avatar_mime_type = vcard_[i]['TYPE']
|
|
pixbuf = gtkgui_helpers.get_pixbuf_from_data(photo_decoded)
|
|
if pixbuf is None:
|
|
continue
|
|
pixbuf = pixbuf.scale_simple(
|
|
AvatarSize.PROFILE, AvatarSize.PROFILE,
|
|
GdkPixbuf.InterpType.BILINEAR)
|
|
image.set_from_pixbuf(pixbuf)
|
|
button.show()
|
|
remove_avatar.show()
|
|
text_button.hide()
|
|
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_object('DESC_textview').get_buffer().set_text(
|
|
vcard_[i], len(vcard_[i].encode('utf-8')))
|
|
else:
|
|
self.set_value(i + '_entry', vcard_[i])
|
|
if self.update_progressbar_timeout_id is not None:
|
|
if self.message_id:
|
|
self.statusbar.remove(self.context_id, self.message_id)
|
|
self.message_id = self.statusbar.push(self.context_id,
|
|
_('Information received'))
|
|
self.remove_statusbar_timeout_id = GLib.timeout_add_seconds(3,
|
|
self.remove_statusbar, self.message_id)
|
|
GLib.source_remove(self.update_progressbar_timeout_id)
|
|
self.progressbar.hide()
|
|
self.progressbar.set_fraction(0)
|
|
self.update_progressbar_timeout_id = None
|
|
|
|
def _nec_vcard_received(self, jid, resource, room, vcard_):
|
|
self.set_values(vcard_)
|
|
|
|
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 entries[0] not in loc:
|
|
loc[entries[0]] = []
|
|
found = False
|
|
for e in loc[entries[0]]:
|
|
if entries[1] in e:
|
|
e[entries[2]] = txt
|
|
break
|
|
else:
|
|
loc[entries[0]].append({entries[1]: '', entries[2]: txt})
|
|
return vcard_
|
|
while len(entries) > 1:
|
|
if entries[0] not in loc:
|
|
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_object(e + '_entry').get_text()
|
|
if txt != '':
|
|
vcard_ = self.add_to_vcard(vcard_, e, txt)
|
|
|
|
# DESC textview
|
|
buff = self.xml.get_object('DESC_textview').get_buffer()
|
|
start_iter = buff.get_start_iter()
|
|
end_iter = buff.get_end_iter()
|
|
txt = buff.get_text(start_iter, end_iter, False)
|
|
if txt != '':
|
|
vcard_['DESC'] = txt
|
|
|
|
# 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_, self.avatar_sha
|
|
|
|
def on_ok_button_clicked(self, widget):
|
|
if self.update_progressbar_timeout_id:
|
|
# Operation in progress
|
|
return
|
|
if app.connections[self.account].connected < 2:
|
|
dialogs.ErrorDialog(_('You are not connected to the server'),
|
|
_('Without a connection, you can not publish your contact '
|
|
'information.'), transient_for=self.window)
|
|
return
|
|
vcard_, sha = self.make_vcard()
|
|
nick = ''
|
|
if 'NICKNAME' in vcard_:
|
|
nick = vcard_['NICKNAME']
|
|
app.connections[self.account].send_nickname(nick)
|
|
if nick == '':
|
|
app.connections[self.account].retract_nickname()
|
|
nick = app.config.get_per('accounts', self.account, 'name')
|
|
app.nicks[self.account] = nick
|
|
app.connections[self.account].send_vcard(vcard_, sha)
|
|
self.message_id = self.statusbar.push(self.context_id,
|
|
_('Sending profile…'))
|
|
self.progressbar.show()
|
|
self.update_progressbar_timeout_id = GLib.timeout_add(100,
|
|
self.update_progressbar)
|
|
|
|
def _nec_vcard_published(self, obj):
|
|
if obj.conn.name != self.account:
|
|
return
|
|
if self.update_progressbar_timeout_id is not None:
|
|
GLib.source_remove(self.update_progressbar_timeout_id)
|
|
self.update_progressbar_timeout_id = None
|
|
self.window.destroy()
|
|
|
|
def _nec_vcard_not_published(self, obj):
|
|
if obj.conn.name != self.account:
|
|
return
|
|
if self.message_id:
|
|
self.statusbar.remove(self.context_id, self.message_id)
|
|
self.message_id = self.statusbar.push(self.context_id,
|
|
_('Information NOT published'))
|
|
self.remove_statusbar_timeout_id = GLib.timeout_add_seconds(3,
|
|
self.remove_statusbar, self.message_id)
|
|
if self.update_progressbar_timeout_id is not None:
|
|
GLib.source_remove(self.update_progressbar_timeout_id)
|
|
self.progressbar.set_fraction(0)
|
|
self.update_progressbar_timeout_id = None
|
|
dialogs.InformationDialog(_('vCard publication failed'),
|
|
_('There was an error while publishing your personal information, '
|
|
'try again later.'), transient_for=self.window)
|
|
|
|
def on_cancel_button_clicked(self, widget):
|
|
self.window.destroy()
|