tooltips.py 23.6 KB
Newer Older
Philipp Hörist's avatar
Philipp Hörist committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
#                    Stéphan Kochen <stephan AT kochen.nl>
# Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
# Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
#                    Stefan Bethge <stefan AT lanpartei.de>
# Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
# Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
# Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
# Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.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/>.
26

Philipp Hörist's avatar
Philipp Hörist committed
27
28
import os
import time
Philipp Hörist's avatar
Philipp Hörist committed
29
import logging
Philipp Hörist's avatar
Philipp Hörist committed
30
31
from datetime import datetime

32
from gi.repository import Gtk
Yann Leboulanger's avatar
Yann Leboulanger committed
33
from gi.repository import GLib
34
from gi.repository import Pango
35

36
from gajim.common import app
André's avatar
André committed
37
from gajim.common import helpers
Philipp Hörist's avatar
Philipp Hörist committed
38
from gajim.common.const import AvatarSize
39
from gajim.common.const import PEPEventType
André's avatar
André committed
40
from gajim.common.i18n import Q_
41
from gajim.common.i18n import _
42

Philipp Hörist's avatar
Philipp Hörist committed
43
from gajim.gtk.util import get_builder
44
from gajim.gtk.util import get_icon_name
45
from gajim.gtk.util import format_mood
Philipp Hörist's avatar
Philipp Hörist committed
46
from gajim.gtk.util import format_activity
Philipp Hörist's avatar
Philipp Hörist committed
47
from gajim.gtk.util import format_tune
Philipp Hörist's avatar
Philipp Hörist committed
48
from gajim.gtk.util import format_location
Philipp Hörist's avatar
Philipp Hörist committed
49

Philipp Hörist's avatar
Philipp Hörist committed
50

51
log = logging.getLogger('gajim.gtk.tooltips')
Philipp Hörist's avatar
Philipp Hörist committed
52

53
54

class StatusTable:
55
56
57
58
59
60
    """
    Contains methods for creating status table. This is used in Roster and
    NotificationArea tooltips
    """

    def __init__(self):
lovetox's avatar
lovetox committed
61
        self.current_row = 0
62
63
64
65
66
        self.table = None
        self.text_label = None
        self.spacer_label = '   '

    def create_table(self):
67
68
        self.table = Gtk.Grid()
        self.table.insert_column(0)
69
        self.table.set_property('column-spacing', 3)
70

lovetox's avatar
lovetox committed
71
72
    def add_text_row(self, text, col_inc=0):
        self.table.insert_row(self.current_row)
73
        self.text_label = Gtk.Label()
74
        self.text_label.set_line_wrap(True)
75
        self.text_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
76
77
78
        self.text_label.set_lines(3)
        self.text_label.set_ellipsize(Pango.EllipsizeMode.END)
        self.text_label.set_max_width_chars(30)
79
80
        self.text_label.set_halign(Gtk.Align.START)
        self.text_label.set_valign(Gtk.Align.START)
81
        self.text_label.set_xalign(0)
82
        self.text_label.set_selectable(False)
83
84

        self.text_label.set_text(text)
Philipp Hörist's avatar
Philipp Hörist committed
85
86
87
88
        self.table.attach(self.text_label, 1 + col_inc,
                          self.current_row,
                          3 - col_inc,
                          1)
lovetox's avatar
lovetox committed
89
        self.current_row += 1
90

91
92
    def add_status_row(self, show, str_status, show_lock=False,
                       indent=True, transport=None):
93
94
95
        """
        Append a new row with status icon to the table
        """
lovetox's avatar
lovetox committed
96
        self.table.insert_row(self.current_row)
97
        image = Gtk.Image()
98
        icon_name = get_icon_name(show, transport=transport)
99
        image.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
100
        spacer = Gtk.Label(label=self.spacer_label)
101
        image.set_halign(Gtk.Align.START)
102
        image.set_valign(Gtk.Align.CENTER)
103
        if indent:
104
105
            self.table.attach(spacer, 1, self.current_row, 1, 1)
        self.table.attach(image, 2, self.current_row, 1, 1)
