profile.py 10.9 KB
Newer Older
Philipp Hörist's avatar
Philipp Hörist committed
1
import logging
Yann Leboulanger's avatar
Yann Leboulanger committed
2

Daniel Brötzmann's avatar
Daniel Brötzmann committed
3
from gi.repository import Gio
4
from gi.repository import Gdk
Daniel Brötzmann's avatar
Daniel Brötzmann committed
5
from gi.repository import Gtk
6
7
from gi.repository import GLib

Daniel Brötzmann's avatar
Daniel Brötzmann committed
8
9
10
11
from nbxmpp.errors import StanzaError
from nbxmpp.namespaces import Namespace
from nbxmpp.modules.vcard4 import VCard
from nbxmpp.modules.user_avatar import Avatar
12

13
14
from gajim.common import app
from gajim.common.const import AvatarSize
Daniel Brötzmann's avatar
Daniel Brötzmann committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from gajim.common.i18n import _
from gajim.common.i18n import Q_

from gajim.gui.avatar import clip_circle
from gajim.gui.avatar_selector import AvatarSelector
from gajim.gui.filechoosers import AvatarChooserDialog
from gajim.gui.util import get_builder
from gajim.gui.vcard_grid import VCardGrid
from gajim.gui.util import scroll_to_end

log = logging.getLogger('gajim.gui.profile')

MENU_DICT = {
    'fn': _('Full Name'),
    'bday': _('Birthday'),
    'gender': _('Gender'),
    'adr': _('Address'),
    'email': _('Email'),
    'impp': 'IM Address',
    'tel': _('Phone No.'),
    'org': Q_('?profile:Organisation'),
    'title': Q_('?profile:Title'),
    'role': Q_('?profile:Role'),
    'url': _('URL'),
39
    'key': Q_('?profile:Public Encryption Key'),
Daniel Brötzmann's avatar
Daniel Brötzmann committed
40
}
Yann Leboulanger's avatar
Yann Leboulanger committed
41

42

43
class ProfileWindow(Gtk.ApplicationWindow):
Daniel Brötzmann's avatar
Daniel Brötzmann committed
44
    def __init__(self, account, *args):
45
46
47
48
        Gtk.ApplicationWindow.__init__(self)
        self.set_application(app.app)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_show_menubar(False)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
49
50
51
52
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_resizable(True)
        self.set_default_size(700, 600)
        self.set_name('ProfileWindow')
53
54
        self.set_title(_('Profile'))

55
        self.account = account
Daniel Brötzmann's avatar
Daniel Brötzmann committed
56
        self._jid = app.get_jid_from_account(account)
57

Daniel Brötzmann's avatar
Daniel Brötzmann committed
58
        self._ui = get_builder('profile.ui')
59

Daniel Brötzmann's avatar
Daniel Brötzmann committed
60
61
62
        menu = Gio.Menu()
        for action, label in MENU_DICT.items():
            menu.append(label, 'win.add-' + action.lower())
63

Daniel Brötzmann's avatar
Daniel Brötzmann committed
64
65
        self._ui.add_entry_button.set_menu_model(menu)
        self._add_actions()
66

Daniel Brötzmann's avatar
Daniel Brötzmann committed
67
68
        self._avatar_selector = None
        self._current_avatar = None
69
        self._current_vcard = None
Daniel Brötzmann's avatar
Daniel Brötzmann committed
70
        self._avatar_nick_public = None
71

Daniel Brötzmann's avatar
Daniel Brötzmann committed
72
73
74
75
        # False  - no change to avatar
        # None   - we want to delete the avatar
        # Avatar - upload new avatar
        self._new_avatar = False
76

Daniel Brötzmann's avatar
Daniel Brötzmann committed
77
        self._ui.nickname_entry.set_text(app.nicks[account])
78

79
80
        self._vcard_grid = VCardGrid(self.account)
        self._ui.profile_box.add(self._vcard_grid)
81

Daniel Brötzmann's avatar
Daniel Brötzmann committed
82
83
        self.add(self._ui.profile_stack)
        self.show_all()
84

Daniel Brötzmann's avatar
Daniel Brötzmann committed
85
        self._load_avatar()
86

Daniel Brötzmann's avatar
Daniel Brötzmann committed
87
88
89
        client = app.get_client(account)
        client.get_module('VCard4').request_vcard(
            callback=self._on_vcard_received)
90

Daniel Brötzmann's avatar
Daniel Brötzmann committed
91
92
93
94
        client.get_module('PubSub').get_access_model(
            Namespace.VCARD4_PUBSUB,
            callback=self._on_access_model_received,
            user_data=Namespace.VCARD4_PUBSUB)
95

