root/branches/gajim_0.9.1/src/tooltips.py

Revision 4743, 18.2 kB (checked in by nk, 3 years ago)

failsafe to dcraven

  • Property svn:eol-style set to LF
Line 
1##      tooltips.py
2##
3## Contributors for this file:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Nikos Kouremenos <kourem@gmail.com>
6##      - Dimitur Kirov <dkirov@gmail.com>
7##
8## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
9##                         Vincent Hanquez <tab@snarc.org>
10## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
11##                    Vincent Hanquez <tab@snarc.org>
12##                    Nikos Kouremenos <nkour@jabber.org>
13##                    Dimitur Kirov <dkirov@gmail.com>
14##                    Travis Shirk <travis@pobox.com>
15##                    Norman Rasmussen <norman@rasmussen.co.za>
16##
17## This program is free software; you can redistribute it and/or modify
18## it under the terms of the GNU General Public License as published
19## by the Free Software Foundation; version 2 only.
20##
21## This program is distributed in the hope that it will be useful,
22## but WITHOUT ANY WARRANTY; without even the implied warranty of
23## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24## GNU General Public License for more details.
25##
26
27import gtk
28import gobject
29import os
30
31import gtkgui_helpers
32
33from common import gajim
34from common import helpers
35from common import i18n
36
37_ = i18n._
38APP = i18n.APP
39
40class BaseTooltip:
41        ''' Base Tooltip; Usage:
42                tooltip = BaseTooltip()
43                ....
44                tooltip.show_tooltip('', window_positions, widget_positions)
45                #FIXME: what is window, what is widget?
46                ....
47                if tooltip.timeout != 0:
48                        tooltip.hide_tooltip()
49        '''
50        def __init__(self):
51                self.timeout = 0
52                self.prefered_position = [0, 0]
53                self.win = None
54                self.id = None
55               
56        def populate(self, data):
57                ''' this method must be overriden by all extenders '''
58                self.create_window()
59                self.win.add(gtk.Label(data))
60               
61        def create_window(self):
62                ''' create a popup window each time tooltip is requested '''
63                self.win = gtk.Window(gtk.WINDOW_POPUP)
64                self.win.set_border_width(3)
65                self.win.set_resizable(False)
66                self.win.set_name('gtk-tooltips')
67               
68               
69                self.win.set_events(gtk.gdk.POINTER_MOTION_MASK)
70                self.win.connect_after('expose_event', self.expose)
71                self.win.connect('size-request', self.size_request)
72                self.win.connect('motion-notify-event', self.motion_notify_event)
73       
74        def motion_notify_event(self, widget, event):
75                self.hide_tooltip()
76
77        def size_request(self, widget, requisition):
78                screen = self.win.get_screen()
79                half_width = requisition.width / 2 + 1
80                if self.prefered_position[0] < half_width:
81                        self.prefered_position[0] = 0
82                elif self.prefered_position[0]  + requisition.width > screen.get_width() \
83                                + half_width:
84                        self.prefered_position[0] = screen.get_width() - requisition.width
85                else:
86                        self.prefered_position[0] -= half_width
87                        screen.get_height()
88                if self.prefered_position[1] + requisition.height > screen.get_height():
89                        # flip tooltip up
90                        self.prefered_position[1] -= requisition.height  + self.widget_height + 8
91                if self.prefered_position[1] < 0:
92                        self.prefered_position[1] = 0
93                self.win.move(self.prefered_position[0], self.prefered_position[1])
94
95        def expose(self, widget, event):
96                style = self.win.get_style()
97                size = self.win.get_size()
98                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
99                        self.win, 'tooltip', 0, 0, -1, 1)
100                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
101                        self.win, 'tooltip', 0, size[1] - 1, -1, 1)
102                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
103                        self.win, 'tooltip', 0, 0, 1, -1)
104                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
105                        self.win, 'tooltip', size[0] - 1, 0, 1, -1)
106                return True
107       
108        def show_tooltip(self, data, widget_pos, win_size):
109                self.populate(data)
110                new_x = win_size[0] + widget_pos[0] 
111                new_y = win_size[1] + widget_pos[1] + 4
112                self.prefered_position = [new_x, new_y]
113                self.widget_height = widget_pos[1]
114                self.win.ensure_style()
115                self.win.show_all()
116
117        def hide_tooltip(self):
118                if self.timeout > 0:
119                        gobject.source_remove(self.timeout)
120                        self.timeout = 0
121                if self.win:
122                        self.win.destroy()
123                        self.win = None
124                self.id = None
125
126class StatusTable:
127        ''' Contains methods for creating status table. This
128        is used in Roster and NotificationArea tooltips '''
129        def __init__(self):
130                self.current_row = 1
131                self.table = None
132                self.text_label = None
133                self.spacer_label = '   '
134               
135        def create_table(self):
136                self.table = gtk.Table(3, 1)
137                self.table.set_property('column-spacing', 2)
138                self.text_label = gtk.Label()
139                self.text_label.set_line_wrap(True)
140                self.text_label.set_alignment(0, 0)
141                self.text_label.set_selectable(False)
142                self.table.attach(self.text_label, 1, 4, 1, 2)
143               
144        def get_status_info(self, resource, priority, show, status):
145                str_status = resource + ' (' + unicode(priority) + ')'
146                if status:
147                        status = status.strip()
148                        if status != '':
149                                # make sure 'status' is unicode before we send to to reduce_chars...
150                                if isinstance(status, str):
151                                        status = unicode(status, encoding='utf-8')
152                                status = gtkgui_helpers.reduce_chars_newlines(status, 0, 1)
153                                str_status += ' - ' + status
154                return gtkgui_helpers.escape_for_pango_markup(str_status)
155       
156        def add_status_row(self, file_path, show, str_status):
157                ''' appends a new row with status icon to the table '''
158                self.current_row += 1
159                state_file = show.replace(' ', '_')
160                files = []
161                files.append(os.path.join(file_path, state_file + '.png'))
162                files.append(os.path.join(file_path, state_file + '.gif'))
163                image = gtk.Image()
164                image.set_from_pixbuf(None)
165                for file in files:
166                        if os.path.exists(file):
167                                image.set_from_file(file)
168                                break
169                spacer = gtk.Label(self.spacer_label)
170                image.set_alignment(0, 1.)
171                self.table.attach(spacer, 1, 2, self.current_row, 
172                        self.current_row + 1, 0, 0, 0, 0)
173                self.table.attach(image, 2, 3, self.current_row, 
174                        self.current_row + 1, 0, 0, 3, 0)
175                status_label = gtk.Label()
176                status_label.set_markup(str_status)
177                status_label.set_alignment(0, 0)
178                self.table.attach(status_label, 3, 4, self.current_row,
179                        self.current_row + 1, gtk.EXPAND | gtk.FILL, 0, 0, 0)
180       
181class NotificationAreaTooltip(BaseTooltip, StatusTable):
182        ''' Tooltip that is shown in the notification area '''
183        def __init__(self):
184                BaseTooltip.__init__(self)
185                StatusTable.__init__(self)
186
187        def get_accounts_info(self):
188                accounts = []
189                if gajim.contacts:
190                        for account in gajim.contacts.keys():
191                                status_idx = gajim.connections[account].connected
192                                # uncomment the following to hide offline accounts
193                                # if status_idx == 0: continue
194                                status = gajim.SHOW_LIST[status_idx]
195                                message = gajim.connections[account].status
196                                single_line = helpers.get_uf_show(status)
197                                if message is None:
198                                        message = ''
199                                else:
200                                        message = message.strip()
201                                if message != '':
202                                        single_line += ': ' + message
203                                # the other solution is to hide offline accounts
204                                elif status == 'offline':
205                                        message = helpers.get_uf_show(status)
206                                accounts.append({'name': account, 'status_line': single_line, 
207                                                'show': status, 'message': message})
208                return accounts
209
210        def fill_table_with_accounts(self, accounts):
211                iconset = gajim.config.get('iconset')
212                if not iconset:
213                        iconset = 'dcraven'
214                file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
215                for acct in accounts:
216                        message = acct['message']
217                        # before reducing the chars we should assure we send unicode, else
218                        # there are possible pango TBs on 'set_markup'
219                        if isinstance(message, str):
220                                message = unicode(message, encoding = 'utf-8')
221                        message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
222                        message = gtkgui_helpers.escape_for_pango_markup(message)
223                        if message:
224                                self.add_status_row(file_path, acct['show'], '<span weight="bold">' + 
225                                        gtkgui_helpers.escape_for_pango_markup(acct['name']) + '</span>' 
226                                        + ' - ' + message)
227                        else:
228                                self.add_status_row(file_path, acct['show'], '<span weight="bold">' + 
229                                        gtkgui_helpers.escape_for_pango_markup(acct['name']) + '</span>')
230
231        def populate(self, data):
232                self.create_window()
233                self.create_table()
234                self.hbox = gtk.HBox()
235                self.table.set_property('column-spacing', 1)
236                text, single_line = '', ''
237
238                unread_chat = gajim.interface.roster.nb_unread
239                unread_single_chat = 0
240                unread_gc = 0
241                unread_pm = 0
242
243                accounts = self.get_accounts_info()
244
245                for acct in gajim.connections:
246                        # we count unread chat/pm messages
247                        chat_wins = gajim.interface.instances[acct]['chats']
248                        for jid in chat_wins:
249                                if jid != 'tabbed':
250                                        if gajim.contacts[acct].has_key(jid):
251                                                unread_chat += chat_wins[jid].nb_unread[jid]
252                                        else:
253                                                unread_pm += chat_wins[jid].nb_unread[jid]
254                        # we count unread gc/pm messages
255                        gc_wins = gajim.interface.instances[acct]['gc']
256                        for jid in gc_wins:
257                                if jid != 'tabbed':
258                                        pm_msgs = gc_wins[jid].get_specific_unread(jid)
259                                        unread_gc += gc_wins[jid].nb_unread[jid]
260                                        unread_gc -= pm_msgs
261                                        unread_pm += pm_msgs
262
263                if unread_chat or unread_single_chat or unread_gc or unread_pm:
264                        text = ''
265                        if unread_chat:
266                                text += i18n.ngettext(
267                                        'Gajim - %d unread message',
268                                        'Gajim - %d unread messages',
269                                        unread_chat, unread_chat, unread_chat)
270                                text += '\n'
271                        if unread_single_chat:
272                                text += i18n.ngettext(
273                                        'Gajim - %d unread single message',
274                                        'Gajim - %d unread single messages',
275                                        unread_single_chat, unread_single_chat, unread_single_chat)
276                                text += '\n'
277                        if unread_gc:
278                                text += i18n.ngettext(
279                                        'Gajim - %d unread group chat message',
280                                        'Gajim - %d unread group chat messages',
281                                        unread_gc, unread_gc, unread_gc)
282                                text += '\n'
283                        if unread_pm:
284                                text += i18n.ngettext(
285                                        'Gajim - %d unread private message',
286                                        'Gajim - %d unread private messages',
287                                        unread_pm, unread_pm, unread_pm)
288                                text += '\n'
289                        text = text[:-1] # remove latest \n
290                elif len(accounts) > 1:
291                        text = _('Gajim')
292                        self.current_row = 1
293                        self.table.resize(2, 1)
294                        self.fill_table_with_accounts(accounts)
295
296                elif len(accounts) == 1:
297                        message = accounts[0]['status_line']
298                        message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
299                        message = gtkgui_helpers.escape_for_pango_markup(message)
300                        text = _('Gajim - %s') % message
301                else:
302                        text = _('Gajim - %s') % helpers.get_uf_show('offline')
303                self.text_label.set_markup(text)
304                self.hbox.add(self.table)
305                self.win.add(self.hbox)
306
307class GCTooltip(BaseTooltip):
308        ''' Tooltip that is shown in the GC treeview '''
309        def __init__(self):
310                self.account = None
311
312                self.text_label = gtk.Label()
313                self.text_label.set_line_wrap(True)
314                self.text_label.set_alignment(0, 0)
315                self.text_label.set_selectable(False)
316
317                BaseTooltip.__init__(self)
318               
319        def populate(self, contact):
320                if not contact:
321                        return
322                self.create_window()
323                hbox = gtk.HBox()
324               
325                if contact.jid.strip() != '':
326                        info = '<span size="large" weight="bold">' + contact.jid + '</span>'
327                else:
328                        info = '<span size="large" weight="bold">' + contact.name + '</span>'
329                       
330                info += '\n<span weight="bold">' + _('Role: ') + '</span>' + \
331                         helpers.get_uf_role(contact.role)
332
333                info += '\n<span weight="bold">' + _('Affiliation: ') + '</span>' + \
334                        contact.affiliation.capitalize()
335
336                info += '\n<span weight="bold">' + _('Status: ') + \
337                                        '</span>' + helpers.get_uf_show(contact.show)
338               
339                if contact.status:
340                        status = contact.status.strip()
341                        if status != '':
342                                # escape markup entities
343                                info += ' - ' + gtkgui_helpers.escape_for_pango_markup(status)
344               
345                if contact.resource.strip() != '':
346                        info += '\n<span weight="bold">' + _('Resource: ') + \
347                                        '</span>' + gtkgui_helpers.escape_for_pango_markup(
348                                                contact.resource) 
349
350                self.text_label.set_markup(info)
351                hbox.add(self.text_label)
352                self.win.add(hbox)
353
354class RosterTooltip(NotificationAreaTooltip):
355        ''' Tooltip that is shown in the roster treeview '''
356        def __init__(self):
357                self.account = None
358                self.image = gtk.Image()
359                self.image.set_alignment(0, 0)
360                # padding is independent of the total length and better than alignment
361                self.image.set_padding(1, 2) 
362                NotificationAreaTooltip.__init__(self)
363
364        def populate(self, contacts):
365                self.create_window()
366                self.hbox = gtk.HBox()
367                self.hbox.set_homogeneous(False)
368                self.hbox.set_spacing(0)
369                self.create_table()
370                if not contacts or len(contacts) == 0:
371                        # Tooltip for merged accounts row
372                        accounts = self.get_accounts_info()
373                        self.current_row = 0
374                        self.table.resize(2, 1)
375                        self.spacer_label = ''
376                        self.fill_table_with_accounts(accounts)
377                        self.hbox.add(self.table)
378                        self.win.add(self.hbox)
379                        return
380                # primary contact
381                prim_contact = gajim.get_highest_prio_contact_from_contacts(contacts)
382               
383                # try to find the image for the contact status
384                icon_name = helpers.get_icon_name_to_show(prim_contact)
385                state_file = icon_name.replace(' ', '_')
386                transport = gajim.get_transport_name_from_jid(prim_contact.jid)
387                if transport:
388                        file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', 
389                                transport , '16x16')
390                else:
391                        iconset = gajim.config.get('iconset')
392                        if not iconset:
393                                iconset = 'dcraven'
394                        file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
395
396                files = []
397                file_full_path = os.path.join(file_path, state_file)
398                files.append(file_full_path + '.png')
399                files.append(file_full_path + '.gif')
400                self.image.set_from_pixbuf(None)
401                for file in files:
402                        if os.path.exists(file):
403                                self.image.set_from_file(file)
404                                break
405               
406                info = '<span size="large" weight="bold">' + prim_contact.jid + '</span>'
407                info += '\n<span weight="bold">' + _('Name: ') + '</span>' + \
408                        gtkgui_helpers.escape_for_pango_markup(prim_contact.name)
409                if prim_contact.sub:
410                        info += '\n<span weight="bold">' + _('Subscription: ') + '</span>' + \
411                                gtkgui_helpers.escape_for_pango_markup(prim_contact.sub)
412
413                if prim_contact.keyID:
414                        keyID = None
415                        if len(prim_contact.keyID) == 8:
416                                keyID = prim_contact.keyID
417                        elif len(prim_contact.keyID) == 16:
418                                keyID = prim_contact.keyID[8:]
419                        if keyID:
420                                info += '\n<span weight="bold">' + _('OpenPGP: ') + \
421                                        '</span>' + gtkgui_helpers.escape_for_pango_markup(keyID)
422
423                num_resources = 0
424                for contact in contacts:
425                        if contact.resource:
426                                num_resources += 1
427                if num_resources > 1:
428                        self.current_row = 1
429                        self.table.resize(2,1)
430                        info += '\n<span weight="bold">' + _('Status: ') + '</span>'
431                        for contact in contacts:
432                                if contact.resource:
433                                        status_line = self.get_status_info(contact.resource, contact.priority, 
434                                                contact.show, contact.status)
435                                        icon_name = helpers.get_icon_name_to_show(contact)
436                                        self.add_status_row(file_path, icon_name, status_line)
437                                       
438                else: # only one resource
439                        if contact.resource:
440                                info += '\n<span weight="bold">' + _('Resource: ') + \
441                                        '</span>' + gtkgui_helpers.escape_for_pango_markup(
442                                                contact.resource) + ' (' + unicode(contact.priority) + ')'
443                        if contact.show:
444                                info += '\n<span weight="bold">' + _('Status: ') + \
445                                        '</span>' + helpers.get_uf_show(contact.show) 
446                                if contact.status:
447                                        status = contact.status.strip()
448                                        if status != '':
449                                                # reduce long status
450                                                # (no more than 130 chars on line and no more than 5 lines)
451                                                status = gtkgui_helpers.reduce_chars_newlines(status, 130, 5)
452                                                # escape markup entities.
453                                                info += ' - ' + gtkgui_helpers.escape_for_pango_markup(status)
454               
455                self.text_label.set_markup(info)
456                self.hbox.pack_start(self.image, False, False)
457                self.hbox.pack_start(self.table, True, True)
458                self.win.add(self.hbox)
459
460class FileTransfersTooltip(BaseTooltip):
461        ''' Tooltip that is shown in the notification area '''
462        def __init__(self):
463                self.text_label = gtk.Label()
464                self.text_label.set_line_wrap(True)
465                self.text_label.set_alignment(0, 0)
466                self.text_label.set_selectable(False)
467                BaseTooltip.__init__(self)
468
469        def populate(self, file_props):
470                self.create_window()
471                self.hbox = gtk.HBox()
472                text = '<b>' + _('Name: ') + '</b>' 
473                name = file_props['name']
474                if file_props['type'] == 'r':
475                        (file_path, file_name) = os.path.split(file_props['file-name'])
476                else:
477                        file_name = file_props['name']
478                text += gtkgui_helpers.escape_for_pango_markup(file_name) 
479                text += '\n<b>' + _('Type: ') + '</b>'
480                if file_props['type'] == 'r':
481                        text += _('Download')
482                else:
483                        text += _('Upload')
484                if file_props['type'] == 'r':
485                        text += '\n<b>' + _('Sender: ') + '</b>'
486                        sender = unicode(file_props['sender']).split('/')[0]
487                        name = gajim.get_first_contact_instance_from_jid( 
488                                file_props['tt_account'], sender).name
489                else:
490                        text += '\n<b>' + _('Recipient: ') + '</b>' 
491                        receiver = file_props['receiver']
492                        if hasattr(receiver, 'name'):
493                                receiver = receiver.name
494                        receiver = receiver.split('/')[0]
495                        if receiver.find('@') == -1:
496                                name = receiver
497                        else:
498                                name = gajim.get_first_contact_instance_from_jid( 
499                                file_props['tt_account'], receiver).name
500                text +=  gtkgui_helpers.escape_for_pango_markup(name)
501                text += '\n<b>' + _('Size: ') + '</b>' 
502                text += helpers.convert_bytes(file_props['size'])
503                text += '\n<b>' + _('Transferred: ') + '</b>' 
504                transfered_len = 0
505                if file_props.has_key('received-len'):
506                        transfered_len = file_props['received-len']
507                text += helpers.convert_bytes(transfered_len)
508                text += '\n<b>' + _('Status: ') + '</b>' 
509                status = '' 
510                if not file_props.has_key('started') or not file_props['started']:
511                        status =  _('Not started')
512                elif file_props.has_key('connected'):
513                        if file_props.has_key('stopped') and \
514                                file_props['stopped'] == True:
515                                status = _('Stopped')
516                        elif file_props['completed']:
517                                        status = _('Completed')
518                        elif file_props['connected'] == False:
519                                if file_props['completed']:
520                                        status = _('Completed')
521                        else:
522                                if file_props.has_key('paused') and  \
523                                        file_props['paused'] == True:
524                                        status = _('Paused')
525                                elif file_props.has_key('stalled') and \
526                                        file_props['stalled'] == True:
527                                        #stalled is not paused. it is like 'frozen' it stopped alone
528                                        status = _('Stalled')
529                                else:
530                                        status = _('Transferring')
531                else:
532                        status =  _('Not started')
533               
534                text += status
535                self.text_label.set_markup(text)
536                self.hbox.add(self.text_label)
537                self.win.add(self.hbox)
538
539
540class ServiceDiscoveryTooltip(BaseTooltip):
541        ''' Tooltip that is shown when hovering over a service discovery row '''
542        def populate(self, status):
543                self.create_window()
544                label = gtk.Label()
545                label.set_line_wrap(True)
546                label.set_alignment(0, 0)
547                label.set_selectable(False)
548                if status == 1:
549                        label.set_text(_('This service has not yet responded with detailed information'))
550                elif status == 2:
551                        label.set_text(_('This service could not respond with detailed information.