106
        status_label = Gtk.Label()
107
        status_label.set_text(str_status)
108
109
        status_label.set_halign(Gtk.Align.START)
        status_label.set_valign(Gtk.Align.START)
110
        status_label.set_xalign(0)
111
        status_label.set_line_wrap(True)
112
113
114
115
        status_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
        status_label.set_lines(3)
        status_label.set_ellipsize(Pango.EllipsizeMode.END)
        status_label.set_max_width_chars(30)
116
        self.table.attach(status_label, 3, self.current_row, 1, 1)
117
        if show_lock:
118
            lock_image = Gtk.Image()
Philipp Hörist's avatar
Philipp Hörist committed
119
            lock_image.set_from_icon_name('dialog-password', Gtk.IconSize.MENU)
120
            self.table.attach(lock_image, 4, self.current_row, 1, 1)
lovetox's avatar
lovetox committed
121
        self.current_row += 1
122

123
124
125
126
    def fill_table_with_accounts(self, accounts):
        for acct in accounts:
            message = acct['message']
            message = helpers.reduce_chars_newlines(message, 100, 1)
Yann Leboulanger's avatar
Yann Leboulanger committed
127
            message = GLib.markup_escape_text(message)
128
129
130
            con_type = app.con_types.get(acct['name'])
            show_lock = con_type in ('tls', 'ssl')

Philipp Hörist's avatar
Philipp Hörist committed
131
            account_label = GLib.markup_escape_text(acct['account_label'])
132
            if message:
Philipp Hörist's avatar
Philipp Hörist committed
133
                status = '%s - %s' % (account_label, message)
134
            else:
Philipp Hörist's avatar
Philipp Hörist committed
135
136
                status = account_label

137
            self.add_status_row(acct['show'],
Philipp Hörist's avatar
Philipp Hörist committed
138
139
140
141
                                status,
                                show_lock=show_lock,
                                indent=False)

142
143
144
            for line in acct['event_lines']:
                self.add_text_row('  ' + line, 1)

Philipp Hörist's avatar
Philipp Hörist committed
145
146
147
148
149
150
151
152
153

class NotificationAreaTooltip(StatusTable):
    """
    Tooltip that is shown in the notification area
    """

    def __init__(self):
        StatusTable.__init__(self)

154
    def get_tooltip(self):
155
156
157
158
159
160
        self.create_table()

        accounts = helpers.get_notification_icon_tooltip_dict()
        self.fill_table_with_accounts(accounts)
        self.table.set_property('column-spacing', 1)

Philipp Hörist's avatar
Philipp Hörist committed
161
162
163
164
        hbox = Gtk.HBox()
        hbox.add(self.table)
        hbox.show_all()
        return hbox
165

nkour's avatar
nkour committed
166

Philipp Hörist's avatar
Philipp Hörist committed
167
168
169
class GCTooltip():
    def __init__(self):
        self.contact = None
lovetox's avatar
lovetox committed
170

Philipp Hörist's avatar
Philipp Hörist committed
171
        self._ui = get_builder('tooltip_gc_contact.ui')
lovetox's avatar
lovetox committed
172
173

    def clear_tooltip(self):
Philipp Hörist's avatar
Philipp Hörist committed
174
175
176
177
        self.contact = None

    def get_tooltip(self, contact):
        if self.contact == contact:
Philipp Hörist's avatar
Philipp Hörist committed
178
            return True, self._ui.tooltip_grid
Philipp Hörist's avatar
Philipp Hörist committed
179
180
181

        self._populate_grid(contact)
        self.contact = contact
Philipp Hörist's avatar
Philipp Hörist committed
182
        return False, self._ui.tooltip_grid
Philipp Hörist's avatar
Philipp Hörist committed
183
184

    def _hide_grid_childs(self):
lovetox's avatar
lovetox committed
185
186
187
        """
        Hide all Elements of the Tooltip Grid
        """
Philipp Hörist's avatar
Philipp Hörist committed
188
        for child in self._ui.tooltip_grid.get_children():
lovetox's avatar
lovetox committed
189
            child.hide()
