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

Revision 3075, 13.9 kB (checked in by dkirov, 3 years ago)

fixed TB in notification area on long nonascii
status messages

Line 
1##      tooltips.py
2##
3## Gajim Team:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Vincent Hanquez <tab@snarc.org>
6##      - Nikos Kouremenos <kourem@gmail.com>
7##      - Dimitur Kirov <dkirov@gmail.com>
8##
9##      Copyright (C) 2003-2005 Gajim Team
10##
11## This program is free software; you can redistribute it and/or modify
12## it under the terms of the GNU General Public License as published
13## by the Free Software Foundation; version 2 only.
14##
15## This program is distributed in the hope that it will be useful,
16## but WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20
21import gtk
22import gobject
23import os
24
25import gtkgui_helpers
26
27from common import gajim
28from common import helpers
29from common import i18n
30
31_ = i18n._
32APP = i18n.APP
33
34class BaseTooltip:
35        ''' Base Tooltip . Usage:
36                tooltip = BaseTooltip()
37                ....
38                tooltip.show_tooltip('', window_postions, widget_postions)
39                ....
40                if tooltip.timeout != 0:
41                        tooltip.hide_tooltip()
42        '''
43        def __init__(self):
44                self.timeout = 0
45                self.prefered_position = [0, 0]
46                self.win = None
47                self.id = None
48               
49        def populate(self, data):
50                ''' this method must be overriden by all extenders '''
51                self.create_window()
52                self.win.add(gtk.Label(data))
53               
54        def create_window(self):
55                ''' create a popup window each time tooltip is requested '''
56                self.win = gtk.Window(gtk.WINDOW_POPUP)
57                self.win.set_border_width(3)
58                self.win.set_resizable(False)
59                self.win.set_name('gtk-tooltips')
60               
61               
62                self.win.set_events(gtk.gdk.POINTER_MOTION_MASK)
63                self.win.connect_after('expose_event', self.expose)
64                self.win.connect('size-request', self.size_request)
65                self.win.connect('motion-notify-event', self.motion_notify_event)
66       
67        def motion_notify_event(self, widget, event):
68                self.hide_tooltip()
69
70        def size_request(self, widget, requisition):
71                screen = self.win.get_screen()
72                half_width = requisition.width / 2 + 1
73                if self.prefered_position[0] < half_width:
74                        self.prefered_position[0] = 0
75                elif self.prefered_position[0]  + requisition.width > screen.get_width() \
76                                + half_width:
77                        self.prefered_position[0] = screen.get_width() - requisition.width
78                else:
79                        self.prefered_position[0] -= half_width
80                        screen.get_height()
81                if self.prefered_position[1] + requisition.height > screen.get_height():
82                        # flip tooltip up
83                        self.prefered_position[1] -= requisition.height  + self.widget_height + 8
84                if self.prefered_position[1] < 0:
85                        self.prefered_position[1] = 0
86                self.win.move(self.prefered_position[0], self.prefered_position[1])
87
88        def expose(self, widget, event):
89                style = self.win.get_style()
90                size = self.win.get_size()
91                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
92                        self.win, 'tooltip', 0, 0, -1, 1)
93                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
94                        self.win, 'tooltip', 0, size[1] - 1, -1, 1)
95                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
96                        self.win, 'tooltip', 0, 0, 1, -1)
97                style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None,
98                        self.win, 'tooltip', size[0] - 1, 0, 1, -1)
99                return True
100       
101        def show_tooltip(self, data, widget_pos, win_size):
102                self.populate(data)
103                new_x = win_size[0] + widget_pos[0] 
104                new_y = win_size[1] + widget_pos[1] + 4
105                self.prefered_position = [new_x, new_y]
106                self.widget_height = widget_pos[1]
107                self.win.ensure_style()
108                self.win.show_all()
109
110        def hide_tooltip(self):
111                if(self.timeout > 0):
112                        gobject.source_remove(self.timeout)
113                        self.timeout = 0
114                if self.win:
115                        self.win.destroy()
116                        self.win = None
117                self.id = None
118
119class StatusTable:
120        ''' Contains methods for creating status table. This
121        is used in Roster and NotificationArea tooltips '''
122        def __init__(self):
123                self.current_row = 1
124                self.table = None
125                self.text_lable = None
126               
127        def create_table(self):
128                self.table = gtk.Table(3, 1)
129                self.table.set_property('column-spacing', 6)
130                self.text_lable = gtk.Label()
131                self.text_lable.set_line_wrap(True)
132                self.text_lable.set_alignment(0, 0)
133                self.text_lable.set_selectable(False)
134                self.table.attach(self.text_lable, 1, 4, 1, 2)
135               
136        def get_status_info(self, resource, priority, show, status):
137                str_status = resource + ' (' + str(priority) + ')'
138                if status:
139                        status = status.strip()
140                        if status != '':
141                                # make sure 'status' is unicode before we send to to reduce_chars...
142                                if type(status) == str:
143                                        status = unicode(status, encoding='utf-8')
144                                if gtk.gtk_version < (2, 6, 0) or gtk.pygtk_version < (2, 6, 0):
145                                        status = gtkgui_helpers.reduce_chars_newlines(status, 50, 1)
146                                else:
147                                        status = gtkgui_helpers.reduce_chars_newlines(status, 0, 1)
148                                str_status += ' - ' + status
149                return gtkgui_helpers.escape_for_pango_markup(str_status)
150       
151        def add_status_row(self, file_path, show, str_status):
152                ''' appends a new row with status icon to the table '''
153                self.current_row += 1
154                state_file = show.replace(' ', '_')
155                files = []
156                files.append(os.path.join(file_path, state_file + '.png'))
157                files.append(os.path.join(file_path, state_file + '.gif'))
158                image = gtk.Image()
159                image.set_from_pixbuf(None)
160                spacer = gtk.Label('   ')
161                for file in files:
162                        if os.path.exists(file):
163                                image.set_from_file(file)
164                                break
165                image.set_alignment(0.01, 1)
166                self.table.attach(spacer, 1, 2, self.current_row, 
167                        self.current_row + 1, 0, 0, 0, 0)
168                self.table.attach(image,2,3,self.current_row, 
169                        self.current_row + 1, 0, 0, 3, 0)
170                image.set_alignment(0.01, 1)
171                status_label = gtk.Label()
172                status_label.set_markup(str_status)
173                status_label.set_alignment(00, 0)
174                self.table.attach(status_label, 3, 4, self.current_row,
175                        self.current_row + 1, gtk.EXPAND | gtk.FILL, 0, 0, 0)
176       
177class NotificationAreaTooltip(BaseTooltip, StatusTable):
178        ''' Tooltip that is shown in the notification area '''
179        def __init__(self, plugin):
180                self.plugin = plugin
181                BaseTooltip.__init__(self)
182                StatusTable.__init__(self)
183
184        def populate(self, data):
185                self.create_window()
186                self.create_table()
187                self.hbox = gtk.HBox()
188                self.table.set_property('column-spacing', 1)
189                text, single_line, accounts = '', '', []
190                if gajim.contacts:
191                        for account in gajim.contacts.keys():
192                                status_idx = gajim.connections[account].connected
193                                # uncomment the following to hide offline accounts
194                                # if status_idx == 0: continue
195                                status = gajim.SHOW_LIST[status_idx]
196                                message = gajim.connections[account].status
197                                single_line = helpers.get_uf_show(status)
198                                if message is None:
199                                        message = ''
200                                else:
201                                        message = message.strip()
202                                if message != '':
203                                        single_line += ': ' + message
204                                # the other solution is to hide offline accounts
205                                elif status == 'offline':
206                                        message = helpers.get_uf_show(status)
207                                accounts.append({'name': account, 'status_line': single_line, 
208                                                'show': status, 'message': message})
209                unread_messages_no = self.plugin.roster.nb_unread
210                if unread_messages_no > 1:
211                        text = _('Gajim - %s unread messages') % unread_messages_no
212                elif unread_messages_no == 1:
213                        text = _('Gajim - 1 unread message')
214                elif len(accounts) > 1:
215                        text = _('Gajim')
216                        self.current_row = 1
217                        self.table.resize(2,1)
218                        iconset = gajim.config.get('iconset')
219                        if not iconset:
220                                iconset = 'sun'
221                        file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
222                        for acct in accounts:
223                                message = acct['message']
224                                # before reducing the chars we should assure we send unicode, else
225                                # there are possible pango TBs on 'set_markup'
226                                if type(message) == str:
227                                        message = unicode(message, encoding='utf-8')
228                                message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
229                                message = gtkgui_helpers.escape_for_pango_markup(message)
230                                self.add_status_row(file_path, acct['show'], '<span weight="bold">' + 
231                                        gtkgui_helpers.escape_for_pango_markup(acct['name']) + '</span>' 
232                                        + ' - ' + message)
233                                       
234                elif len(accounts) == 1:
235                        message = gtkgui_helpers.reduce_chars_newlines(accounts[0]['status_line'], 
236                                50, 1)
237                        message = gtkgui_helpers.escape_for_pango_markup(message)
238                        text = _('Gajim - %s') % message
239                else:
240                        text = _('Gajim - %s') % helpers.get_uf_show('offline')
241                self.text_lable.set_markup(text)
242                self.hbox.add(self.table)
243                self.win.add(self.hbox)
244               
245class RosterTooltip(BaseTooltip, StatusTable):
246        ''' Tooltip that is shown in the roster treeview '''
247        def __init__(self, plugin):
248                self.account = None
249                self.plugin = plugin
250               
251                self.image = gtk.Image()
252                self.image.set_alignment(0.5, 0.025)
253                BaseTooltip.__init__(self)
254                StatusTable.__init__(self)
255               
256        def populate(self, contacts):
257                if not contacts or len(contacts) == 0:
258                        return
259                self.create_window()
260                self.hbox = gtk.HBox()
261                self.hbox.set_homogeneous(False)
262                self.create_table()
263                # primary contact
264                prim_contact = gajim.get_highest_prio_contact_from_contacts(contacts)
265               
266                # try to find the image for the contact status
267                state_file = prim_contact.show.replace(' ', '_')
268                transport = self.plugin.roster.get_transport_name_by_jid(prim_contact.jid)
269                if transport:
270                        file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', 
271                                transport , '16x16')
272                else:
273                        iconset = gajim.config.get('iconset')
274                        if not iconset:
275                                iconset = 'sun'
276                        file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
277
278                files = []
279                file_full_path = os.path.join(file_path, state_file)
280                files.append(file_full_path + '.png')
281                files.append(file_full_path + '.gif')
282                self.image.set_from_pixbuf(None)
283                for file in files:
284                        if os.path.exists(file):
285                                self.image.set_from_file(file)
286                                break
287               
288                info = '<span size="large" weight="bold">' + prim_contact.jid + '</span>'
289                info += '\n<span weight="bold">' + _('Name: ') + '</span>' + \
290                        gtkgui_helpers.escape_for_pango_markup(prim_contact.name)
291                info += '\n<span weight="bold">' + _('Subscription: ') + '</span>' + \
292                        gtkgui_helpers.escape_for_pango_markup(prim_contact.sub)
293
294                if prim_contact.keyID:
295                        keyID = None
296                        if len(prim_contact.keyID) == 8:
297                                keyID = prim_contact.keyID
298                        elif len(prim_contact.keyID) == 16:
299                                keyID = prim_contact.keyID[8:]
300                        if keyID:
301                                info += '\n<span weight="bold">' + _('OpenPGP: ') + \
302                                        '</span>' + gtkgui_helpers.escape_for_pango_markup(keyID)
303
304                single_line, resource_str, multiple_resource= '', '', False
305                num_resources = 0
306                for contact in contacts:
307                        if contact.resource:
308                                num_resources += 1
309                if num_resources > 1:
310                        self.current_row = 1
311                        self.table.resize(2,1)
312                        info += '\n<span weight="bold">' + _('Status: ') + '</span>'
313                        for contact in contacts:
314                                if contact.resource:
315                                        status_line = self.get_status_info(contact.resource, contact.priority, 
316                                                contact.show, contact.status)
317                                        self.add_status_row(file_path, contact.show, status_line)
318                                       
319                else: # only one resource
320                        if contact.resource:
321                                info += '\n<span weight="bold">' + _('Resource: ') + \
322                                        '</span>' + gtkgui_helpers.escape_for_pango_markup(
323                                                contact.resource) + ' (' + str(contact.priority) + ')'
324                        if contact.show:
325                                info += '\n<span weight="bold">' + _('Status: ') + \
326                                        '</span>' + helpers.get_uf_show(contact.show) 
327                                if contact.status:
328                                        status = contact.status.strip()
329                                        if status != '':
330                                                # escape markup entities. Is it posible to have markup in status?
331                                                info += ' - ' + gtkgui_helpers.escape_for_pango_markup(status)
332               
333                self.text_lable.set_markup(info)
334                self.hbox.pack_start(self.image, False, False)
335                self.hbox.pack_start(self.table, True, True)
336                self.win.add(self.hbox)
337
338class FileTransfersTooltip(BaseTooltip):
339        ''' Tooltip that is shown in the notification area '''
340        def __init__(self):
341                self.text_lable = gtk.Label()
342                self.text_lable.set_line_wrap(True)
343                self.text_lable.set_alignment(0, 0)
344                self.text_lable.set_selectable(False)
345                BaseTooltip.__init__(self)
346
347        def populate(self, file_props):
348                self.create_window()
349                self.hbox = gtk.HBox()
350                text = '<b>' + _('Name: ') + '</b>' 
351                name = file_props['name']
352                if not name and file_props['file-name']:
353                        if os.path.exists(file_props['file-name']):
354                                (path, name) = os.path.split(file_props['file-name'])
355                text += gtkgui_helpers.escape_for_pango_markup(name) 
356                text += '\n<b>' + _('Type: ') + '</b>'
357                if file_props['type'] == 'r':
358                        text += _('Download')
359                else:
360                        text += _('Upload')
361                if file_props['type'] == 'r':
362                        text += '\n<b>' + _('Sender: ') + '</b>'
363                        sender = str(file_props['sender']).split('/')[0]
364                        name = gajim.get_first_contact_instance_from_jid( 
365                                file_props['tt_account'], sender).name
366                else:
367                        text += '\n<b>' + _('Recipient: ') + '</b>' 
368                        receiver = file_props['receiver']
369                        if hasattr(receiver, 'name'):
370                                receiver = receiver.name
371                        receiver = receiver.split('/')[0]
372                        if receiver.find('@') == -1:
373                                name = receiver
374                        else:
375                                name = gajim.get_first_contact_instance_from_jid( 
376                                file_props['tt_account'], receiver).name
377                text +=  gtkgui_helpers.escape_for_pango_markup(name)
378                text += '\n<b>' + _('Size: ') + '</b>' 
379                text += helpers.convert_bytes(file_props['size'])
380                text += '\n<b>' + _('Transferred: ') + '</b>' 
381                transfered_len = 0
382                if file_props.has_key('received-len'):
383                        transfered_len = file_props['received-len']
384                text += helpers.convert_bytes(transfered_len)
385                text += '\n<b>' + _('Status: ') + '</b>' 
386                status = '' 
387                if not file_props.has_key('started') or not file_props['started']:
388                        status =  _('not started')
389                elif file_props.has_key('connected'):
390                        if file_props.has_key('stopped') and \
391                                file_props['stopped'] == True:
392                                status = _('stopped')
393                        elif file_props['completed']:
394                                        status = _('completed')
395                        elif file_props['connected'] == False:
396                                if file_props['completed']:
397                                        status = _('completed') # FIXME: all those make them Completed
398                        else:
399                                if file_props.has_key('paused') and  \
400                                        file_props['paused'] == True:
401                                        status = _('paused')
402                                elif file_props.has_key('stalled') and \
403                                        file_props['stalled'] == True:
404                                        #stalled is not paused. it is like 'frozen' it stopped alone
405                                        status = _('stalled') # FIXME: all those make them Stalled
406                                else:
407                                        status = _('transferring')
408                else:
409                        status =  _('not started')
410               
411                text += status
412                self.text_lable.set_markup(text)
413                self.hbox.add(self.text_lable)
414                self.win.add(self.hbox)
Note: See TracBrowser for help on using the browser.