diff --git a/src/dialogs.py b/src/dialogs.py index b1377afb36620d3f31b1d8e1ab956ebcb6bc7192..be36d389ec902ceea8a9fc9685bd2f231f18809b 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -4,7 +4,7 @@ ## - Yann Le Boulanger <asterix@lagaule.org> ## - Vincent Hanquez <tab@snarc.org> ## - Nikos Kouremenos <kourem@gmail.com> -## - Dimitur Kirov <dkirov@gmail.com> +## - Dimitur Kirov <dkirov@gmail.com> ## ## Copyright (C) 2003-2005 Gajim Team ## @@ -26,12 +26,13 @@ import os import gtkgui_helpers from vcard import VcardWindow +from filetransfers_window import FileTransfersWindow from gajim_themes_window import GajimThemesWindow from advanced import AdvancedConfigurationWindow from gajim import Contact from common import gajim -from common import i18n from common import helpers +from common import i18n _ = i18n._ APP = i18n.APP @@ -521,381 +522,6 @@ class InformationDialog(HigDialog): def on_ok_button_clicked(self, widget): self.destroy() -class BaseTooltip: - ''' Base Tooltip . Usage: - tooltip = BaseTooltip() - .... - tooltip.show_tooltip('', window_postions, widget_postions) - .... - if tooltip.timeout != 0: - tooltip.hide_tooltip() - ''' - def __init__(self): - self.timeout = 0 - self.prefered_position = [0, 0] - self.win = None - self.id = None - - def populate(self, data): - ''' this method must be overriden by all extenders ''' - self.create_window() - self.win.add(gtk.Label(data)) - - def create_window(self): - ''' create a popup window each time tooltip is requested ''' - self.win = gtk.Window(gtk.WINDOW_POPUP) - self.win.set_border_width(3) - self.win.set_resizable(False) - self.win.set_name('gtk-tooltips') - - - self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) - self.win.connect_after('expose_event', self.expose) - self.win.connect('size-request', self.size_request) - self.win.connect('motion-notify-event', self.motion_notify_event) - - def motion_notify_event(self, widget, event): - self.hide_tooltip() - - def size_request(self, widget, requisition): - screen = self.win.get_screen() - half_width = requisition.width / 2 + 1 - if self.prefered_position[0] < half_width: - self.prefered_position[0] = 0 - elif self.prefered_position[0] + requisition.width > screen.get_width() \ - + half_width: - self.prefered_position[0] = screen.get_width() - requisition.width - else: - self.prefered_position[0] -= half_width - screen.get_height() - if self.prefered_position[1] + requisition.height > screen.get_height(): - # flip tooltip up - self.prefered_position[1] -= requisition.height + self.widget_height + 8 - if self.prefered_position[1] < 0: - self.prefered_position[1] = 0 - self.win.move(self.prefered_position[0], self.prefered_position[1]) - - def expose(self, widget, event): - style = self.win.get_style() - size = self.win.get_size() - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, - self.win, 'tooltip', 0, 0, -1, 1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, - self.win, 'tooltip', 0, size[1] - 1, -1, 1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, - self.win, 'tooltip', 0, 0, 1, -1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, - self.win, 'tooltip', size[0] - 1, 0, 1, -1) - return True - - def show_tooltip(self, data, widget_pos, win_size): - self.populate(data) - new_x = win_size[0] + widget_pos[0] - new_y = win_size[1] + widget_pos[1] + 4 - self.prefered_position = [new_x, new_y] - self.widget_height = widget_pos[1] - self.win.ensure_style() - self.win.show_all() - - def hide_tooltip(self): - if(self.timeout > 0): - gobject.source_remove(self.timeout) - self.timeout = 0 - if self.win: - self.win.destroy() - self.win = None - self.id = None - -class StatusTable: - ''' Contains methods for creating status table. This - is used in Roster and NotificationArea tooltips ''' - def __init__(self): - self.current_row = 1 - self.table = None - self.text_lable = None - - def create_table(self): - self.table = gtk.Table(3, 1) - self.table.set_property('column-spacing', 6) - self.text_lable = gtk.Label() - self.text_lable.set_line_wrap(True) - self.text_lable.set_alignment(0, 0) - self.text_lable.set_selectable(False) - self.table.attach(self.text_lable, 1, 4, 1, 2) - - def get_status_info(self, resource, priority, show, status): - str_status = resource + ' (' + str(priority) + ')' - if status: - status = status.strip() - if status != '': - if gtk.gtk_version < (2, 6, 0) or gtk.pygtk_version < (2, 6, 0): - # FIXME: check and do the same if we have more than one \n - status = gtkgui_helpers.reduce_chars_newlines(status, 50, 1) - else: - status = gtkgui_helpers.reduce_chars_newlines(status, 0, 1) - str_status += ' - ' + status - return gtkgui_helpers.escape_for_pango_markup(str_status) - - def add_status_row(self, file_path, show, str_status): - ''' appends a new row with status icon to the table ''' - self.current_row += 1 - state_file = show.replace(' ', '_') - files = [] - files.append(os.path.join(file_path, state_file + '.png')) - files.append(os.path.join(file_path, state_file + '.gif')) - image = gtk.Image() - image.set_from_pixbuf(None) - spacer = gtk.Label(' ') - for file in files: - if os.path.exists(file): - image.set_from_file(file) - break - image.set_alignment(0.01, 1) - self.table.attach(spacer, 1, 2, self.current_row, - self.current_row + 1, 0, 0, 0, 0) - self.table.attach(image,2,3,self.current_row, - self.current_row + 1, 0, 0, 3, 0) - image.set_alignment(0.01, 1) - status_label = gtk.Label() - status_label.set_markup(str_status) - status_label.set_alignment(00, 0) - self.table.attach(status_label, 3, 4, self.current_row, - self.current_row + 1, gtk.EXPAND | gtk.FILL, 0, 0, 0) - -class NotificationAreaTooltip(BaseTooltip, StatusTable): - ''' Tooltip that is shown in the notification area ''' - def __init__(self, plugin): - self.plugin = plugin - BaseTooltip.__init__(self) - StatusTable.__init__(self) - - def populate(self, data): - self.create_window() - self.create_table() - self.hbox = gtk.HBox() - self.table.set_property('column-spacing', 1) - text, single_line, accounts = '', '', [] - if gajim.contacts: - for account in gajim.contacts.keys(): - status_idx = gajim.connections[account].connected - # uncomment the following to hide offline accounts - # if status_idx == 0: continue - from common.connection import STATUS_LIST - status = STATUS_LIST[status_idx] - message = gajim.connections[account].status - single_line = helpers.get_uf_show(status) - if message is None: - message = '' - else: - message = message.strip() - if message != '': - single_line += ': ' + message - # the other solution is to hide offline accounts - elif status == 'offline': - message = helpers.get_uf_show(status) - accounts.append({'name': account, 'status_line': single_line, - 'show': status, 'message': message}) - unread_messages_no = self.plugin.roster.nb_unread - if unread_messages_no > 1: - text = _('Gajim - %s unread messages') % unread_messages_no - elif unread_messages_no == 1: - text = _('Gajim - 1 unread message') - elif len(accounts) > 1: - text = _('Gajim') - self.current_row = 1 - self.table.resize(2,1) - iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'sun' - file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') - for acct in accounts: - message = gtkgui_helpers.reduce_chars_newlines(acct['message'], 50, 1) - message = gtkgui_helpers.escape_for_pango_markup(message) - self.add_status_row(file_path, acct['show'], '<span weight="bold">' + - gtkgui_helpers.escape_for_pango_markup(acct['name']) + '</span>' - + ' - ' + message) - - elif len(accounts) == 1: - message = gtkgui_helpers.reduce_chars_newlines(accounts[0]['status_line'], - 50, 1) - message = gtkgui_helpers.escape_for_pango_markup(message) - text = _('Gajim - %s') % message - else: - text = _('Gajim - %s') % helpers.get_uf_show('offline') - self.text_lable.set_markup(text) - self.hbox.add(self.table) - self.win.add(self.hbox) - -class FileTransfersTooltip(BaseTooltip): - ''' Tooltip that is shown in the notification area ''' - def __init__(self): - self.text_lable = gtk.Label() - self.text_lable.set_line_wrap(True) - self.text_lable.set_alignment(0, 0) - self.text_lable.set_selectable(False) - BaseTooltip.__init__(self) - - def populate(self, file_props): - self.create_window() - self.hbox = gtk.HBox() - text = '<b>' + _('Name: ') + '</b>' - name = file_props['name'] - if not name and file_props['file-name']: - if os.path.exists(file_props['file-name']): - (path, name) = os.path.split(file_props['file-name']) - text += gtkgui_helpers.escape_for_pango_markup(name) - text += '\n<b>' + _('Type: ') + '</b>' - if file_props['type'] == 'r': - text += _('Download') - else: - text += _('Upload') - if file_props['type'] == 'r': - text += '\n<b>' + _('Sender: ') + '</b>' - sender = str(file_props['sender']).split('/')[0] - name = gajim.get_first_contact_instance_from_jid( - file_props['tt_account'], sender).name - else: - text += '\n<b>' + _('Recipient: ') + '</b>' - receiver = file_props['receiver'] - if hasattr(receiver, 'name'): - receiver = receiver.name - receiver = receiver.split('/')[0] - if receiver.find('@') == -1: - name = receiver - else: - name = gajim.get_first_contact_instance_from_jid( - file_props['tt_account'], receiver).name - text += gtkgui_helpers.escape_for_pango_markup(name) - text += '\n<b>' + _('Size: ') + '</b>' - text += helpers.convert_bytes(file_props['size']) - text += '\n<b>' + _('Transferred: ') + '</b>' - transfered_len = 0 - if file_props.has_key('received-len'): - transfered_len = file_props['received-len'] - text += helpers.convert_bytes(transfered_len) - text += '\n<b>' + _('Status: ') + '</b>' - status = '' - if not file_props.has_key('started') or not file_props['started']: - status = _('not started') - elif file_props.has_key('connected'): - if file_props.has_key('stopped') and \ - file_props['stopped'] == True: - status = _('stopped') - elif file_props['completed']: - status = _('completed') - elif file_props['connected'] == False: - if file_props['completed']: - status = _('completed') - else: - if file_props.has_key('paused') and \ - file_props['paused'] == True: - status = _('paused') - elif file_props.has_key('stalled') and \ - file_props['stalled'] == True: - status = _('stalled') - else: - status = _('transferring') - else: - status = _('not started') - - text += status - self.text_lable.set_markup(text) - self.hbox.add(self.text_lable) - self.win.add(self.hbox) - -class RosterTooltip(BaseTooltip, StatusTable): - ''' Tooltip that is shown in the roster treeview ''' - def __init__(self, plugin): - self.account = None - self.plugin = plugin - - self.image = gtk.Image() - self.image.set_alignment(0.5, 0.025) - BaseTooltip.__init__(self) - StatusTable.__init__(self) - - def populate(self, contacts): - if not contacts or len(contacts) == 0: - return - self.create_window() - self.hbox = gtk.HBox() - self.hbox.set_homogeneous(False) - self.create_table() - # primary contact - prim_contact = gajim.get_highest_prio_contact_from_contacts(contacts) - - # try to find the image for the contact status - state_file = prim_contact.show.replace(' ', '_') - transport = self.plugin.roster.get_transport_name_by_jid(prim_contact.jid) - if transport: - file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', - transport , '16x16') - else: - iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'sun' - file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') - - files = [] - file_full_path = os.path.join(file_path, state_file) - files.append(file_full_path + '.png') - files.append(file_full_path + '.gif') - self.image.set_from_pixbuf(None) - for file in files: - if os.path.exists(file): - self.image.set_from_file(file) - break - - info = '<span size="large" weight="bold">' + prim_contact.jid + '</span>' - info += '\n<span weight="bold">' + _('Name: ') + '</span>' + \ - gtkgui_helpers.escape_for_pango_markup(prim_contact.name) - info += '\n<span weight="bold">' + _('Subscription: ') + '</span>' + \ - gtkgui_helpers.escape_for_pango_markup(prim_contact.sub) - - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - info += '\n<span weight="bold">' + _('OpenPGP: ') + \ - '</span>' + gtkgui_helpers.escape_for_pango_markup(keyID) - - single_line, resource_str, multiple_resource= '', '', False - num_resources = 0 - for contact in contacts: - if contact.resource: - num_resources += 1 - if num_resources > 1: - self.current_row = 1 - self.table.resize(2,1) - info += '\n<span weight="bold">' + _('Status: ') + '</span>' - for contact in contacts: - if contact.resource: - status_line = self.get_status_info(contact.resource, contact.priority, - contact.show, contact.status) - self.add_status_row(file_path, contact.show, status_line) - - else: # only one resource - if contact.resource: - info += '\n<span weight="bold">' + _('Resource: ') + \ - '</span>' + gtkgui_helpers.escape_for_pango_markup( - contact.resource) + ' (' + str(contact.priority) + ')' - if contact.show: - info += '\n<span weight="bold">' + _('Status: ') + \ - '</span>' + helpers.get_uf_show(contact.show) - if contact.status: - status = contact.status.strip() - if status != '': - # escape markup entities. Is it posible to have markup in status? - info += ' - ' + gtkgui_helpers.escape_for_pango_markup(status) - - self.text_lable.set_markup(info) - self.hbox.pack_start(self.image, False, False) - self.hbox.pack_start(self.table, True, True) - self.win.add(self.hbox) - class InputDialog: '''Class for Input dialog''' def __init__(self, title, label_str, input_str = None, is_modal = True, ok_handler = None): @@ -1552,693 +1178,6 @@ class XMLConsoleWindow: # remove us from open windows del self.plugin.windows[self.account]['xml_console'] widget.destroy() - -class FileTransfersWindow: - def __init__(self, plugin): - self.files_props = {'r' : {}, 's': {}} - self.plugin = plugin - self.height_diff = 0 - self.last_save_dir = None - self.xml = gtk.glade.XML(GTKGUI_GLADE, 'file_transfers_window', APP) - self.window = self.xml.get_widget('file_transfers_window') - self.tree = self.xml.get_widget('transfers_list') - self.cancel_button = self.xml.get_widget('cancel_button') - self.pause_button = self.xml.get_widget('pause_restore_button') - self.remove_button = self.xml.get_widget('remove_button') - self.notify_ft_checkbox = self.xml.get_widget( - 'notify_ft_complete_checkbox') - notify = gajim.config.get('notify_on_file_complete') - if notify: - self.notify_ft_checkbox.set_active(True) - else: - self.notify_ft_checkbox.set_active(False) - self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, str) - self.tree.set_model(self.model) - col = gtk.TreeViewColumn() - - render_pixbuf = gtk.CellRendererPixbuf() - - col.pack_start(render_pixbuf, expand = True) - render_pixbuf.set_property('xpad', 3) - render_pixbuf.set_property('ypad', 3) - render_pixbuf.set_property('yalign', .0) - col.add_attribute(render_pixbuf, 'pixbuf', 0) - self.tree.append_column(col) - - col = gtk.TreeViewColumn(_('File')) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'markup' , 1) - renderer.set_property('yalign', 0.) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.add_attribute(renderer, 'markup' , 2) - renderer.set_property('xalign', 0.) - renderer.set_property('yalign', 0.) - col.set_resizable(True) - col.set_expand(True) - self.tree.append_column(col) - - col = gtk.TreeViewColumn(_('Progress')) - renderer = gtk.CellRendererText() - renderer.set_property('yalign', 0.) - renderer.set_property('xalign', 0.) - col.pack_start(renderer, expand = True) - col.set_expand(False) - col.add_attribute(renderer, 'text' , 3) - self.tree.append_column(col) - self.set_images() - self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - self.tree.get_selection().connect('changed', self.selection_changed) - self.tooltip = FileTransfersTooltip() - self.xml.signal_autoconnect(self) - popup_xml = gtk.glade.XML(GTKGUI_GLADE, 'file_transfers_menu', - APP) - self.file_transfers_menu = popup_xml.get_widget('file_transfers_menu') - self.open_folder_menuitem = popup_xml.get_widget('open_folder_menuitem') - self.cancel_menuitem = popup_xml.get_widget('cancel_menuitem') - self.pause_menuitem = popup_xml.get_widget('pause_menuitem') - self.continue_menuitem = popup_xml.get_widget('continue_menuitem') - self.remove_menuitem = popup_xml.get_widget('remove_menuitem') - self.clean_up_menuitem = popup_xml.get_widget('clean_up_menuitem') - if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) - popup_xml.signal_autoconnect(self) - - def show_completed(self, jid, file_props): - self.window.present() - self.window.window.focus() - sectext = '\t' + _('Filename: %s') % \ - gtkgui_helpers.escape_for_pango_markup(file_props['name']) - sectext += '\n\t' + _('Size: %s') % \ - helpers.convert_bytes(file_props['size']) - sectext += '\n\t' +_('Sender: %s') % \ - gtkgui_helpers.escape_for_pango_markup(jid) - if file_props['type'] == 'r': - (path, file) = os.path.split(file_props['file-name']) - sectext += '\n\t' +_('Saved in: %s') % \ - gtkgui_helpers.escape_for_pango_markup(path) - dialog = HigDialog(None, _('File transfer completed'), sectext, - gtk.STOCK_DIALOG_INFO, [[_('_Open Containing Folder'), gtk.RESPONSE_ACCEPT], [ gtk.STOCK_OK, gtk.RESPONSE_OK ]]) - button = dialog.get_button(1) - if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): - button.set_image(gtk.image_new_from_stock( - gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_BUTTON)) - dialog.show_all() - if file_props['type'] == 's': - button.hide() - response = dialog.run() - dialog.destroy() - if response == gtk.RESPONSE_ACCEPT: - if not file_props.has_key('file-name'): - return - (path, file) = os.path.split(file_props['file-name']) - if os.path.exists(path) and os.path.isdir(path): - helpers.launch_file_manager(path) - self.tree.get_selection().unselect_all() - - def show_request_error(self, file_props): - self.window.present() - self.window.window.focus() - InformationDialog(_('File transfer canceled'), _('Connection with peer cannot be established.')) - self.tree.get_selection().unselect_all() - - def show_send_error(self, file_props): - self.window.present() - self.window.window.focus() - InformationDialog(_('File transfer canceled'), -_('Connection with peer cannot be established.')) - self.tree.get_selection().unselect_all() - - def show_stopped(self, jid, file_props): - self.window.present() - self.window.window.focus() - sectext = '\t' + _('Filename: %s') % \ - gtkgui_helpers.escape_for_pango_markup(file_props['name']) - sectext += '\n\t' + _('Sender: %s') % \ - gtkgui_helpers.escape_for_pango_markup(jid) - ErrorDialog(_('File transfer stopped by the contact of the other side'), \ - sectext).get_response() - self.tree.get_selection().unselect_all() - - def show_file_send_request(self, account, contact): - #FIXME: user better name for this function - #atm it's like it shows popup for incoming file transfer request - dialog = gtk.FileChooserDialog(title=_('Choose File to Send...'), - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) - butt = dialog.add_button(_('Send'), gtk.RESPONSE_OK) - butt.set_use_stock(True) - dialog.set_default_response(gtk.RESPONSE_OK) - if self.last_save_dir and os.path.exists(self.last_save_dir) \ - and os.path.isdir(self.last_save_dir): - dialog.set_current_folder(self.last_save_dir) - file_props = {} - response = dialog.run() - if response == gtk.RESPONSE_OK: - file_path = unicode(dialog.get_filename(), 'utf-8') - (file_dir, file_name) = os.path.split(file_path) - if file_dir: - self.last_save_dir = file_dir - dialog.destroy() - self.send_file(account, contact, file_path) - else: - dialog.destroy() - - def send_file(self, account, contact, file_path): - (file_dir, file_name) = os.path.split(file_path) - file_props = self.get_send_file_props(account, contact, - file_path, file_name) - self.add_transfer(account, contact, file_props) - gajim.connections[account].send_file_request(file_props) - - def show_file_request(self, account, contact, file_props): - if file_props is None or not file_props.has_key('name'): - return - sec_text = '\t' + _('File: %s') % \ - gtkgui_helpers.escape_for_pango_markup(file_props['name']) - if file_props.has_key('size'): - sec_text += '\n\t' + _('Size: %s') % \ - helpers.convert_bytes(file_props['size']) - if file_props.has_key('mime-type'): - sec_text += '\n\t' + _('Type: %s') % \ - gtkgui_helpers.escape_for_pango_markup(file_props['mime-type']) - if file_props.has_key('desc'): - sec_text += '\n\t' + _('Description: %s') % \ - gtkgui_helpers.escape_for_pango_markup(file_props['desc']) - prim_text = _('%s wants to send you a file:') % contact.jid - dialog = ConfirmationDialog(prim_text, sec_text) - if dialog.get_response() == gtk.RESPONSE_OK: - dialog = gtk.FileChooserDialog(title=_('Save File as...'), - action=gtk.FILE_CHOOSER_ACTION_SAVE, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK)) - dialog.set_current_name(file_props['name']) - dialog.set_default_response(gtk.RESPONSE_OK) - if self.last_save_dir and os.path.exists(self.last_save_dir) \ - and os.path.isdir(self.last_save_dir): - dialog.set_current_folder(self.last_save_dir) - while True: - response = dialog.run() - if response == gtk.RESPONSE_OK: - file_path = dialog.get_filename() - if os.path.exists(file_path): - primtext = _('This file already exists') - sectext = _('Would you like to overwrite it?') - dialog2 = ConfirmationDialog(primtext, sectext) - if dialog2.get_response() != gtk.RESPONSE_OK: - continue - (file_dir, file_name) = os.path.split(file_path) - if file_dir: - self.last_save_dir = file_dir - file_props['file-name'] = file_path.decode('utf-8') - self.add_transfer(account, contact, file_props) - gajim.connections[account].send_file_approval(file_props) - else: - gajim.connections[account].send_file_rejection(file_props) - dialog.destroy() - break - else: - gajim.connections[account].send_file_rejection(file_props) - - def set_images(self): - self.images = {} - self.images['upload'] = self.window.render_icon(gtk.STOCK_GO_UP, - gtk.ICON_SIZE_MENU) - self.images['download'] = self.window.render_icon(gtk.STOCK_GO_DOWN, - gtk.ICON_SIZE_MENU) - self.images['stop'] = self.window.render_icon(gtk.STOCK_STOP, - gtk.ICON_SIZE_MENU) - self.images['waiting'] = self.window.render_icon(gtk.STOCK_REFRESH, - gtk.ICON_SIZE_MENU) - self.images['pause'] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, - gtk.ICON_SIZE_MENU) - self.images['continue'] = self.window.render_icon(gtk.STOCK_MEDIA_PLAY, - gtk.ICON_SIZE_MENU) - self.images['ok'] = self.window.render_icon(gtk.STOCK_APPLY, - gtk.ICON_SIZE_MENU) - - def set_status(self, typ, sid, status): - iter = self.get_iter_by_sid(typ, sid) - if iter is None: - return - sid = self.model[iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if status == 'stop': - file_props['stopped'] = True - elif status == 'ok': - file_props['completed'] = True - self.model.set(iter, 0, self.images[status]) - - def set_progress(self, typ, sid, transfered_size, iter = None): - if not self.files_props[typ].has_key(sid): - return - file_props = self.files_props[typ][sid] - full_size = int(file_props['size']) - if full_size == 0: - percent = 0 - else: - percent = round(float(transfered_size) / full_size * 100) - if iter is None: - iter = self.get_iter_by_sid(typ, sid) - if iter is not None: - text = str(percent) + '%\n' - if transfered_size == 0: - text += '0' - else: - text += helpers.convert_bytes(transfered_size) - text += '/' + helpers.convert_bytes(full_size) - self.model.set(iter, 3, text) - if file_props['type'] == 'r': - status = 'download' - else: - status = 'upload' - if file_props.has_key('paused') and file_props['paused'] == True: - status = 'pause' - elif file_props.has_key('stalled') and file_props['stalled'] == True: - status = 'waiting' - if file_props.has_key('connected') and file_props['connected'] == False: - status = 'stop' - self.model.set(iter, 0, self.images[status]) - if percent == 100: - self.set_status(typ, sid, 'ok') - - def get_iter_by_sid(self, typ, sid): - '''returns iter to the row, which holds file transfer, identified by the - session id''' - iter = self.model.get_iter_root() - while iter: - if typ + sid == self.model[iter][4]: - return iter - iter = self.model.iter_next(iter) - - def get_sid(self): - rng = range(65, 90) - rng.extend(range(48, 57)) - char_sequence = map(lambda e:chr(e), rng) - from random import sample - return reduce(lambda e1, e2: e1 + e2, - sample(char_sequence, 16)) - - def get_send_file_props(self, account, contact, file_path, file_name): - file_props = {'file-name' : file_path, 'name' : file_name, - 'type' : 's'} - if os.path.exists(file_path) and os.path.isfile(file_path): - stat = os.stat(file_path) - os.stat(file_path) - file_props['size'] = str(stat[6]) - file_props['sid'] = self.get_sid() - file_props['completed'] = False - file_props['started'] = False - file_props['sender'] = account - file_props['receiver'] = contact - file_props['tt_account'] = account - return file_props - - def add_transfer(self, account, contact, file_props): - self.on_transfers_list_leave_notify_event(None) - if file_props is None: - return - self.files_props[file_props['type']][file_props['sid']] = file_props - iter = self.model.append() - text_labels = '<b>' + _('Name: ') + '</b>\n' - if file_props['type'] == 'r': - text_labels += '<b>' + _('Sender: ') + '</b>' - else: - text_labels += '<b>' + _('Recipient: ') + '</b>' - text_props = gtkgui_helpers.escape_for_pango_markup(file_props['name']) + '\n' - text_props += gtkgui_helpers.escape_for_pango_markup(contact.name) - self.model.set(iter, 1, text_labels, 2, text_props, 4, \ - file_props['type'] + file_props['sid']) - self.set_progress(file_props['type'], file_props['sid'], 0, iter) - if file_props.has_key('started') and file_props['started'] is False: - status = 'waiting' - elif file_props['type'] == 'r': - status = 'download' - else: - status = 'upload' - file_props['tt_account'] = account - self.set_status(file_props['type'], file_props['sid'], status) - self.window.show_all() - - def on_transfers_list_motion_notify_event(self, widget, event): - pointer = self.tree.get_pointer() - orig = widget.window.get_origin() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - self.height_diff = pointer[1] - int(event.y) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - [row, col, x, y] = props - iter = None - try: - iter = self.model.get_iter(row) - except: - self.tooltip.hide_tooltip() - return - sid = self.model[iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if file_props is not None: - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, widget) - - def on_transfers_list_leave_notify_event(self, widget = None, event = None): - if event is not None: - self.height_diff = int(event.y) - elif self.height_diff is 0: - return - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], - pointer[1] - self.height_diff) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def on_transfers_list_row_activated(self, widget, path, col): - # try to open the containing folder - self.on_open_folder_menuitem_activate(widget) - - def is_transfer_paused(self, file_props): - if file_props.has_key('stopped') and file_props['stopped']: - return False - if file_props.has_key('completed') and file_props['completed']: - return False - if not file_props.has_key('disconnect_cb'): - return False - return file_props['paused'] - - def is_transfer_active(self, file_props): - if file_props.has_key('stopped') and file_props['stopped']: - return False - if file_props.has_key('completed') and file_props['completed']: - return False - if not file_props.has_key('started') or not file_props['started']: - return False - if not file_props.has_key('paused'): - return True - return not file_props['paused'] - - def is_transfer_stoped(self, file_props): - if file_props.has_key('error') and file_props['error'] != 0: - return True - if file_props.has_key('completed') and file_props['completed']: - return True - if not file_props.has_key('stopped') or not \ - file_props['stopped']: - return False - return True - - def set_all_insensitive(self): - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - self.remove_button.set_sensitive(False) - self.remove_menuitem.set_sensitive(False) - self.cancel_button.set_sensitive(False) - self.cancel_menuitem.set_sensitive(False) - self.open_folder_menuitem.set_sensitive(False) - - def set_buttons_sensitive(self, path, is_row_selected): - if path is None: - self.set_all_insensitive() - return - current_iter = self.model.get_iter(path) - sid = self.model[current_iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - self.remove_button.set_sensitive(is_row_selected) - self.remove_menuitem.set_sensitive(is_row_selected) - self.open_folder_menuitem.set_sensitive(is_row_selected) - is_stopped = False - if self.is_transfer_stoped(file_props): - is_stopped = True - self.cancel_button.set_sensitive(not is_stopped) - self.cancel_menuitem.set_sensitive(not is_stopped) - if not is_row_selected: - # no selection, disable the buttons - self.set_all_insensitive() - elif not is_stopped: - if self.is_transfer_active(file_props): - # file transfer is active - self.toggle_pause_continue(True) - self.pause_button.set_sensitive(True) - elif self.is_transfer_paused(file_props): - # file transfer is paused - self.toggle_pause_continue(False) - self.pause_button.set_sensitive(True) - else: - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - else: - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - return True - - def selection_changed(self, args): - selection = args - selected = selection.get_selected_rows() - if selected[1] != []: - selected_path = selected[1][0] - self.select_func(selected_path) - else: - self.set_all_insensitive() - - def select_func(self, path): - is_selected = False - selected = self.tree.get_selection().get_selected_rows() - if selected[1] != []: - selected_path = selected[1][0] - if selected_path == path: - is_selected = True - self.set_buttons_sensitive(path, is_selected) - return True - - def on_remove_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if not file_props.has_key('tt_account'): - # file transfer is not set yet - return - account = file_props['tt_account'] - if not gajim.connections.has_key(account): - # no connection to the account - return - gajim.connections[account].remove_transfer(file_props) - self.model.remove(s_iter) - self.set_all_insensitive() - - def toggle_pause_continue(self, status): - if status: - label = _('Pause') - self.pause_button.set_label(label) - if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) - - self.pause_menuitem.set_sensitive(True) - self.pause_menuitem.set_no_show_all(False) - self.continue_menuitem.hide() - self.continue_menuitem.set_no_show_all(True) - - else: - label = _('_Continue') - self.pause_button.set_label(label) - if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU)) - self.pause_menuitem.hide() - self.pause_menuitem.set_no_show_all(True) - self.continue_menuitem.set_sensitive(True) - self.continue_menuitem.set_no_show_all(False) - - def on_pause_restore_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if self.is_transfer_paused(file_props): - file_props['paused'] = False - types = {'r' : 'download', 's' : 'upload'} - self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) - self.toggle_pause_continue(True) - elif self.is_transfer_active(file_props): - file_props['paused'] = True - self.set_status(file_props['type'], file_props['sid'], 'pause') - self.toggle_pause_continue(False) - - def on_cancel_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if not file_props.has_key('tt_account'): - return - account = file_props['tt_account'] - if not gajim.connections.has_key(account): - return - gajim.connections[account].disconnect_transfer(file_props) - self.set_status(file_props['type'], file_props['sid'], 'stop') - - def show_tooltip(self, widget): - if self.height_diff == 0: - self.tooltip.hide_tooltip() - return - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], - pointer[1] - self.height_diff) - if props and self.tooltip.id == props[0]: - # check if the current pointer is at the same path - # as it was before setting the timeout - iter = self.model.get_iter(props[0]) - sid = self.model[iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - rect = self.tree.get_cell_area(props[0],props[1]) - position = widget.window.get_origin() - self.tooltip.show_tooltip(file_props , (pointer[0], rect.height ), - (position[0], position[1] + rect.y + self.height_diff)) - else: - self.tooltip.hide_tooltip() - - def on_notify_ft_complete_checkbox_toggled(self, widget): - gajim.config.set('notify_on_file_complete', - widget.get_active()) - - def on_file_transfers_dialog_delete_event(self, widget, event): - self.on_transfers_list_leave_notify_event(widget, None) - self.window.hide() - return True # do NOT destory window - - def on_close_button_clicked(self, widget): - self.window.hide() - - def show_context_menu(self, event, iter): - # change the sensitive propery of the buttons and menuitems - if len(self.model) == 0: - self.clean_up_menuitem.set_sensitive(False) - else: - self.clean_up_menuitem.set_sensitive(True) - path = None - if iter is not None: - path = self.model.get_path(iter) - self.set_buttons_sensitive(path, True) - - event_button = self.get_possible_button_event(event) - self.file_transfers_menu.popup(None, self.tree, None, - event_button, event.time) - self.file_transfers_menu.show_all() - - def get_possible_button_event(self, event): - '''mouse or keyboard caused the event?''' - if event.type == gtk.gdk.KEY_PRESS: - event_button = 0 # no event.button so pass 0 - else: # BUTTON_PRESS event, so pass event.button - event_button = event.button - - return event_button - - def on_transfers_list_key_press_event(self, widget, event): - '''when a key is pressed in the treeviews''' - self.tooltip.hide_tooltip() - iter = None - try: - store, iter = self.tree.get_selection().get_selected() - except TypeError: - self.tree.get_selection().unselect_all() - - if iter is not None: - path = self.model.get_path(iter) - self.tree.get_selection().select_path(path) - - if event.keyval == gtk.keysyms.Menu: - self.show_context_menu(event, iter) - return True - - - def on_transfers_list_button_release_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - path = None - try: - path, column, x, y = self.tree.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - self.tree.get_selection().unselect_all() - if path is None: - self.set_all_insensitive() - else: - self.select_func(path) - - def on_transfers_list_button_press_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - path, iter = None, None - try: - path, column, x, y = self.tree.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - self.tree.get_selection().unselect_all() - if event.button == 3: # Right click - if path is not None: - self.tree.get_selection().select_path(path) - iter = self.model.get_iter(path) - self.show_context_menu(event, iter) - if path is not None: - return True - - - def on_clean_up_menuitem_activate(self, widget): - i = len(self.model) - 1 - while i >= 0: - iter = self.model.get_iter((i)) - sid = self.model[iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if file_props.has_key('completed') and file_props['completed']: - self.model.remove(iter) - elif file_props.has_key('stopped') and file_props['stopped']: - self.model.remove(iter) - i -= 1 - self.tree.get_selection().unselect_all() - self.set_all_insensitive() - - def on_open_folder_menuitem_activate(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][4] - file_props = self.files_props[sid[0]][sid[1:]] - if not file_props.has_key('file-name'): - return - (path, file) = os.path.split(file_props['file-name']) - if os.path.exists(path) and os.path.isdir(path): - helpers.launch_file_manager(path) - - def on_cancel_menuitem_activate(self, widget): - self.on_cancel_button_clicked(widget) - - def on_continue_menuitem_activate(self, widget): - self.on_pause_restore_button_clicked(widget) - - def on_pause_menuitem_activate(self, widget): - self.on_pause_restore_button_clicked(widget) - # TODO change the stock - - def on_remove_menuitem_activate(self, widget): - self.on_remove_button_clicked(widget) - class InvitationDialog: def __init__(self, plugin, account, room_jid, contact_jid, password = None, comment = None): diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py new file mode 100644 index 0000000000000000000000000000000000000000..2c59ba532b781d0213cb77a5178d041aa3dacfe9 --- /dev/null +++ b/src/filetransfers_window.py @@ -0,0 +1,725 @@ +## filetransfers_window.py +## +## Gajim Team: +## - Yann Le Boulanger <asterix@lagaule.org> +## - Vincent Hanquez <tab@snarc.org> +## - Nikos Kouremenos <kourem@gmail.com> +## - Dimitur Kirov <dkirov@gmail.com> +## +## Copyright (C) 2003-2005 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import gtk +import gtk.glade +import gobject +import os + +import gtkgui_helpers +import tooltips +import dialogs + +from common import gajim +from common import helpers +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain (APP, i18n.DIR) +gtk.glade.textdomain (APP) + +GTKGUI_GLADE = 'gtkgui.glade' + +class FileTransfersWindow: + def __init__(self, plugin): + self.files_props = {'r' : {}, 's': {}} + self.plugin = plugin + self.height_diff = 0 + self.last_save_dir = None + self.xml = gtk.glade.XML(GTKGUI_GLADE, 'file_transfers_window', APP) + self.window = self.xml.get_widget('file_transfers_window') + self.tree = self.xml.get_widget('transfers_list') + self.cancel_button = self.xml.get_widget('cancel_button') + self.pause_button = self.xml.get_widget('pause_restore_button') + self.remove_button = self.xml.get_widget('remove_button') + self.notify_ft_checkbox = self.xml.get_widget( + 'notify_ft_complete_checkbox') + notify = gajim.config.get('notify_on_file_complete') + if notify: + self.notify_ft_checkbox.set_active(True) + else: + self.notify_ft_checkbox.set_active(False) + self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, str) + self.tree.set_model(self.model) + col = gtk.TreeViewColumn() + + render_pixbuf = gtk.CellRendererPixbuf() + + col.pack_start(render_pixbuf, expand = True) + render_pixbuf.set_property('xpad', 3) + render_pixbuf.set_property('ypad', 3) + render_pixbuf.set_property('yalign', .0) + col.add_attribute(render_pixbuf, 'pixbuf', 0) + self.tree.append_column(col) + + col = gtk.TreeViewColumn(_('File')) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'markup' , 1) + renderer.set_property('yalign', 0.) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.add_attribute(renderer, 'markup' , 2) + renderer.set_property('xalign', 0.) + renderer.set_property('yalign', 0.) + col.set_resizable(True) + col.set_expand(True) + self.tree.append_column(col) + + col = gtk.TreeViewColumn(_('Progress')) + renderer = gtk.CellRendererText() + renderer.set_property('yalign', 0.) + renderer.set_property('xalign', 0.) + col.pack_start(renderer, expand = True) + col.set_expand(False) + col.add_attribute(renderer, 'text' , 3) + self.tree.append_column(col) + self.set_images() + self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) + self.tree.get_selection().connect('changed', self.selection_changed) + self.tooltip = tooltips.FileTransfersTooltip() + self.xml.signal_autoconnect(self) + popup_xml = gtk.glade.XML(GTKGUI_GLADE, 'file_transfers_menu', + APP) + self.file_transfers_menu = popup_xml.get_widget('file_transfers_menu') + self.open_folder_menuitem = popup_xml.get_widget('open_folder_menuitem') + self.cancel_menuitem = popup_xml.get_widget('cancel_menuitem') + self.pause_menuitem = popup_xml.get_widget('pause_menuitem') + self.continue_menuitem = popup_xml.get_widget('continue_menuitem') + self.remove_menuitem = popup_xml.get_widget('remove_menuitem') + self.clean_up_menuitem = popup_xml.get_widget('clean_up_menuitem') + if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) + popup_xml.signal_autoconnect(self) + + def show_completed(self, jid, file_props): + self.window.present() + self.window.window.focus() + sectext = '\t' + _('Filename: %s') % \ + gtkgui_helpers.escape_for_pango_markup(file_props['name']) + sectext += '\n\t' + _('Size: %s') % \ + helpers.convert_bytes(file_props['size']) + sectext += '\n\t' +_('Sender: %s') % \ + gtkgui_helpers.escape_for_pango_markup(jid) + if file_props['type'] == 'r': + (path, file) = os.path.split(file_props['file-name']) + sectext += '\n\t' +_('Saved in: %s') % \ + gtkgui_helpers.escape_for_pango_markup(path) + dialog = dialogs.HigDialog(None, _('File transfer completed'), sectext, + gtk.STOCK_DIALOG_INFO, [[_('_Open Containing Folder'), gtk.RESPONSE_ACCEPT], [ gtk.STOCK_OK, gtk.RESPONSE_OK ]]) + button = dialog.get_button(1) + if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): + button.set_image(gtk.image_new_from_stock( + gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_BUTTON)) + dialog.show_all() + if file_props['type'] == 's': + button.hide() + response = dialog.run() + dialog.destroy() + if response == gtk.RESPONSE_ACCEPT: + if not file_props.has_key('file-name'): + return + (path, file) = os.path.split(file_props['file-name']) + if os.path.exists(path) and os.path.isdir(path): + helpers.launch_file_manager(path) + self.tree.get_selection().unselect_all() + + def show_request_error(self, file_props): + self.window.present() + self.window.window.focus() + dialogs.InformationDialog(_('File transfer canceled'), _('Connection with peer cannot be established.')) + self.tree.get_selection().unselect_all() + + def show_send_error(self, file_props): + self.window.present() + self.window.window.focus() + dialogs.InformationDialog(_('File transfer canceled'), +_('Connection with peer cannot be established.')) + self.tree.get_selection().unselect_all() + + def show_stopped(self, jid, file_props): + self.window.present() + self.window.window.focus() + sectext = '\t' + _('Filename: %s') % \ + gtkgui_helpers.escape_for_pango_markup(file_props['name']) + sectext += '\n\t' + _('Sender: %s') % \ + gtkgui_helpers.escape_for_pango_markup(jid) + dialogs.ErrorDialog(_('File transfer stopped by the contact of the other side'), \ + sectext).get_response() + self.tree.get_selection().unselect_all() + + def show_file_send_request(self, account, contact): + #FIXME: user better name for this function + #atm it's like it shows popup for incoming file transfer request + dialog = gtk.FileChooserDialog(title=_('Choose File to Send...'), + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) + butt = dialog.add_button(_('Send'), gtk.RESPONSE_OK) + butt.set_use_stock(True) + dialog.set_default_response(gtk.RESPONSE_OK) + if self.last_save_dir and os.path.exists(self.last_save_dir) \ + and os.path.isdir(self.last_save_dir): + dialog.set_current_folder(self.last_save_dir) + file_props = {} + response = dialog.run() + if response == gtk.RESPONSE_OK: + file_path = unicode(dialog.get_filename(), 'utf-8') + (file_dir, file_name) = os.path.split(file_path) + if file_dir: + self.last_save_dir = file_dir + dialog.destroy() + self.send_file(account, contact, file_path) + else: + dialog.destroy() + + def send_file(self, account, contact, file_path): + (file_dir, file_name) = os.path.split(file_path) + file_props = self.get_send_file_props(account, contact, + file_path, file_name) + self.add_transfer(account, contact, file_props) + gajim.connections[account].send_file_request(file_props) + + def show_file_request(self, account, contact, file_props): + if file_props is None or not file_props.has_key('name'): + return + sec_text = '\t' + _('File: %s') % \ + gtkgui_helpers.escape_for_pango_markup(file_props['name']) + if file_props.has_key('size'): + sec_text += '\n\t' + _('Size: %s') % \ + helpers.convert_bytes(file_props['size']) + if file_props.has_key('mime-type'): + sec_text += '\n\t' + _('Type: %s') % \ + gtkgui_helpers.escape_for_pango_markup(file_props['mime-type']) + if file_props.has_key('desc'): + sec_text += '\n\t' + _('Description: %s') % \ + gtkgui_helpers.escape_for_pango_markup(file_props['desc']) + prim_text = _('%s wants to send you a file:') % contact.jid + dialog = dialogs.ConfirmationDialog(prim_text, sec_text) + if dialog.get_response() == gtk.RESPONSE_OK: + dialog = gtk.FileChooserDialog(title=_('Save File as...'), + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + dialog.set_current_name(file_props['name']) + dialog.set_default_response(gtk.RESPONSE_OK) + if self.last_save_dir and os.path.exists(self.last_save_dir) \ + and os.path.isdir(self.last_save_dir): + dialog.set_current_folder(self.last_save_dir) + while True: + response = dialog.run() + if response == gtk.RESPONSE_OK: + file_path = dialog.get_filename() + if os.path.exists(file_path): + primtext = _('This file already exists') + sectext = _('Would you like to overwrite it?') + dialog2 = dialogs.ConfirmationDialog(primtext, sectext) + if dialog2.get_response() != gtk.RESPONSE_OK: + continue + (file_dir, file_name) = os.path.split(file_path) + if file_dir: + self.last_save_dir = file_dir + file_props['file-name'] = file_path.decode('utf-8') + self.add_transfer(account, contact, file_props) + gajim.connections[account].send_file_approval(file_props) + else: + gajim.connections[account].send_file_rejection(file_props) + dialog.destroy() + break + else: + gajim.connections[account].send_file_rejection(file_props) + + def set_images(self): + self.images = {} + self.images['upload'] = self.window.render_icon(gtk.STOCK_GO_UP, + gtk.ICON_SIZE_MENU) + self.images['download'] = self.window.render_icon(gtk.STOCK_GO_DOWN, + gtk.ICON_SIZE_MENU) + self.images['stop'] = self.window.render_icon(gtk.STOCK_STOP, + gtk.ICON_SIZE_MENU) + self.images['waiting'] = self.window.render_icon(gtk.STOCK_REFRESH, + gtk.ICON_SIZE_MENU) + self.images['pause'] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, + gtk.ICON_SIZE_MENU) + self.images['continue'] = self.window.render_icon(gtk.STOCK_MEDIA_PLAY, + gtk.ICON_SIZE_MENU) + self.images['ok'] = self.window.render_icon(gtk.STOCK_APPLY, + gtk.ICON_SIZE_MENU) + + def set_status(self, typ, sid, status): + iter = self.get_iter_by_sid(typ, sid) + if iter is None: + return + sid = self.model[iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if status == 'stop': + file_props['stopped'] = True + elif status == 'ok': + file_props['completed'] = True + self.model.set(iter, 0, self.images[status]) + + def set_progress(self, typ, sid, transfered_size, iter = None): + if not self.files_props[typ].has_key(sid): + return + file_props = self.files_props[typ][sid] + full_size = int(file_props['size']) + if full_size == 0: + percent = 0 + else: + percent = round(float(transfered_size) / full_size * 100) + if iter is None: + iter = self.get_iter_by_sid(typ, sid) + if iter is not None: + text = str(percent) + '%\n' + if transfered_size == 0: + text += '0' + else: + text += helpers.convert_bytes(transfered_size) + text += '/' + helpers.convert_bytes(full_size) + self.model.set(iter, 3, text) + if file_props['type'] == 'r': + status = 'download' + else: + status = 'upload' + if file_props.has_key('paused') and file_props['paused'] == True: + status = 'pause' + elif file_props.has_key('stalled') and file_props['stalled'] == True: + status = 'waiting' + if file_props.has_key('connected') and file_props['connected'] == False: + status = 'stop' + self.model.set(iter, 0, self.images[status]) + if percent == 100: + self.set_status(typ, sid, 'ok') + + def get_iter_by_sid(self, typ, sid): + '''returns iter to the row, which holds file transfer, identified by the + session id''' + iter = self.model.get_iter_root() + while iter: + if typ + sid == self.model[iter][4]: + return iter + iter = self.model.iter_next(iter) + + def get_sid(self): + rng = range(65, 90) + rng.extend(range(48, 57)) + char_sequence = map(lambda e:chr(e), rng) + from random import sample + return reduce(lambda e1, e2: e1 + e2, + sample(char_sequence, 16)) + + def get_send_file_props(self, account, contact, file_path, file_name): + file_props = {'file-name' : file_path, 'name' : file_name, + 'type' : 's'} + if os.path.exists(file_path) and os.path.isfile(file_path): + stat = os.stat(file_path) + os.stat(file_path) + file_props['size'] = str(stat[6]) + file_props['sid'] = self.get_sid() + file_props['completed'] = False + file_props['started'] = False + file_props['sender'] = account + file_props['receiver'] = contact + file_props['tt_account'] = account + return file_props + + def add_transfer(self, account, contact, file_props): + self.on_transfers_list_leave_notify_event(None) + if file_props is None: + return + self.files_props[file_props['type']][file_props['sid']] = file_props + iter = self.model.append() + text_labels = '<b>' + _('Name: ') + '</b>\n' + if file_props['type'] == 'r': + text_labels += '<b>' + _('Sender: ') + '</b>' + else: + text_labels += '<b>' + _('Recipient: ') + '</b>' + text_props = gtkgui_helpers.escape_for_pango_markup(file_props['name']) + '\n' + text_props += gtkgui_helpers.escape_for_pango_markup(contact.name) + self.model.set(iter, 1, text_labels, 2, text_props, 4, \ + file_props['type'] + file_props['sid']) + self.set_progress(file_props['type'], file_props['sid'], 0, iter) + if file_props.has_key('started') and file_props['started'] is False: + status = 'waiting' + elif file_props['type'] == 'r': + status = 'download' + else: + status = 'upload' + file_props['tt_account'] = account + self.set_status(file_props['type'], file_props['sid'], status) + self.window.show_all() + + def on_transfers_list_motion_notify_event(self, widget, event): + pointer = self.tree.get_pointer() + orig = widget.window.get_origin() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + self.height_diff = pointer[1] - int(event.y) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + [row, col, x, y] = props + iter = None + try: + iter = self.model.get_iter(row) + except: + self.tooltip.hide_tooltip() + return + sid = self.model[iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if file_props is not None: + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, widget) + + def on_transfers_list_leave_notify_event(self, widget = None, event = None): + if event is not None: + self.height_diff = int(event.y) + elif self.height_diff is 0: + return + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], + pointer[1] - self.height_diff) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def on_transfers_list_row_activated(self, widget, path, col): + # try to open the containing folder + self.on_open_folder_menuitem_activate(widget) + + def is_transfer_paused(self, file_props): + if file_props.has_key('stopped') and file_props['stopped']: + return False + if file_props.has_key('completed') and file_props['completed']: + return False + if not file_props.has_key('disconnect_cb'): + return False + return file_props['paused'] + + def is_transfer_active(self, file_props): + if file_props.has_key('stopped') and file_props['stopped']: + return False + if file_props.has_key('completed') and file_props['completed']: + return False + if not file_props.has_key('started') or not file_props['started']: + return False + if not file_props.has_key('paused'): + return True + return not file_props['paused'] + + def is_transfer_stoped(self, file_props): + if file_props.has_key('error') and file_props['error'] != 0: + return True + if file_props.has_key('completed') and file_props['completed']: + return True + if not file_props.has_key('stopped') or not \ + file_props['stopped']: + return False + return True + + def set_all_insensitive(self): + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + self.remove_button.set_sensitive(False) + self.remove_menuitem.set_sensitive(False) + self.cancel_button.set_sensitive(False) + self.cancel_menuitem.set_sensitive(False) + self.open_folder_menuitem.set_sensitive(False) + + def set_buttons_sensitive(self, path, is_row_selected): + if path is None: + self.set_all_insensitive() + return + current_iter = self.model.get_iter(path) + sid = self.model[current_iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + self.remove_button.set_sensitive(is_row_selected) + self.remove_menuitem.set_sensitive(is_row_selected) + self.open_folder_menuitem.set_sensitive(is_row_selected) + is_stopped = False + if self.is_transfer_stoped(file_props): + is_stopped = True + self.cancel_button.set_sensitive(not is_stopped) + self.cancel_menuitem.set_sensitive(not is_stopped) + if not is_row_selected: + # no selection, disable the buttons + self.set_all_insensitive() + elif not is_stopped: + if self.is_transfer_active(file_props): + # file transfer is active + self.toggle_pause_continue(True) + self.pause_button.set_sensitive(True) + elif self.is_transfer_paused(file_props): + # file transfer is paused + self.toggle_pause_continue(False) + self.pause_button.set_sensitive(True) + else: + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + else: + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + return True + + def selection_changed(self, args): + selection = args + selected = selection.get_selected_rows() + if selected[1] != []: + selected_path = selected[1][0] + self.select_func(selected_path) + else: + self.set_all_insensitive() + + def select_func(self, path): + is_selected = False + selected = self.tree.get_selection().get_selected_rows() + if selected[1] != []: + selected_path = selected[1][0] + if selected_path == path: + is_selected = True + self.set_buttons_sensitive(path, is_selected) + return True + + def on_remove_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if not file_props.has_key('tt_account'): + # file transfer is not set yet + return + account = file_props['tt_account'] + if not gajim.connections.has_key(account): + # no connection to the account + return + gajim.connections[account].remove_transfer(file_props) + self.model.remove(s_iter) + self.set_all_insensitive() + + def toggle_pause_continue(self, status): + if status: + label = _('Pause') + self.pause_button.set_label(label) + if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) + + self.pause_menuitem.set_sensitive(True) + self.pause_menuitem.set_no_show_all(False) + self.continue_menuitem.hide() + self.continue_menuitem.set_no_show_all(True) + + else: + label = _('_Continue') + self.pause_button.set_label(label) + if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0): + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU)) + self.pause_menuitem.hide() + self.pause_menuitem.set_no_show_all(True) + self.continue_menuitem.set_sensitive(True) + self.continue_menuitem.set_no_show_all(False) + + def on_pause_restore_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if self.is_transfer_paused(file_props): + file_props['paused'] = False + types = {'r' : 'download', 's' : 'upload'} + self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) + self.toggle_pause_continue(True) + elif self.is_transfer_active(file_props): + file_props['paused'] = True + self.set_status(file_props['type'], file_props['sid'], 'pause') + self.toggle_pause_continue(False) + + def on_cancel_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if not file_props.has_key('tt_account'): + return + account = file_props['tt_account'] + if not gajim.connections.has_key(account): + return + gajim.connections[account].disconnect_transfer(file_props) + self.set_status(file_props['type'], file_props['sid'], 'stop') + + def show_tooltip(self, widget): + if self.height_diff == 0: + self.tooltip.hide_tooltip() + return + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], + pointer[1] - self.height_diff) + if props and self.tooltip.id == props[0]: + # check if the current pointer is at the same path + # as it was before setting the timeout + iter = self.model.get_iter(props[0]) + sid = self.model[iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + rect = self.tree.get_cell_area(props[0],props[1]) + position = widget.window.get_origin() + self.tooltip.show_tooltip(file_props , (pointer[0], rect.height ), + (position[0], position[1] + rect.y + self.height_diff)) + else: + self.tooltip.hide_tooltip() + + def on_notify_ft_complete_checkbox_toggled(self, widget): + gajim.config.set('notify_on_file_complete', + widget.get_active()) + + def on_file_transfers_dialog_delete_event(self, widget, event): + self.on_transfers_list_leave_notify_event(widget, None) + self.window.hide() + return True # do NOT destory window + + def on_close_button_clicked(self, widget): + self.window.hide() + + def show_context_menu(self, event, iter): + # change the sensitive propery of the buttons and menuitems + if len(self.model) == 0: + self.clean_up_menuitem.set_sensitive(False) + else: + self.clean_up_menuitem.set_sensitive(True) + path = None + if iter is not None: + path = self.model.get_path(iter) + self.set_buttons_sensitive(path, True) + + event_button = self.get_possible_button_event(event) + self.file_transfers_menu.popup(None, self.tree, None, + event_button, event.time) + self.file_transfers_menu.show_all() + + def get_possible_button_event(self, event): + '''mouse or keyboard caused the event?''' + if event.type == gtk.gdk.KEY_PRESS: + event_button = 0 # no event.button so pass 0 + else: # BUTTON_PRESS event, so pass event.button + event_button = event.button + + return event_button + + def on_transfers_list_key_press_event(self, widget, event): + '''when a key is pressed in the treeviews''' + self.tooltip.hide_tooltip() + iter = None + try: + store, iter = self.tree.get_selection().get_selected() + except TypeError: + self.tree.get_selection().unselect_all() + + if iter is not None: + path = self.model.get_path(iter) + self.tree.get_selection().select_path(path) + + if event.keyval == gtk.keysyms.Menu: + self.show_context_menu(event, iter) + return True + + + def on_transfers_list_button_release_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + path = None + try: + path, column, x, y = self.tree.get_path_at_pos(int(event.x), + int(event.y)) + except TypeError: + self.tree.get_selection().unselect_all() + if path is None: + self.set_all_insensitive() + else: + self.select_func(path) + + def on_transfers_list_button_press_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + path, iter = None, None + try: + path, column, x, y = self.tree.get_path_at_pos(int(event.x), + int(event.y)) + except TypeError: + self.tree.get_selection().unselect_all() + if event.button == 3: # Right click + if path is not None: + self.tree.get_selection().select_path(path) + iter = self.model.get_iter(path) + self.show_context_menu(event, iter) + if path is not None: + return True + + + def on_clean_up_menuitem_activate(self, widget): + i = len(self.model) - 1 + while i >= 0: + iter = self.model.get_iter((i)) + sid = self.model[iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if file_props.has_key('completed') and file_props['completed']: + self.model.remove(iter) + elif file_props.has_key('stopped') and file_props['stopped']: + self.model.remove(iter) + i -= 1 + self.tree.get_selection().unselect_all() + self.set_all_insensitive() + + def on_open_folder_menuitem_activate(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][4] + file_props = self.files_props[sid[0]][sid[1:]] + if not file_props.has_key('file-name'): + return + (path, file) = os.path.split(file_props['file-name']) + if os.path.exists(path) and os.path.isdir(path): + helpers.launch_file_manager(path) + + def on_cancel_menuitem_activate(self, widget): + self.on_cancel_button_clicked(widget) + + def on_continue_menuitem_activate(self, widget): + self.on_pause_restore_button_clicked(widget) + + def on_pause_menuitem_activate(self, widget): + self.on_pause_restore_button_clicked(widget) + # TODO change the stock + + def on_remove_menuitem_activate(self, widget): + self.on_remove_button_clicked(widget) diff --git a/src/roster_window.py b/src/roster_window.py index db754393735c76b8df6d6b0c534c6eab3418180b..bb742fe4374cd56337259bbf688ae1bb5d50ed1f 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -32,6 +32,7 @@ import dialogs import config import gtkgui_helpers import cell_renderer_image +import tooltips from gajim import Contact from common import gajim @@ -2156,7 +2157,7 @@ _('If "%s" accepts this request you will know his status.') %jid) self.on_status_combobox_changed) self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t') - self.tooltip = dialogs.RosterTooltip(self.plugin) + self.tooltip = tooltips.RosterTooltip(self.plugin) self.make_menu() self.draw_roster() diff --git a/src/systray.py b/src/systray.py index 813f3cb724f1af2232e116d9f14b5294e4c417c7..7ed7d68d9dbf1ead9281ec58b30ea49e11685d2c 100644 --- a/src/systray.py +++ b/src/systray.py @@ -24,6 +24,8 @@ import gobject import dialogs import os +import tooltips + from common import gajim from common.connection import STATUS_LIST from common import helpers @@ -321,7 +323,7 @@ class Systray: eb.connect('button-press-event', self.on_clicked) eb.connect('motion-notify-event', self.on_tray_motion_notify_event) eb.connect('leave-notify-event', self.on_tray_leave_notify_event) - self.tooltip = dialogs.NotificationAreaTooltip(self.plugin) + self.tooltip = tooltips.NotificationAreaTooltip(self.plugin) self.img_tray = gtk.Image() eb.add(self.img_tray) diff --git a/src/tooltips.py b/src/tooltips.py new file mode 100644 index 0000000000000000000000000000000000000000..c54369bf2e40c0a8dbd2907f7a847bd0a975305c --- /dev/null +++ b/src/tooltips.py @@ -0,0 +1,407 @@ +## tooltips.py +## +## Gajim Team: +## - Yann Le Boulanger <asterix@lagaule.org> +## - Vincent Hanquez <tab@snarc.org> +## - Nikos Kouremenos <kourem@gmail.com> +## - Dimitur Kirov <dkirov@gmail.com> +## +## Copyright (C) 2003-2005 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import gtk +import gobject +import os + +import gtkgui_helpers + +from common import gajim +from common import helpers +from common import i18n + +_ = i18n._ +APP = i18n.APP + +class BaseTooltip: + ''' Base Tooltip . Usage: + tooltip = BaseTooltip() + .... + tooltip.show_tooltip('', window_postions, widget_postions) + .... + if tooltip.timeout != 0: + tooltip.hide_tooltip() + ''' + def __init__(self): + self.timeout = 0 + self.prefered_position = [0, 0] + self.win = None + self.id = None + + def populate(self, data): + ''' this method must be overriden by all extenders ''' + self.create_window() + self.win.add(gtk.Label(data)) + + def create_window(self): + ''' create a popup window each time tooltip is requested ''' + self.win = gtk.Window(gtk.WINDOW_POPUP) + self.win.set_border_width(3) + self.win.set_resizable(False) + self.win.set_name('gtk-tooltips') + + + self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) + self.win.connect_after('expose_event', self.expose) + self.win.connect('size-request', self.size_request) + self.win.connect('motion-notify-event', self.motion_notify_event) + + def motion_notify_event(self, widget, event): + self.hide_tooltip() + + def size_request(self, widget, requisition): + screen = self.win.get_screen() + half_width = requisition.width / 2 + 1 + if self.prefered_position[0] < half_width: + self.prefered_position[0] = 0 + elif self.prefered_position[0] + requisition.width > screen.get_width() \ + + half_width: + self.prefered_position[0] = screen.get_width() - requisition.width + else: + self.prefered_position[0] -= half_width + screen.get_height() + if self.prefered_position[1] + requisition.height > screen.get_height(): + # flip tooltip up + self.prefered_position[1] -= requisition.height + self.widget_height + 8 + if self.prefered_position[1] < 0: + self.prefered_position[1] = 0 + self.win.move(self.prefered_position[0], self.prefered_position[1]) + + def expose(self, widget, event): + style = self.win.get_style() + size = self.win.get_size() + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, + self.win, 'tooltip', 0, 0, -1, 1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, + self.win, 'tooltip', 0, size[1] - 1, -1, 1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, + self.win, 'tooltip', 0, 0, 1, -1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, + self.win, 'tooltip', size[0] - 1, 0, 1, -1) + return True + + def show_tooltip(self, data, widget_pos, win_size): + self.populate(data) + new_x = win_size[0] + widget_pos[0] + new_y = win_size[1] + widget_pos[1] + 4 + self.prefered_position = [new_x, new_y] + self.widget_height = widget_pos[1] + self.win.ensure_style() + self.win.show_all() + + def hide_tooltip(self): + if(self.timeout > 0): + gobject.source_remove(self.timeout) + self.timeout = 0 + if self.win: + self.win.destroy() + self.win = None + self.id = None + +class StatusTable: + ''' Contains methods for creating status table. This + is used in Roster and NotificationArea tooltips ''' + def __init__(self): + self.current_row = 1 + self.table = None + self.text_lable = None + + def create_table(self): + self.table = gtk.Table(3, 1) + self.table.set_property('column-spacing', 6) + self.text_lable = gtk.Label() + self.text_lable.set_line_wrap(True) + self.text_lable.set_alignment(0, 0) + self.text_lable.set_selectable(False) + self.table.attach(self.text_lable, 1, 4, 1, 2) + + def get_status_info(self, resource, priority, show, status): + str_status = resource + ' (' + str(priority) + ')' + if status: + status = status.strip() + if status != '': + if gtk.gtk_version < (2, 6, 0) or gtk.pygtk_version < (2, 6, 0): + # FIXME: check and do the same if we have more than one \n + status = gtkgui_helpers.reduce_chars_newlines(status, 50, 1) + else: + status = gtkgui_helpers.reduce_chars_newlines(status, 0, 1) + str_status += ' - ' + status + return gtkgui_helpers.escape_for_pango_markup(str_status) + + def add_status_row(self, file_path, show, str_status): + ''' appends a new row with status icon to the table ''' + self.current_row += 1 + state_file = show.replace(' ', '_') + files = [] + files.append(os.path.join(file_path, state_file + '.png')) + files.append(os.path.join(file_path, state_file + '.gif')) + image = gtk.Image() + image.set_from_pixbuf(None) + spacer = gtk.Label(' ') + for file in files: + if os.path.exists(file): + image.set_from_file(file) + break + image.set_alignment(0.01, 1) + self.table.attach(spacer, 1, 2, self.current_row, + self.current_row + 1, 0, 0, 0, 0) + self.table.attach(image,2,3,self.current_row, + self.current_row + 1, 0, 0, 3, 0) + image.set_alignment(0.01, 1) + status_label = gtk.Label() + status_label.set_markup(str_status) + status_label.set_alignment(00, 0) + self.table.attach(status_label, 3, 4, self.current_row, + self.current_row + 1, gtk.EXPAND | gtk.FILL, 0, 0, 0) + +class NotificationAreaTooltip(BaseTooltip, StatusTable): + ''' Tooltip that is shown in the notification area ''' + def __init__(self, plugin): + self.plugin = plugin + BaseTooltip.__init__(self) + StatusTable.__init__(self) + + def populate(self, data): + self.create_window() + self.create_table() + self.hbox = gtk.HBox() + self.table.set_property('column-spacing', 1) + text, single_line, accounts = '', '', [] + if gajim.contacts: + for account in gajim.contacts.keys(): + status_idx = gajim.connections[account].connected + # uncomment the following to hide offline accounts + # if status_idx == 0: continue + from common.connection import STATUS_LIST + status = STATUS_LIST[status_idx] + message = gajim.connections[account].status + single_line = helpers.get_uf_show(status) + if message is None: + message = '' + else: + message = message.strip() + if message != '': + single_line += ': ' + message + # the other solution is to hide offline accounts + elif status == 'offline': + message = helpers.get_uf_show(status) + accounts.append({'name': account, 'status_line': single_line, + 'show': status, 'message': message}) + unread_messages_no = self.plugin.roster.nb_unread + if unread_messages_no > 1: + text = _('Gajim - %s unread messages') % unread_messages_no + elif unread_messages_no == 1: + text = _('Gajim - 1 unread message') + elif len(accounts) > 1: + text = _('Gajim') + self.current_row = 1 + self.table.resize(2,1) + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'sun' + file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') + for acct in accounts: + message = gtkgui_helpers.reduce_chars_newlines(acct['message'], 50, 1) + message = gtkgui_helpers.escape_for_pango_markup(message) + self.add_status_row(file_path, acct['show'], '<span weight="bold">' + + gtkgui_helpers.escape_for_pango_markup(acct['name']) + '</span>' + + ' - ' + message) + + elif len(accounts) == 1: + message = gtkgui_helpers.reduce_chars_newlines(accounts[0]['status_line'], + 50, 1) + message = gtkgui_helpers.escape_for_pango_markup(message) + text = _('Gajim - %s') % message + else: + text = _('Gajim - %s') % helpers.get_uf_show('offline') + self.text_lable.set_markup(text) + self.hbox.add(self.table) + self.win.add(self.hbox) + +class RosterTooltip(BaseTooltip, StatusTable): + ''' Tooltip that is shown in the roster treeview ''' + def __init__(self, plugin): + self.account = None + self.plugin = plugin + + self.image = gtk.Image() + self.image.set_alignment(0.5, 0.025) + BaseTooltip.__init__(self) + StatusTable.__init__(self) + + def populate(self, contacts): + if not contacts or len(contacts) == 0: + return + self.create_window() + self.hbox = gtk.HBox() + self.hbox.set_homogeneous(False) + self.create_table() + # primary contact + prim_contact = gajim.get_highest_prio_contact_from_contacts(contacts) + + # try to find the image for the contact status + state_file = prim_contact.show.replace(' ', '_') + transport = self.plugin.roster.get_transport_name_by_jid(prim_contact.jid) + if transport: + file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', + transport , '16x16') + else: + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'sun' + file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') + + files = [] + file_full_path = os.path.join(file_path, state_file) + files.append(file_full_path + '.png') + files.append(file_full_path + '.gif') + self.image.set_from_pixbuf(None) + for file in files: + if os.path.exists(file): + self.image.set_from_file(file) + break + + info = '<span size="large" weight="bold">' + prim_contact.jid + '</span>' + info += '\n<span weight="bold">' + _('Name: ') + '</span>' + \ + gtkgui_helpers.escape_for_pango_markup(prim_contact.name) + info += '\n<span weight="bold">' + _('Subscription: ') + '</span>' + \ + gtkgui_helpers.escape_for_pango_markup(prim_contact.sub) + + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + info += '\n<span weight="bold">' + _('OpenPGP: ') + \ + '</span>' + gtkgui_helpers.escape_for_pango_markup(keyID) + + single_line, resource_str, multiple_resource= '', '', False + num_resources = 0 + for contact in contacts: + if contact.resource: + num_resources += 1 + if num_resources > 1: + self.current_row = 1 + self.table.resize(2,1) + info += '\n<span weight="bold">' + _('Status: ') + '</span>' + for contact in contacts: + if contact.resource: + status_line = self.get_status_info(contact.resource, contact.priority, + contact.show, contact.status) + self.add_status_row(file_path, contact.show, status_line) + + else: # only one resource + if contact.resource: + info += '\n<span weight="bold">' + _('Resource: ') + \ + '</span>' + gtkgui_helpers.escape_for_pango_markup( + contact.resource) + ' (' + str(contact.priority) + ')' + if contact.show: + info += '\n<span weight="bold">' + _('Status: ') + \ + '</span>' + helpers.get_uf_show(contact.show) + if contact.status: + status = contact.status.strip() + if status != '': + # escape markup entities. Is it posible to have markup in status? + info += ' - ' + gtkgui_helpers.escape_for_pango_markup(status) + + self.text_lable.set_markup(info) + self.hbox.pack_start(self.image, False, False) + self.hbox.pack_start(self.table, True, True) + self.win.add(self.hbox) + +class FileTransfersTooltip(BaseTooltip): + ''' Tooltip that is shown in the notification area ''' + def __init__(self): + self.text_lable = gtk.Label() + self.text_lable.set_line_wrap(True) + self.text_lable.set_alignment(0, 0) + self.text_lable.set_selectable(False) + BaseTooltip.__init__(self) + + def populate(self, file_props): + self.create_window() + self.hbox = gtk.HBox() + text = '<b>' + _('Name: ') + '</b>' + name = file_props['name'] + if not name and file_props['file-name']: + if os.path.exists(file_props['file-name']): + (path, name) = os.path.split(file_props['file-name']) + text += gtkgui_helpers.escape_for_pango_markup(name) + text += '\n<b>' + _('Type: ') + '</b>' + if file_props['type'] == 'r': + text += _('Download') + else: + text += _('Upload') + if file_props['type'] == 'r': + text += '\n<b>' + _('Sender: ') + '</b>' + sender = str(file_props['sender']).split('/')[0] + name = gajim.get_first_contact_instance_from_jid( + file_props['tt_account'], sender).name + else: + text += '\n<b>' + _('Recipient: ') + '</b>' + receiver = file_props['receiver'] + if hasattr(receiver, 'name'): + receiver = receiver.name + receiver = receiver.split('/')[0] + if receiver.find('@') == -1: + name = receiver + else: + name = gajim.get_first_contact_instance_from_jid( + file_props['tt_account'], receiver).name + text += gtkgui_helpers.escape_for_pango_markup(name) + text += '\n<b>' + _('Size: ') + '</b>' + text += helpers.convert_bytes(file_props['size']) + text += '\n<b>' + _('Transferred: ') + '</b>' + transfered_len = 0 + if file_props.has_key('received-len'): + transfered_len = file_props['received-len'] + text += helpers.convert_bytes(transfered_len) + text += '\n<b>' + _('Status: ') + '</b>' + status = '' + if not file_props.has_key('started') or not file_props['started']: + status = _('not started') + elif file_props.has_key('connected'): + if file_props.has_key('stopped') and \ + file_props['stopped'] == True: + status = _('stopped') + elif file_props['completed']: + status = _('completed') + elif file_props['connected'] == False: + if file_props['completed']: + status = _('completed') + else: + if file_props.has_key('paused') and \ + file_props['paused'] == True: + status = _('paused') + elif file_props.has_key('stalled') and \ + file_props['stalled'] == True: + status = _('stalled') + else: + status = _('transferring') + else: + status = _('not started') + + text += status + self.text_lable.set_markup(text) + self.hbox.add(self.text_lable) + self.win.add(self.hbox)