190

Philipp Hörist's avatar
Philipp Hörist committed
191
    def _populate_grid(self, contact):
lovetox's avatar
lovetox committed
192
193
194
        """
        Populate the Tooltip Grid with data of from the contact
        """
Philipp Hörist's avatar
Philipp Hörist committed
195
        self._hide_grid_childs()
196

Philipp Hörist's avatar
Philipp Hörist committed
197
198
        self._ui.nick.set_text(contact.get_shown_name())
        self._ui.nick.show()
199

lovetox's avatar
lovetox committed
200
201
        # Status Message
        if contact.status:
202
203
            status = contact.status.strip()
            if status != '':
Philipp Hörist's avatar
Philipp Hörist committed
204
205
                self._ui.status.set_text(status)
                self._ui.status.show()
206

lovetox's avatar
lovetox committed
207
        # Status
Philipp Hörist's avatar
Philipp Hörist committed
208
        show = helpers.get_uf_show(contact.show.value)
Philipp Hörist's avatar
Philipp Hörist committed
209
210
        self._ui.user_show.set_markup(colorize_status(show))
        self._ui.user_show.show()
211

lovetox's avatar
lovetox committed
212
        # JID
Philipp Hörist's avatar
Philipp Hörist committed
213
214
        if contact.jid is not None:
            self._ui.jid.set_text(str(contact.jid))
Philipp Hörist's avatar
Philipp Hörist committed
215
            self._ui.jid.show()
216

lovetox's avatar
lovetox committed
217
        # Affiliation
Philipp Hörist's avatar
Philipp Hörist committed
218
        if not contact.affiliation.is_none:
Philipp Hörist's avatar
Philipp Hörist committed
219
            uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
220
            uf_affiliation = \
lovetox's avatar
lovetox committed
221
222
                _('%(owner_or_admin_or_member)s of this group chat') \
                % {'owner_or_admin_or_member': uf_affiliation}
223
            uf_affiliation = self.colorize_affiliation(uf_affiliation)
Philipp Hörist's avatar
Philipp Hörist committed
224
225
            self._ui.affiliation.set_markup(uf_affiliation)
            self._ui.affiliation.show()
226

lovetox's avatar
lovetox committed
227
        # Avatar
Philipp Hörist's avatar
Philipp Hörist committed
228
229
230
        if contact.avatar_sha is not None:
            app.log('avatar').debug(
                'Load GCTooltip: %s %s', contact.name, contact.avatar_sha)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
231
232
233
234
235
236
        scale = self._ui.tooltip_grid.get_scale_factor()
        surface = app.interface.get_avatar(
            contact, AvatarSize.TOOLTIP, scale)
        self._ui.avatar.set_from_surface(surface)
        self._ui.avatar.show()
        self._ui.fillelement.show()
237

238
        app.plugin_manager.gui_extension_point(
Philipp Hörist's avatar
Philipp Hörist committed
239
            'gc_tooltip_populate', self, contact, self._ui.tooltip_grid)
240

lovetox's avatar
lovetox committed
241
242
243
244
245
246
247
248
249
    @staticmethod
    def colorize_affiliation(affiliation):
        """
        Color the affiliation of a MUC participant inside the tooltip by
        it's semantics. Color palette is the Tango.
        """
        formatted = "<span foreground='%s'>%s</span>"
        color = None
        if affiliation.startswith(Q_("?Group Chat Contact Affiliation:None")):
250
            color = app.config.get('tooltip_affiliation_none_color')
lovetox's avatar
lovetox committed
251
        elif affiliation.startswith(_("Member")):
252
            color = app.config.get('tooltip_affiliation_member_color')
lovetox's avatar
lovetox committed
253
        elif affiliation.startswith(_("Administrator")):
254
            color = app.config.get('tooltip_affiliation_administrator_color')
lovetox's avatar
lovetox committed
255
        elif affiliation.startswith(_("Owner")):
256
            color = app.config.get('tooltip_affiliation_owner_color')
lovetox's avatar
lovetox committed
257
258
259
        if color:
            affiliation = formatted % (color, affiliation)
        return affiliation