Daniel Brötzmann's avatar
Daniel Brötzmann committed
96
97
98
99
        client.get_module('PubSub').get_access_model(
            Namespace.AVATAR_METADATA,
            callback=self._on_access_model_received,
            user_data=Namespace.AVATAR_METADATA)
100

Daniel Brötzmann's avatar
Daniel Brötzmann committed
101
102
103
104
        client.get_module('PubSub').get_access_model(
            Namespace.AVATAR_DATA,
            callback=self._on_access_model_received,
            user_data=Namespace.AVATAR_DATA)
105

Daniel Brötzmann's avatar
Daniel Brötzmann committed
106
107
108
109
        client.get_module('PubSub').get_access_model(
            Namespace.NICK,
            callback=self._on_access_model_received,
            user_data=Namespace.NICK)
110

Daniel Brötzmann's avatar
Daniel Brötzmann committed
111
112
        self._ui.connect_signals(self)
        self.connect('key-press-event', self._on_key_press_event)
113

Daniel Brötzmann's avatar
Daniel Brötzmann committed
114
115
    def _on_access_model_received(self, task):
        namespace = task.get_user_data()
116

Daniel Brötzmann's avatar
Daniel Brötzmann committed
117
118
119
120
121
        try:
            result = task.finish()
        except StanzaError as error:
            log.warning('Unable to get access model for %s: %s',
                        namespace, error)
122
123
            return

Daniel Brötzmann's avatar
Daniel Brötzmann committed
124
125
126
127
128
129
130
131
132
133
134
        access_model = result == 'open'

        if namespace == Namespace.VCARD4_PUBSUB:
            self._ui.vcard_access.set_active(access_model)
        else:
            if self._avatar_nick_public is None:
                self._avatar_nick_public = access_model
            else:
                self._avatar_nick_public = (self._avatar_nick_public and
                                            access_model)
            self._ui.avatar_nick_access.set_active(self._avatar_nick_public)
135

Daniel Brötzmann's avatar
Daniel Brötzmann committed
136
    def _on_vcard_received(self, task):
137
        try:
138
            self._current_vcard = task.finish()
Daniel Brötzmann's avatar
Daniel Brötzmann committed
139
140
        except StanzaError as error:
            log.info('Error loading VCard: %s', error)
141
            self._current_vcard = VCard()
142

143
144
145
        if self._current_vcard is None:
            self._current_vcard = VCard()

Daniel Brötzmann's avatar
Daniel Brötzmann committed
146
        self._load_avatar()
147
        self._vcard_grid.set_vcard(self._current_vcard.copy())
Daniel Brötzmann's avatar
Daniel Brötzmann committed
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
        self._ui.profile_stack.set_visible_child_name('profile')
        self._ui.spinner.stop()

    def _load_avatar(self):
        scale = self.get_scale_factor()
        self._current_avatar = app.contacts.get_avatar(
            self.account,
            self._jid,
            AvatarSize.VCARD,
            scale)

        self._ui.avatar_image.set_from_surface(self._current_avatar)
        self._ui.avatar_image.show()

    def _on_key_press_event(self, _widget, event):
        if event.keyval == Gdk.KEY_Escape:
164
165
            self.destroy()

Daniel Brötzmann's avatar
Daniel Brötzmann committed
166
167
168
169
170
171
172
173
174
175
    def _add_actions(self):
        for action in MENU_DICT:
            action_name = 'add-' + action.lower()
            act = Gio.SimpleAction.new(action_name, None)
            act.connect('activate', self._on_action)
            self.add_action(act)

    def _on_action(self, action, _param):
        name = action.get_name()
        key = name.split('-')[1]
176
        self._vcard_grid.add_new_property(key)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
177
178
179
        GLib.idle_add(scroll_to_end, self._ui.scrolled)

    def _on_edit_clicked(self, *args):
180
        self._vcard_grid.set_editable(True)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
181
182
183
184
185
186
187
188
189
190
191
        self._ui.edit_button.hide()
        self._ui.add_entry_button.set_no_show_all(False)
        self._ui.add_entry_button.show_all()
        self._ui.cancel_button.show()
        self._ui.save_button.show()
        self._ui.remove_avatar_button.show()
        self._ui.edit_avatar_button.show()
        self._ui.nickname_entry.set_sensitive(True)
        self._ui.privacy_button.show()

    def _on_cancel_clicked(self, _widget):
192
        self._vcard_grid.set_editable(False)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
193
194
195
196
197
198
199
200
201
202
        self._ui.edit_button.show()
        self._ui.add_entry_button.hide()
        self._ui.cancel_button.hide()
        self._ui.save_button.hide()
        self._ui.remove_avatar_button.hide()
        self._ui.edit_avatar_button.hide()
        self._ui.privacy_button.hide()
        self._ui.nickname_entry.set_sensitive(False)
        self._ui.avatar_image.set_from_surface(self._current_avatar)
        self._ui.nickname_entry.set_text(app.nicks[self.account])
