root/branches/gajim_0.11/src/filetransfers_window.py

Revision 7787, 32.9 kB (checked in by asterix, 21 months ago)

merge changeset from trunk except pyopenssl stuff

  • Property svn:eol-style set to LF
Line 
1##      filetransfers_window.py
2##
3## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
4## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
5## Copyright (C) 2005
6##                    Dimitur Kirov <dkirov@gmail.com>
7##                    Travis Shirk <travis@pobox.com>
8## Copyright (C) 2004-2005 Vincent Hanquez <tab@snarc.org>
9##
10## This program is free software; you can redistribute it and/or modify
11## it under the terms of the GNU General Public License as published
12## by the Free Software Foundation; version 2 only.
13##
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17## GNU General Public License for more details.
18##
19
20import gtk
21import gobject
22import pango
23import os
24import time
25
26import gtkgui_helpers
27import tooltips
28import dialogs
29
30from common import gajim
31from common import helpers
32
33C_IMAGE = 0
34C_LABELS = 1
35C_FILE = 2
36C_TIME = 3
37C_PROGRESS = 4
38C_PERCENT = 5
39C_SID = 6
40
41
42class FileTransfersWindow:
43        def __init__(self):
44                self.files_props = {'r' : {}, 's': {}}
45                self.height_diff = 0
46                self.xml = gtkgui_helpers.get_glade('filetransfers.glade')
47                self.window = self.xml.get_widget('file_transfers_window')
48                self.tree = self.xml.get_widget('transfers_list')
49                self.cancel_button = self.xml.get_widget('cancel_button')
50                self.pause_button = self.xml.get_widget('pause_restore_button')
51                self.cleanup_button = self.xml.get_widget('cleanup_button')
52                self.notify_ft_checkbox = self.xml.get_widget(
53                        'notify_ft_complete_checkbox')
54                notify = gajim.config.get('notify_on_file_complete')
55                if notify:
56                        self.notify_ft_checkbox.set_active(True)
57                else:
58                        self.notify_ft_checkbox.set_active(False)
59                self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str)
60                self.tree.set_model(self.model)
61                col = gtk.TreeViewColumn()
62
63                render_pixbuf = gtk.CellRendererPixbuf()
64
65                col.pack_start(render_pixbuf, expand = True)
66                render_pixbuf.set_property('xpad', 3)
67                render_pixbuf.set_property('ypad', 3)
68                render_pixbuf.set_property('yalign', .0)
69                col.add_attribute(render_pixbuf, 'pixbuf', 0)
70                self.tree.append_column(col)
71
72                col = gtk.TreeViewColumn(_('File'))
73                renderer = gtk.CellRendererText()
74                col.pack_start(renderer, expand=False)
75                col.add_attribute(renderer, 'markup' , C_LABELS)
76                renderer.set_property('yalign', 0.)
77                renderer = gtk.CellRendererText()
78                col.pack_start(renderer, expand=True)
79                col.add_attribute(renderer, 'markup' , C_FILE)
80                renderer.set_property('xalign', 0.)
81                renderer.set_property('yalign', 0.)
82                renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
83                col.set_resizable(True)
84                col.set_expand(True)
85                self.tree.append_column(col)
86
87                col = gtk.TreeViewColumn(_('Time'))
88                renderer = gtk.CellRendererText()
89                col.pack_start(renderer, expand=False)
90                col.add_attribute(renderer, 'markup' , C_TIME)
91                renderer.set_property('yalign', 0.5)
92                renderer.set_property('xalign', 0.5)
93                renderer = gtk.CellRendererText()
94                renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
95                col.set_resizable(True)
96                col.set_expand(False)
97                self.tree.append_column(col)
98
99                col = gtk.TreeViewColumn(_('Progress'))
100                renderer = gtk.CellRendererProgress()
101                renderer.set_property('yalign', 0.5)
102                renderer.set_property('xalign', 0.5)
103                col.pack_start(renderer, expand = False)
104                col.add_attribute(renderer, 'text' , C_PROGRESS)
105                col.add_attribute(renderer, 'value' , C_PERCENT)
106                col.set_resizable(True)
107                col.set_expand(False)
108                self.tree.append_column(col)
109
110                self.set_images()
111                self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
112                self.tree.get_selection().connect('changed', self.selection_changed)
113                self.tooltip = tooltips.FileTransfersTooltip()
114                self.file_transfers_menu = self.xml.get_widget('file_transfers_menu')
115                self.open_folder_menuitem = self.xml.get_widget('open_folder_menuitem')
116                self.cancel_menuitem = self.xml.get_widget('cancel_menuitem')
117                self.pause_menuitem = self.xml.get_widget('pause_menuitem')
118                self.continue_menuitem = self.xml.get_widget('continue_menuitem')
119                self.continue_menuitem.hide()
120                self.continue_menuitem.set_no_show_all(True)
121                self.remove_menuitem = self.xml.get_widget('remove_menuitem')
122                self.xml.signal_autoconnect(self)
123
124        def find_transfer_by_jid(self, account, jid):
125                ''' find all transfers with peer 'jid' that belong to 'account' '''
126                active_transfers = [[],[]] # ['senders', 'receivers']
127
128                # 'account' is the sender
129                for file_props in self.files_props['s'].values():
130                        if file_props['tt_account'] == account:
131                                receiver_jid = unicode(file_props['receiver']).split('/')[0]
132                                if jid == receiver_jid:
133                                        if not self.is_transfer_stoped(file_props):
134                                                active_transfers[0].append(file_props)
135
136                # 'account' is the recipient
137                for file_props in self.files_props['r'].values():
138                        if file_props['tt_account'] == account:
139                                sender_jid = unicode(file_props['sender']).split('/')[0]
140                                if jid == sender_jid:
141                                        if not self.is_transfer_stoped(file_props):
142                                                active_transfers[1].append(file_props)
143                return active_transfers
144
145        def show_completed(self, jid, file_props):
146                ''' show a dialog saying that file (file_props) has been transferred'''
147                self.window.present()
148                self.window.window.focus()
149                def on_open(widget, file_props):
150                        dialog.destroy()
151                        if not file_props.has_key('file-name'):
152                                return
153                        (path, file) = os.path.split(file_props['file-name'])
154                        if os.path.exists(path) and os.path.isdir(path):
155                                helpers.launch_file_manager(path)
156                        self.tree.get_selection().unselect_all()
157
158                if file_props['type'] == 'r':
159                        # file path is used below in 'Save in'
160                        (file_path, file_name) = os.path.split(file_props['file-name'])
161                else:
162                        file_name = file_props['name']
163                sectext = '\t' + _('Filename: %s') % file_name
164                sectext += '\n\t' + _('Size: %s') % \
165                helpers.convert_bytes(file_props['size'])
166                if file_props['type'] == 'r':
167                        jid = unicode(file_props['sender']).split('/')[0]
168                        sender_name = gajim.contacts.get_first_contact_from_jid(
169                                file_props['tt_account'], jid).get_shown_name()
170                        sender = sender_name
171                else:
172                        #You is a reply of who sent a file
173                        sender = _('You')
174                sectext += '\n\t' +_('Sender: %s') % sender
175                sectext += '\n\t' +_('Recipient: ')
176                if file_props['type'] == 's':
177                        jid = unicode(file_props['receiver']).split('/')[0]
178                        receiver_name = gajim.contacts.get_first_contact_from_jid(
179                                file_props['tt_account'], jid).get_shown_name()
180                        recipient = receiver_name
181                else:
182                        #You is a reply of who received a file
183                        recipient = _('You')
184                sectext += recipient
185                if file_props['type'] == 'r':
186                        sectext += '\n\t' +_('Saved in: %s') % file_path
187                dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
188                                _('File transfer completed'), sectext)
189                if file_props['type'] == 'r':
190                        button = gtk.Button(_('_Open Containing Folder'))
191                        button.connect('clicked', on_open, file_props)
192                        dialog.action_area.pack_start(button)
193                ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
194                def on_ok(widget):
195                        dialog.destroy()
196                ok_button.connect('clicked', on_ok)
197                dialog.show_all()
198
199        def show_request_error(self, file_props):
200                ''' show error dialog to the recipient saying that transfer
201                has been canceled'''
202                self.window.present()
203                self.window.window.focus()
204                dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.'))
205                self.tree.get_selection().unselect_all()
206
207        def show_send_error(self, file_props):
208                ''' show error dialog to the sender saying that transfer
209                has been canceled'''
210                self.window.present()
211                self.window.window.focus()
212                dialogs.InformationDialog(_('File transfer cancelled'),
213_('Connection with peer cannot be established.'))
214                self.tree.get_selection().unselect_all()
215
216        def show_stopped(self, jid, file_props, error_msg = ''):
217                self.window.present()
218                self.window.window.focus()
219                if file_props['type'] == 'r':
220                        file_name = os.path.basename(file_props['file-name'])
221                else:
222                        file_name = file_props['name']
223                sectext = '\t' + _('Filename: %s') % file_name
224                sectext += '\n\t' + _('Recipient: %s') % jid
225                if error_msg:
226                        sectext += '\n\t' + _('Error message: %s') % error_msg
227                dialogs.ErrorDialog(_('File transfer stopped by the contact at the other '
228                        'end'), sectext)
229                self.tree.get_selection().unselect_all()
230
231        def show_file_send_request(self, account, contact):
232                def on_ok(widget):
233                        file_dir = None
234                        files_path_list = dialog.get_filenames()
235                        files_path_list = gtkgui_helpers.decode_filechooser_file_paths(
236                                files_path_list)
237                        for file_path in files_path_list:
238                                if self.send_file(account, contact, file_path) and file_dir is None:
239                                        file_dir = os.path.dirname(file_path)
240                        if file_dir:
241                                gajim.config.set('last_send_dir', file_dir)
242                                dialog.destroy()
243
244                dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
245                        gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
246                        gtk.RESPONSE_OK,
247                        True, # select multiple true as we can select many files to send
248                        gajim.config.get('last_send_dir'),
249                        on_response_ok = on_ok,
250                        on_response_cancel = lambda e:dialog.destroy()
251                        )
252
253                btn = gtk.Button(_('_Send'))
254                btn.set_property('can-default', True)
255                # FIXME: add send icon to this button (JUMP_TO)
256                dialog.add_action_widget(btn, gtk.RESPONSE_OK)
257                dialog.set_default_response(gtk.RESPONSE_OK)
258                btn.show()
259
260        def send_file(self, account, contact, file_path):
261                ''' start the real transfer(upload) of the file '''
262                if gtkgui_helpers.file_is_locked(file_path):
263                        pritext = _('Gajim cannot access this file')
264                        sextext = _('This file is being used by another process.')
265                        dialogs.ErrorDialog(pritext, sextext)
266                        return
267
268                if isinstance(contact, str):
269                        if contact.find('/') == -1:
270                                return
271                        (jid, resource) = contact.split('/', 1)
272                        contact = gajim.contacts.create_contact(jid = jid,
273                                resource = resource)
274                (file_dir, file_name) = os.path.split(file_path)
275                file_props = self.get_send_file_props(account, contact,
276                                file_path, file_name)
277                if file_props is None:
278                        return False
279                self.add_transfer(account, contact, file_props)
280                gajim.connections[account].send_file_request(file_props)
281                return True
282
283        def _start_receive(self, file_path, account, contact, file_props):
284                file_dir = os.path.dirname(file_path)
285                if file_dir:
286                        gajim.config.set('last_save_dir', file_dir)
287                file_props['file-name'] = file_path
288                self.add_transfer(account, contact, file_props)
289                gajim.connections[account].send_file_approval(file_props)
290
291        def show_file_request(self, account, contact, file_props):
292                ''' show dialog asking for comfirmation and store location of new
293                file requested by a contact'''
294                if file_props is None or not file_props.has_key('name'):
295                        return
296                sec_text = '\t' + _('File: %s') % file_props['name']
297                if file_props.has_key('size'):
298                        sec_text += '\n\t' + _('Size: %s') % \
299                                helpers.convert_bytes(file_props['size'])
300                if file_props.has_key('mime-type'):
301                        sec_text += '\n\t' + _('Type: %s') % file_props['mime-type']
302                if file_props.has_key('desc'):
303                        sec_text += '\n\t' + _('Description: %s') % file_props['desc']
304                prim_text = _('%s wants to send you a file:') % contact.jid
305                dialog, dialog2 = None, None
306
307                def on_response_ok(widget, account, contact, file_props):
308                        dialog.destroy()
309
310                        def on_ok(widget, account, contact, file_props):
311                                file_path = dialog2.get_filename()
312                                file_path = gtkgui_helpers.decode_filechooser_file_paths(
313                                        (file_path,))[0]
314                                if os.path.exists(file_path):
315                                        # check if we have write permissions
316                                        if not os.access(file_path, os.W_OK):
317                                                file_name = os.path.basename(file_path)
318                                                dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name),
319                                                _('A file with this name already exists and you do not have permission to overwrite it.'))
320                                                return
321                                        stat = os.stat(file_path)
322                                        dl_size = stat.st_size
323                                        file_size = file_props['size']
324                                        dl_finished = dl_size >= file_size
325                                        dialog = dialogs.FTOverwriteConfirmationDialog(
326                                                _('This file already exists'), _('What do you want to do?'),
327                                                not dl_finished)
328                                        dialog.set_transient_for(dialog2)
329                                        dialog.set_destroy_with_parent(True)
330                                        response = dialog.get_response()
331                                        if response < 0:
332                                                return
333                                        elif response == 100:
334                                                file_props['offset'] = dl_size
335                                else:
336                                        dirname = os.path.dirname(file_path)
337                                        if not os.access(dirname, os.W_OK):
338                                                dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.'))
339                                                return
340                                dialog2.destroy()
341                                self._start_receive(file_path, account, contact, file_props)
342
343                        def on_cancel(widget, account, contact, file_props):
344                                dialog2.destroy()
345                                gajim.connections[account].send_file_rejection(file_props)
346
347                        dialog2 = dialogs.FileChooserDialog(
348                                title_text = _('Save File as...'),
349                                action = gtk.FILE_CHOOSER_ACTION_SAVE,
350                                buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
351                                gtk.STOCK_SAVE, gtk.RESPONSE_OK),
352                                default_response = gtk.RESPONSE_OK,
353                                current_folder = gajim.config.get('last_save_dir'),
354                                on_response_ok = (on_ok, account, contact, file_props),
355                                on_response_cancel = (on_cancel, account, contact, file_props))
356
357                        dialog2.set_current_name(file_props['name'])
358                        dialog2.connect('delete-event', lambda widget, event:
359                                on_cancel(widget, account, contact, file_props))
360
361                def on_response_cancel(widget, account, file_props):
362                        dialog.destroy()
363                        gajim.connections[account].send_file_rejection(file_props)
364
365                dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
366                        on_response_ok = (on_response_ok, account, contact, file_props),
367                        on_response_cancel = (on_response_cancel, account, file_props))
368                dialog.connect('delete-event', lambda widget, event:
369                        on_response_cancel(widget, account, file_props))
370                dialog.popup()
371
372        def set_images(self):
373                ''' create pixbufs for status images in transfer rows'''
374                self.images = {}
375                self.images['upload'] = self.window.render_icon(gtk.STOCK_GO_UP,
376                        gtk.ICON_SIZE_MENU)
377                self.images['download'] = self.window.render_icon(gtk.STOCK_GO_DOWN,
378                        gtk.ICON_SIZE_MENU)
379                self.images['stop'] = self.window.render_icon(gtk.STOCK_STOP,
380                        gtk.ICON_SIZE_MENU)
381                self.images['waiting'] = self.window.render_icon(gtk.STOCK_REFRESH,
382                        gtk.ICON_SIZE_MENU)
383                self.images['pause'] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE,
384                        gtk.ICON_SIZE_MENU)
385                self.images['continue'] = self.window.render_icon(gtk.STOCK_MEDIA_PLAY,
386                        gtk.ICON_SIZE_MENU)
387                self.images['ok'] = self.window.render_icon(gtk.STOCK_APPLY,
388                        gtk.ICON_SIZE_MENU)
389
390        def set_status(self, typ, sid, status):
391                ''' change the status of a transfer to state 'status' '''
392                iter = self.get_iter_by_sid(typ, sid)
393                if iter is None:
394                        return
395                sid = self.model[iter][C_SID].decode('utf-8')
396                file_props = self.files_props[sid[0]][sid[1:]]
397                if status == 'stop':
398                        file_props['stopped'] = True
399                elif status == 'ok':
400                        file_props['completed'] = True
401                self.model.set(iter, C_IMAGE, self.images[status])
402                path = self.model.get_path(iter)
403                self.select_func(path)
404
405        def _format_percent(self, percent):
406                ''' add extra spaces from both sides of the percent, so that
407                progress string has always a fixed size'''
408                _str = '          '
409                if percent != 100.:
410                        _str += ' '
411                if percent < 10:
412                        _str += ' '
413                _str += unicode(percent) + '%          \n'
414                return _str
415
416        def _format_time(self, _time):
417                times = { 'hours': 0, 'minutes': 0, 'seconds': 0 }
418                _time = int(_time)
419                times['seconds'] = _time % 60
420                if _time >= 60:
421                        _time /= 60
422                        times['minutes'] = _time % 60
423                        if _time >= 60:
424                                times['hours'] = _time / 60
425
426                #Print remaining time in format 00:00:00
427                #You can change the places of (hours), (minutes), (seconds) -
428                #they are not translatable.
429                return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d')  % times
430
431        def _get_eta_and_speed(self, full_size, transfered_size, elapsed_time):
432                if elapsed_time == 0:
433                        return 0., 0.
434                speed = round(float(transfered_size) / elapsed_time)
435                if speed == 0.:
436                        return 0., 0.
437                remaining_size = full_size - transfered_size
438                eta = remaining_size / speed
439                return eta, speed
440
441        def _remove_transfer(self, iter, sid, file_props):
442                self.model.remove(iter)
443                if  file_props.has_key('tt_account'):
444                        # file transfer is set
445                        account = file_props['tt_account']
446                        if gajim.connections.has_key(account):
447                                # there is a connection to the account
448                                gajim.connections[account].remove_transfer(file_props)
449                        if file_props['type'] == 'r': # we receive a file
450                                other = file_props['sender']
451                        else: # we send a file
452                                other = file_props['receiver']
453                        if isinstance(other, unicode):
454                                jid = gajim.get_jid_without_resource(other)
455                        else: # It's a Contact instance
456                                jid = other.jid
457                        for ev_type in ('file-error', 'file-completed', 'file-request-error',
458                        'file-send-error', 'file-stopped'):
459                                for event in gajim.events.get_events(account, jid, [ev_type]):
460                                        if event.parameters['sid'] == file_props['sid']:
461                                                gajim.events.remove_events(account, jid, event)
462                                                gajim.interface.roster.draw_contact(jid, account)
463                                                gajim.interface.roster.show_title()
464                del(self.files_props[sid[0]][sid[1:]])
465                del(file_props)
466
467        def set_progress(self, typ, sid, transfered_size, iter = None):
468                ''' change the progress of a transfer with new transfered size'''
469                if not self.files_props[typ].has_key(sid):
470                        return
471                file_props = self.files_props[typ][sid]
472                full_size = int(file_props['size'])
473                if full_size == 0:
474                        percent = 0
475                else:
476                        percent = round(float(transfered_size) / full_size * 100)
477                if iter is None:
478                        iter = self.get_iter_by_sid(typ, sid)
479                if iter is not None:
480                        just_began = False
481                        if self.model[iter][C_PERCENT] == 0 and int(percent > 0):
482                                just_began = True
483                        text = self._format_percent(percent)
484                        if transfered_size == 0:
485                                text += '0'
486                        else:
487                                text += helpers.convert_bytes(transfered_size)
488                        text += '/' + helpers.convert_bytes(full_size)
489                        # Kb/s
490
491                        # remaining time
492                        if file_props.has_key('offset') and file_props['offset']:
493                                transfered_size -= file_props['offset']
494                                full_size -= file_props['offset']
495                        eta, speed = self._get_eta_and_speed(full_size, transfered_size,
496                                file_props['elapsed-time'])
497
498                        self.model.set(iter, C_PROGRESS, text)
499                        self.model.set(iter, C_PERCENT, int(percent))
500                        text = self._format_time(eta)
501                        text += '\n'
502                        #This should make the string Kb/s,
503                        #where 'Kb' part is taken from %s.
504                        #Only the 's' after / (which means second) should be translated.
505                        text += _('(%(filesize_unit)s/s)') % {'filesize_unit':
506                                helpers.convert_bytes(speed)}
507                        self.model.set(iter, C_TIME, text)
508
509                        # try to guess what should be the status image
510                        if file_props['type'] == 'r':
511                                status = 'download'
512                        else:
513                                status = 'upload'
514                        if file_props.has_key('paused') and file_props['paused'] == True:
515                                status = 'pause'
516                        elif file_props.has_key('stalled') and file_props['stalled'] == True:
517                                status = 'waiting'
518                        if file_props.has_key('connected') and file_props['connected'] == False:
519                                status = 'stop'
520                        self.model.set(iter, 0, self.images[status])
521                        if transfered_size == full_size:
522                                self.set_status(typ, sid, 'ok')
523                        elif just_began:
524                                path = self.model.get_path(iter)
525                                self.select_func(path)
526
527        def get_iter_by_sid(self, typ, sid):
528                '''returns iter to the row, which holds file transfer, identified by the
529                session id'''
530                iter = self.model.get_iter_root()
531                while iter:
532                        if typ + sid == self.model[iter][C_SID].decode('utf-8'):
533                                return iter
534                        iter = self.model.iter_next(iter)
535
536        def get_send_file_props(self, account, contact, file_path, file_name):
537                ''' create new file_props dict and set initial file transfer
538                properties in it'''
539                file_props = {'file-name' : file_path, 'name' : file_name,
540                        'type' : 's'}
541                if os.path.isfile(file_path):
542                        stat = os.stat(file_path)
543                else:
544                        dialogs.ErrorDialog(_('Invalid File'), _('File: ')  + file_path)
545                        return None
546                if stat[6] == 0:
547                        dialogs.ErrorDialog(_('Invalid File'),
548                        _('It is not possible to send empty files'))
549                        return None
550                file_props['elapsed-time'] = 0
551                file_props['size'] = unicode(stat[6])
552                file_props['sid'] = helpers.get_random_string_16()
553                file_props['completed'] = False
554                file_props['started'] = False
555                file_props['sender'] = account
556                file_props['receiver'] = contact
557                file_props['tt_account'] = account
558                return file_props
559
560        def add_transfer(self, account, contact, file_props):
561                ''' add new transfer to FT window and show the FT window '''
562                self.on_transfers_list_leave_notify_event(None)
563                if file_props is None:
564                        return
565                file_props['elapsed-time'] = 0
566                self.files_props[file_props['type']][file_props['sid']] = file_props
567                iter = self.model.append()
568                text_labels = '<b>' + _('Name: ') + '</b>\n' 
569                if file_props['type'] == 'r':
570                        text_labels += '<b>' + _('Sender: ') + '</b>' 
571                else:
572                        text_labels += '<b>' + _('Recipient: ') + '</b>' 
573
574                if file_props['type'] == 'r':
575                        (file_path, file_name) = os.path.split(file_props['file-name'])
576                else:
577                        file_name = file_props['name']
578                text_props = gtkgui_helpers.escape_for_pango_markup(file_name) + '\n'
579                text_props += contact.get_shown_name()
580                self.model.set(iter, 1, text_labels, 2, text_props, C_SID,
581                        file_props['type'] + file_props['sid'])
582                self.set_progress(file_props['type'], file_props['sid'], 0, iter)
583                if file_props.has_key('started') and file_props['started'] is False:
584                        status = 'waiting'
585                elif file_props['type'] == 'r':
586                        status = 'download'
587                else:
588                        status = 'upload'
589                file_props['tt_account'] = account
590                self.set_status(file_props['type'], file_props['sid'], status)
591                self.set_cleanup_sensitivity()
592                self.window.show_all()
593
594        def on_transfers_list_motion_notify_event(self, widget, event):
595                pointer = self.tree.get_pointer()
596                orig = widget.window.get_origin()
597                props = widget.get_path_at_pos(int(event.x), int(event.y))
598                self.height_diff = pointer[1] - int(event.y)
599                if self.tooltip.timeout > 0:
600                        if not props or self.tooltip.id != props[0]:
601                                self.tooltip.hide_tooltip()
602                if props:
603                        [row, col, x, y] = props
604                        iter = None
605                        try:
606                                iter = self.model.get_iter(row)
607                        except:
608                                self.tooltip.hide_tooltip()
609                                return
610                        sid = self.model[iter][C_SID].decode('utf-8')
611                        file_props = self.files_props[sid[0]][sid[1:]]
612                        if file_props is not None:
613                                if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
614                                        self.tooltip.id = row
615                                        self.tooltip.timeout = gobject.timeout_add(500,
616                                                self.show_tooltip, widget)
617
618        def on_transfers_list_leave_notify_event(self, widget = None, event = None):
619                if event is not None:
620                        self.height_diff = int(event.y)
621                elif self.height_diff is 0:
622                        return
623                pointer = self.tree.get_pointer()
624                props = self.tree.get_path_at_pos(pointer[0],
625                        pointer[1] - self.height_diff)
626                if self.tooltip.timeout > 0:
627                        if not props or self.tooltip.id == props[0]:
628                                self.tooltip.hide_tooltip()
629
630        def on_transfers_list_row_activated(self, widget, path, col):
631                # try to open the containing folder
632                self.on_open_folder_menuitem_activate(widget)
633
634        def is_transfer_paused(self, file_props):
635                if file_props.has_key('stopped') and file_props['stopped']:
636                        return False
637                if file_props.has_key('completed') and file_props['completed']:
638                        return False
639                if not file_props.has_key('disconnect_cb'):
640                        return False
641                return file_props['paused']
642
643        def is_transfer_active(self, file_props):
644                if file_props.has_key('stopped') and file_props['stopped']:
645                        return False
646                if file_props.has_key('completed') and file_props['completed']:
647                        return False
648                if not file_props.has_key('started') or not file_props['started']:
649                        return False
650                if not file_props.has_key('paused'):
651                        return True
652                return not file_props['paused']
653
654        def is_transfer_stoped(self, file_props):
655                if file_props.has_key('error') and file_props['error'] != 0:
656                        return True
657                if file_props.has_key('completed') and file_props['completed']:
658                        return True
659                if file_props.has_key('connected') and file_props['connected'] == False:
660                        return True
661                if not file_props.has_key('stopped') or not file_props['stopped']:
662                        return False
663                return True
664
665        def set_cleanup_sensitivity(self):
666                ''' check if there are transfer rows and set cleanup_button
667                sensitive, or insensitive if model is empty'''
668                if len(self.model) == 0:
669                        self.cleanup_button.set_sensitive(False)
670                else:
671                        self.cleanup_button.set_sensitive(True)
672
673        def set_all_insensitive(self):
674                ''' make all buttons/menuitems insensitive '''
675                self.pause_button.set_sensitive(False)
676                self.pause_menuitem.set_sensitive(False)
677                self.continue_menuitem.set_sensitive(False)
678                self.remove_menuitem.set_sensitive(False)
679                self.cancel_button.set_sensitive(False)
680                self.cancel_menuitem.set_sensitive(False)
681                self.open_folder_menuitem.set_sensitive(False)
682                self.set_cleanup_sensitivity()
683
684        def set_buttons_sensitive(self, path, is_row_selected):
685                ''' make buttons/menuitems sensitive as appropriate to
686                the state of file transfer located at path 'path' '''
687                if path is None:
688                        self.set_all_insensitive()
689                        return
690                current_iter = self.model.get_iter(path)
691                sid = self.model[current_iter][C_SID].decode('utf-8')
692                file_props = self.files_props[sid[0]][sid[1:]]
693                self.remove_menuitem.set_sensitive(is_row_selected)
694                self.open_folder_menuitem.set_sensitive(is_row_selected)
695                is_stopped = False
696                if self.is_transfer_stoped(file_props):
697                        is_stopped = True
698                self.cancel_button.set_sensitive(not is_stopped)
699                self.cancel_menuitem.set_sensitive(not is_stopped)
700                if not is_row_selected:
701                        # no selection, disable the buttons
702                        self.set_all_insensitive()
703                elif not is_stopped:
704                        if self.is_transfer_active(file_props):
705                                # file transfer is active
706                                self.toggle_pause_continue(True)
707                                self.pause_button.set_sensitive(True)
708                        elif self.is_transfer_paused(file_props):
709                                # file transfer is paused
710                                self.toggle_pause_continue(False)
711                                self.pause_button.set_sensitive(True)
712                        else:
713                                self.pause_button.set_sensitive(False)
714                                self.pause_menuitem.set_sensitive(False)
715                                self.continue_menuitem.set_sensitive(False)
716                else:
717                        self.pause_button.set_sensitive(False)
718                        self.pause_menuitem.set_sensitive(False)
719                        self.continue_menuitem.set_sensitive(False)
720                return True
721
722        def selection_changed(self, args):
723                ''' selection has changed - change the sensitivity of the
724                buttons/menuitems'''
725                selection = args
726                selected = selection.get_selected_rows()
727                if selected[1] != []:
728                        selected_path = selected[1][0]
729                        self.select_func(selected_path)
730                else:
731                        self.set_all_insensitive()
732
733        def select_func(self, path):
734                is_selected = False
735                selected = self.tree.get_selection().get_selected_rows()
736                if selected[1] != []:
737                        selected_path = selected[1][0]
738                        if selected_path == path:
739                                is_selected = True
740                self.set_buttons_sensitive(path, is_selected)
741                self.set_cleanup_sensitivity()
742                return True
743
744        def on_cleanup_button_clicked(self, widget):
745                i = len(self.model) - 1
746                while i >= 0:
747                        iter = self.model.get_iter((i))
748                        sid = self.model[iter][C_SID].decode('utf-8')
749                        file_props = self.files_props[sid[0]][sid[1:]]
750                        if self.is_transfer_stoped(file_props):
751                                self._remove_transfer(iter, sid, file_props)
752                        i -= 1
753                self.tree.get_selection().unselect_all()
754                self.set_all_insensitive()
755
756        def toggle_pause_continue(self, status):
757                if status:
758                        label = _('Pause')
759                        self.pause_button.set_label(label)
760                        self.pause_button.set_image(gtk.image_new_from_stock(
761                                gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU))
762
763                        self.pause_menuitem.set_sensitive(True)
764                        self.pause_menuitem.set_no_show_all(False)
765                        self.continue_menuitem.hide()
766                        self.continue_menuitem.set_no_show_all(True)
767
768                else:
769                        label = _('_Continue')
770                        self.pause_button.set_label(label)
771                        self.pause_button.set_image(gtk.image_new_from_stock(
772                                gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU))
773                        self.pause_menuitem.hide()
774                        self.pause_menuitem.set_no_show_all(True)
775                        self.continue_menuitem.set_sensitive(True)