nkour's avatar
nkour committed
260

Philipp Hörist's avatar
Philipp Hörist committed
261
262
263
    def destroy(self):
        self._ui.tooltip_grid.destroy()

264
265
class RosterTooltip(StatusTable):
    def __init__(self):
lovetox's avatar
lovetox committed
266
267
        StatusTable.__init__(self)
        self.create_table()
Philipp Hörist's avatar
Philipp Hörist committed
268
        self.account = None
lovetox's avatar
lovetox committed
269
270
        self.row = None
        self.contact_jid = None
Philipp Hörist's avatar
Philipp Hörist committed
271
        self.prim_contact = None
lovetox's avatar
lovetox committed
272
273
        self.last_widget = None
        self.num_resources = 0
274

Philipp Hörist's avatar
Philipp Hörist committed
275
        self._ui = get_builder('tooltip_roster_contact.ui')
276

lovetox's avatar
lovetox committed
277
278
279
280
    def clear_tooltip(self):
        """
        Hide all Elements of the Tooltip Grid
        """
Philipp Hörist's avatar
Philipp Hörist committed
281
        for child in self._ui.tooltip_grid.get_children():
lovetox's avatar
lovetox committed
282
            child.hide()
283
        status_table = self._ui.tooltip_grid.get_child_at(1, 3)
lovetox's avatar
lovetox committed
284
285
286
        if status_table:
            status_table.destroy()
            self.create_table()
287
288
289
290
291
292
293
294
295
        self.row = None

    def get_tooltip(self, row, connected_contacts, account, typ):
        if self.row == row:
            return True, self._ui.tooltip_grid

        self._populate_grid(connected_contacts, account, typ)
        self.row = row
        return False, self._ui.tooltip_grid
lovetox's avatar
lovetox committed
296

297
    def _populate_grid(self, contacts, account, typ):
lovetox's avatar
lovetox committed
298
299
300
301
302
303
304
305
306
307
308
        """
        Populate the Tooltip Grid with data of from the contact
        """
        self.current_row = 0
        self.account = account
        if self.last_widget:
            self.last_widget.set_vexpand(False)

        self.clear_tooltip()

        if account == 'all':
309
            # Tooltip for merged accounts row
Philipp Hörist's avatar
Philipp Hörist committed
310
            self._show_merged_account_tooltip()
311
312
            return

lovetox's avatar
lovetox committed
313
        if typ == 'account':
314
            jid = app.get_jid_from_account(account)
lovetox's avatar
lovetox committed
315
            contacts = []
316
            connection = app.connections[account]
lovetox's avatar
lovetox committed
317
318
            # get our current contact info

319
            nbr_on, nbr_total = app.\
Philipp Hörist's avatar
Philipp Hörist committed
320
                contacts.get_nb_online_total_contacts(accounts=[account])
321
            account_name = app.get_account_label(account)
322
            if app.account_is_connected(account):
Philipp Hörist's avatar
Philipp Hörist committed
323
324
325
326
327
328
329
                account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
            contact = app.contacts.create_self_contact(
                jid=jid,
                account=account,
                name=account_name,
                show=connection.get_status(),
                status=connection.status,
lovetox's avatar
lovetox committed
330
331
                resource=connection.server_resource,
                priority=connection.priority)
Philipp Hörist's avatar
Philipp Hörist committed
332

lovetox's avatar
lovetox committed
333
334
335
            contacts.append(contact)

        # Username/Account/Groupchat
336
        self.prim_contact = app.contacts.get_highest_prio_contact_from_contacts(
337
            contacts)
Philipp Hörist's avatar
Philipp Hörist committed
338
339
        if self.prim_contact is None:
            log.error('No contact for Roster tooltip found')
Philipp Hörist's avatar
Philipp Hörist committed
340
341
            log.error('contacts: %s, typ: %s, account: %s',
                      contacts, typ, account)
Philipp Hörist's avatar
Philipp Hörist committed
342
            return