203
        self._vcard_grid.set_vcard(self._current_vcard.copy())
Daniel Brötzmann's avatar
Daniel Brötzmann committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
        self._new_avatar = False

    def _on_save_clicked(self, _widget):
        self._ui.spinner.start()
        self._ui.profile_stack.set_visible_child_name('spinner')
        self._ui.add_entry_button.hide()
        self._ui.cancel_button.hide()
        self._ui.save_button.hide()
        self._ui.edit_button.show()
        self._ui.remove_avatar_button.hide()
        self._ui.edit_avatar_button.hide()
        self._ui.privacy_button.hide()
        self._ui.nickname_entry.set_sensitive(False)

218
        self._vcard_grid.validate()
Philipp Hörist's avatar
Philipp Hörist committed
219
        self._vcard_grid.sort()
220
221
222

        vcard = self._vcard_grid.get_vcard()
        self._current_vcard = vcard.copy()
Daniel Brötzmann's avatar
Daniel Brötzmann committed
223
224
225

        con = app.connections[self.account]
        con.get_module('VCard4').set_vcard(
226
            self._current_vcard,
Daniel Brötzmann's avatar
Daniel Brötzmann committed
227
228
229
230
231
232
233
234
            public=self._ui.vcard_access.get_active(),
            callback=self._on_save_finished)

        public = self._ui.avatar_nick_access.get_active()

        if self._new_avatar is False:
            if self._avatar_nick_public != public:
                con.get_module('UserAvatar').set_access_model(public)
235

Daniel Brötzmann's avatar
Daniel Brötzmann committed
236
237
238
239
240
241
242
        else:
            # Only update avatar if it changed
            con.get_module('UserAvatar').set_avatar(self._new_avatar,
                                                    public=public)

        nick = GLib.markup_escape_text(self._ui.nickname_entry.get_text())
        con.get_module('UserNickname').set_nickname(nick, public=public)
243
244

        if not nick:
Daniel Brötzmann's avatar
Daniel Brötzmann committed
245
246
            nick = app.settings.get_account_setting(
                self.account, 'name')
247
        app.nicks[self.account] = nick
248

Daniel Brötzmann's avatar
Daniel Brötzmann committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    def _on_remove_avatar(self, _button):
        contact = app.contacts.create_contact(self._jid, self.account)
        scale = self.get_scale_factor()
        surface = app.interface.avatar_storage.get_surface(
            contact, AvatarSize.VCARD, scale, default=True)

        self._ui.avatar_image.set_from_surface(surface)
        self._ui.remove_avatar_button.hide()
        self._new_avatar = None

    def _on_edit_avatar(self, button):
        def _on_file_selected(path):
            if self._avatar_selector is None:
                self._avatar_selector = AvatarSelector()
                self._ui.avatar_selector_box.add(self._avatar_selector)

            self._avatar_selector.prepare_crop_area(path)
            self._ui.avatar_update_button.set_sensitive(
                self._avatar_selector.get_prepared())
            self._ui.profile_stack.set_visible_child_name('avatar_selector')

        AvatarChooserDialog(_on_file_selected,
                            transient_for=button.get_toplevel())

    def _on_cancel_update_avatar(self, _button):
        self._ui.profile_stack.set_visible_child_name('profile')

    def _on_update_avatar(self, _button):
        success, data, width, height = self._avatar_selector.get_avatar_bytes()
        if not success:
            # TODO: Error handling
            return

        sha = app.interface.avatar_storage.save_avatar(data)
        if sha is None:
            # TODO: Error handling
            return

        self._new_avatar = Avatar()
        self._new_avatar.add_image_source(data, 'image/png', height, width)

        scale = self.get_scale_factor()
        surface = app.interface.avatar_storage.surface_from_filename(
            sha, AvatarSize.VCARD, scale)

        self._ui.avatar_image.set_from_surface(clip_circle(surface))
        self._ui.remove_avatar_button.show()
        self._ui.profile_stack.set_visible_child_name('profile')

    def _access_switch_toggled(self, *args):
        avatar_nick_access = self._ui.avatar_nick_access.get_active()
        vcard_access = self._ui.vcard_access.get_active()
        self._ui.avatar_nick_access_label.set_text(
            _('Everyone') if avatar_nick_access else _('Contacts'))
        self._ui.vcard_access_label.set_text(
            _('Everyone') if vcard_access else _('Contacts'))

    def _on_save_finished(self, task):
        try:
            task.finish()
        except StanzaError as err:
            log.error('Could not publish VCard: %s', err)
            # TODO Handle error
            return

314
        self._vcard_grid.set_editable(False)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
315
316
        self._ui.profile_stack.set_visible_child_name('profile')
        self._ui.spinner.stop()