lovetox's avatar
lovetox committed
343
344
        self.contact_jid = self.prim_contact.jid
        name = GLib.markup_escape_text(self.prim_contact.get_shown_name())
345

346
347
        if app.config.get('mergeaccounts'):
            color = app.config.get('tooltip_account_name_color')
Philipp Hörist's avatar
Philipp Hörist committed
348
349
            account_name = GLib.markup_escape_text(
                self.prim_contact.account.name)
350
            name += " <span foreground='{}'>({})</span>".format(
lovetox's avatar
lovetox committed
351
                color, account_name)
352

353
        self._ui.name.set_markup(name)
Philipp Hörist's avatar
Philipp Hörist committed
354
        self._ui.name.show()
lovetox's avatar
lovetox committed
355
356

        self.num_resources = 0
357
358
359
360
        # put contacts in dict, where key is priority
        contacts_dict = {}
        for contact in contacts:
            if contact.resource:
lovetox's avatar
lovetox committed
361
362
363
364
                self.num_resources += 1
                priority = int(contact.priority)
                if priority in contacts_dict:
                    contacts_dict[priority].append(contact)
365
                else:
lovetox's avatar
lovetox committed
366
367
                    contacts_dict[priority] = [contact]
        if self.num_resources > 1:
368
            transport = app.get_transport_name_from_jid(self.prim_contact.jid)
369
370
            if transport == 'jabber':
                transport = None
371
372
373
374
            contact_keys = sorted(contacts_dict.keys())
            contact_keys.reverse()
            for priority in contact_keys:
                for acontact in contacts_dict[priority]:
375
                    show = self._get_icon_name_for_tooltip(acontact)
376
377
378
379
380
381
382
                    status = acontact.status
                    resource_line = '%s (%s)' % (acontact.resource,
                                                 str(acontact.priority))
                    self.add_status_row(
                        show, resource_line, transport=transport)
                    if status:
                        self.add_text_row(status, 2)
lovetox's avatar
lovetox committed
383

384
            self._ui.tooltip_grid.attach(self.table, 1, 3, 2, 1)
lovetox's avatar
lovetox committed
385
            self.table.show_all()
386

lovetox's avatar
lovetox committed
387
        else:  # only one resource
388
389
390
391
392
393
394
395
            if contact.is_groupchat:
                disco_info = app.logger.get_last_disco_info(contact.jid)
                if disco_info is not None:
                    description = disco_info.muc_description
                    if description:
                        self._ui.status.set_text(description)
                        self._ui.status.show()
            elif contact.show and contact.status:
396
397
                status = contact.status.strip()
                if status:
Philipp Hörist's avatar
Philipp Hörist committed
398
399
                    self._ui.status.set_text(status)
                    self._ui.status.show()
400

lovetox's avatar
lovetox committed
401
402
        # PEP Info
        self._append_pep_info(contact)
403

lovetox's avatar
lovetox committed
404
        # JID
Philipp Hörist's avatar
Philipp Hörist committed
405
406
        self._ui.jid.set_text(self.prim_contact.jid)
        self._ui.jid.show()
407

Alexander Krotov's avatar
Alexander Krotov committed
408
        # contact has only one resource
lovetox's avatar
lovetox committed
409
410
411
        if self.num_resources == 1 and contact.resource:
            res = GLib.markup_escape_text(contact.resource)
            prio = str(contact.priority)
Philipp Hörist's avatar
Philipp Hörist committed
412
413
414
            self._ui.resource.set_text("{} ({})".format(res, prio))
            self._ui.resource.show()
            self._ui.resource_label.show()
415

416
        if self.prim_contact.jid not in app.gc_connected[account]:
lovetox's avatar
lovetox committed
417
            if (account and
Philipp Hörist's avatar
Philipp Hörist committed
418
                    self.prim_contact.sub and
lovetox's avatar
lovetox committed
419
420
                    self.prim_contact.sub != 'both'):
                # ('both' is the normal sub so we don't show it)
Philipp Hörist's avatar
Philipp Hörist committed
421
422
423
                self._ui.sub.set_text(helpers.get_uf_sub(self.prim_contact.sub))
                self._ui.sub.show()
                self._ui.sub_label.show()
lovetox's avatar
lovetox committed
424
425
426
427

        self._set_idle_time(contact)

        # Avatar
428
        scale = self._ui.tooltip_grid.get_scale_factor()
429
430
        surface = app.contacts.get_avatar(
            account, self.prim_contact.jid, AvatarSize.TOOLTIP, scale)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
431
432
        self._ui.avatar.set_from_surface(surface)
        self._ui.avatar.show()
Philipp Hörist's avatar
Philipp Hörist committed
433

434
        app.plugin_manager.gui_extension_point(
Philipp Hörist's avatar
Philipp Hörist committed
435
            'roster_tooltip_populate', self, contacts, self._ui.tooltip_grid)
436

Philipp Hörist's avatar
Philipp Hörist committed
437
        # Sets the Widget that is at the bottom to expand.
438
        # This is needed in case the Picture takes more Space than the Labels
Philipp Hörist's avatar
Philipp Hörist committed
439
440
        i = 1
        while i < 15:
441
442
443
            if self._ui.tooltip_grid.get_child_at(1, i):
                if self._ui.tooltip_grid.get_child_at(1, i).get_visible():
                    self.last_widget = self._ui.tooltip_grid.get_child_at(1, i)
Philipp Hörist's avatar
Philipp Hörist committed
444
445
            i += 1
        self.last_widget.set_vexpand(True)
lovetox's avatar
lovetox committed
446

Philipp Hörist's avatar
Philipp Hörist committed
447
448
449
450
451
452
453
    def _show_merged_account_tooltip(self):
        accounts = helpers.get_notification_icon_tooltip_dict()
        self.spacer_label = ''
        self.fill_table_with_accounts(accounts)
        self._ui.tooltip_grid.attach(self.table, 1, 3, 2, 1)
        self.table.show_all()

lovetox's avatar
lovetox committed
454
455
    def _append_pep_info(self, contact):
        """
Philipp Hörist's avatar
Philipp Hörist committed
456
457
        Append Tune, Mood, Activity, Location information of the
        specified contact to the given property list.
lovetox's avatar
lovetox committed
458
        """
459
460
        if PEPEventType.MOOD in contact.pep:
            mood = format_mood(*contact.pep[PEPEventType.MOOD])
Philipp Hörist's avatar
Philipp Hörist committed
461
462
463
            self._ui.mood.set_markup(mood)
            self._ui.mood.show()
            self._ui.mood_label.show()
lovetox's avatar
lovetox committed
464

Philipp Hörist's avatar
Philipp Hörist committed
465
466
        if PEPEventType.ACTIVITY in contact.pep:
            activity = format_activity(*contact.pep[PEPEventType.ACTIVITY])
Philipp Hörist's avatar
Philipp Hörist committed
467
468
469
            self._ui.activity.set_markup(activity)
            self._ui.activity.show()
            self._ui.activity_label.show()
lovetox's avatar
lovetox committed
470

Philipp Hörist's avatar
Philipp Hörist committed
471
472
        if PEPEventType.TUNE in contact.pep:
            tune = format_tune(*contact.pep[PEPEventType.TUNE])
Philipp Hörist's avatar
Philipp Hörist committed
473
474
475
            self._ui.tune.set_markup(tune)
            self._ui.tune.show()
            self._ui.tune_label.show()
476

Philipp Hörist's avatar
Philipp Hörist committed
477
478
        if PEPEventType.LOCATION in contact.pep:
            location = format_location(contact.pep[PEPEventType.LOCATION])
Philipp Hörist's avatar
Philipp Hörist committed
479
480
481
            self._ui.location.set_markup(location)
            self._ui.location.show()
            self._ui.location_label.show()
lovetox's avatar
lovetox committed
482
483

    def _set_idle_time(self, contact):
484
        if contact.idle_time:
485
            idle_color = app.config.get('tooltip_idle_color')
486
487
488
            idle_time = contact.idle_time
            idle_time = time.localtime(contact.idle_time)
            idle_time = datetime(*(idle_time[:6]))
489
            current = datetime.now()
490
            if idle_time.date() == current.date():
Philipp Hörist's avatar
Philipp Hörist committed
491
                formatted = idle_time.strftime('%X')
492
            else:
Philipp Hörist's avatar
Philipp Hörist committed
493
494
495
496
497
498
                formatted = idle_time.strftime('%c')
            idle_markup = '<span foreground="{}">{}</span>'.format(
                idle_color, formatted)
            self._ui.idle_since.set_markup(idle_markup)
            self._ui.idle_since.show()
            self._ui.idle_since_label.show()
lovetox's avatar
lovetox committed
499
500
501
502
503

        if contact.show and self.num_resources < 2:
            show = helpers.get_uf_show(contact.show)
            # Contact is Groupchat
            if (self.account and
504
505
                    self.prim_contact.jid in app.gc_connected[self.account]):
                if app.gc_connected[self.account][self.prim_contact.jid]:
lovetox's avatar
lovetox committed
506
507
508
                    show = _('Connected')
                else:
                    show = _('Disconnected')
509

Philipp Hörist's avatar
Philipp Hörist committed
510
511
            self._ui.user_show.set_markup(colorize_status(show))
            self._ui.user_show.show()
lovetox's avatar
lovetox committed
512

Philipp Hörist's avatar
Philipp Hörist committed
513
514
    @staticmethod
    def _get_icon_name_for_tooltip(contact):
lovetox's avatar
lovetox committed
515
        """
Alexander Krotov's avatar
Alexander Krotov committed
516
        Helper function used for tooltip contacts/accounts
lovetox's avatar
lovetox committed
517
518
519
520
521
522

        Tooltip on account has fake contact with sub == '', in this case we show
        real status of the account
        """
        if contact.ask == 'subscribe':
            return 'requested'
523
        if contact.sub in ('both', 'to', ''):
lovetox's avatar
lovetox committed
524
525
526
            return contact.show
        return 'not in roster'

527

528
class FileTransfersTooltip():
529
    def __init__(self):
530
531
        self.sid = None
        self.widget = None
532
533
534
535
        if app.config.get('use_kib_mib'):
            self.units = GLib.FormatSizeFlags.IEC_UNITS
        else:
            self.units = GLib.FormatSizeFlags.DEFAULT
536

537
538
539
540
541
542
543
544
545
546
547
548
    def clear_tooltip(self):
        self.sid = None
        self.widget = None

    def get_tooltip(self, file_props, sid):
        if self.sid == sid:
            return True, self.widget

        self.widget = self._create_tooltip(file_props, sid)
        self.sid = sid
        return False, self.widget

Philipp Hörist's avatar
Philipp Hörist committed
549
    def _create_tooltip(self, file_props, _sid):
550
551
552
553
554
        ft_grid = Gtk.Grid.new()
        ft_grid.insert_column(0)
        ft_grid.set_row_spacing(6)
        ft_grid.set_column_spacing(12)
        current_row = 0
555
        properties = []
zimio's avatar
zimio committed
556
557
558
        name = file_props.name
        if file_props.type_ == 'r':
            file_name = os.path.split(file_props.file_name)[1]
559
        else:
zimio's avatar
zimio committed
560
            file_name = file_props.name
561
562
        properties.append((_('File Name: '),
                           GLib.markup_escape_text(file_name)))
zimio's avatar
zimio committed
563
        if file_props.type_ == 'r':
Yann Leboulanger's avatar
Yann Leboulanger committed
564
            type_ = Q_('?Noun:Download')
565
            actor = _('Sender: ')
Yann Leboulanger's avatar
Yann Leboulanger committed
566
            sender = file_props.sender.split('/')[0]
567
            name = app.contacts.get_first_contact_from_jid(
Philipp Hörist's avatar
Philipp Hörist committed
568
                file_props.tt_account, sender).get_shown_name()
569
        else:
Yann Leboulanger's avatar
Yann Leboulanger committed
570
            type_ = Q_('?Noun:Upload')
571
            actor = _('Recipient: ')
zimio's avatar
zimio committed
572
            receiver = file_props.receiver
573
574
575
576
            if hasattr(receiver, 'name'):
                name = receiver.get_shown_name()
            else:
                name = receiver.split('/')[0]
577
        properties.append((Q_('?transfer type:Type: '), type_))
Yann Leboulanger's avatar
Yann Leboulanger committed
578
        properties.append((actor, GLib.markup_escape_text(name)))
579

zimio's avatar
zimio committed
580
581
582
        transfered_len = file_props.received_len
        if not transfered_len:
            transfered_len = 0
583
        properties.append((Q_('?transfer status:Transferred: '),
584
                           GLib.format_size_full(transfered_len, self.units)))
Philipp Hörist's avatar
Philipp Hörist committed
585
        status = self._get_current_status(file_props)
586
        properties.append((Q_('?transfer status:Status: '), status))
587
        file_desc = file_props.desc or ''
588
589
590
        properties.append((_('Description: '),
                           GLib.markup_escape_text(file_desc)))

591
592
        while properties:
            property_ = properties.pop(0)
593
            label = Gtk.Label()
594
595
            label.set_halign(Gtk.Align.END)
            label.set_valign(Gtk.Align.CENTER)
596
            label.set_markup(property_[0])
597
            ft_grid.attach(label, 0, current_row, 1, 1)
598
            label = Gtk.Label()
599
600
            label.set_halign(Gtk.Align.START)
            label.set_valign(Gtk.Align.START)
601
602
            label.set_line_wrap(True)
            label.set_markup(property_[1])
603
604
            ft_grid.attach(label, 1, current_row, 1, 1)
            current_row += 1
605

606
607
        ft_grid.show_all()
        return ft_grid
lovetox's avatar
lovetox committed
608

Philipp Hörist's avatar
Philipp Hörist committed
609
610
611
    @staticmethod
    def _get_current_status(file_props):
        if file_props.stopped:
612
            return Q_('?transfer status:Aborted')
Philipp Hörist's avatar
Philipp Hörist committed
613
        if file_props.completed:
614
            return Q_('?transfer status:Completed')
Philipp Hörist's avatar
Philipp Hörist committed
615
616
617
618
        if file_props.paused:
            return Q_('?transfer status:Paused')
        if file_props.stalled:
            # stalled is not paused. it is like 'frozen' it stopped alone
619
            return Q_('?transfer status:Stalled')
Philipp Hörist's avatar
Philipp Hörist committed
620
621
622

        if file_props.connected:
            if file_props.started:
623
624
625
                return Q_('?transfer status:Transferring')
            return Q_('?transfer status:Not started')
        return Q_('?transfer status:Not started')
Philipp Hörist's avatar
Philipp Hörist committed
626

lovetox's avatar
lovetox committed
627
628
629
630
631
632
633
634
635

def colorize_status(status):
    """
    Colorize the status message inside the tooltip by it's
    semantics. Color palette is the Tango.
    """
    formatted = "<span foreground='%s'>%s</span>"
    color = None
    if status.startswith(Q_("?user status:Available")):
636
        color = app.config.get('tooltip_status_online_color')
lovetox's avatar
lovetox committed
637
    elif status.startswith(_("Free for Chat")):
638
        color = app.config.get('tooltip_status_free_for_chat_color')
lovetox's avatar
lovetox committed
639
    elif status.startswith(_("Away")):
640
        color = app.config.get('tooltip_status_away_color')
lovetox's avatar
lovetox committed
641
    elif status.startswith(_("Busy")):
642
        color = app.config.get('tooltip_status_busy_color')
lovetox's avatar
lovetox committed
643
    elif status.startswith(_("Not Available")):
644
        color = app.config.get('tooltip_status_na_color')
lovetox's avatar
lovetox committed
645
    elif status.startswith(_("Offline")):
646
        color = app.config.get('tooltip_status_offline_color')
lovetox's avatar
lovetox committed
647
648
    if color:
        status = formatted % (color, status)
Emmanuel Gil Peyrot's avatar
Emmanuel Gil Peyrot committed
649
    return status