diff options
author | Yann Leboulanger <asterix@lagaule.org> | 2010-07-19 21:53:29 +0400 |
---|---|---|
committer | Yann Leboulanger <asterix@lagaule.org> | 2010-07-19 21:53:29 +0400 |
commit | 796213c9cb1e4b1ac9a138f2b9ec8cfb71c0a63d (patch) | |
tree | c862a47f0f88435054171641d3f4adde003b3c40 /src | |
parent | 9b5ee1e13b3a3e9f31aeec48e62fa69271c35e1e (diff) | |
parent | 337b09d389950edd253457432ab4c3bde2fdf5a4 (diff) |
merge with trunk
Diffstat (limited to 'src')
102 files changed, 4245 insertions, 2871 deletions
diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py index 29a9ed5c9..85ba9cfd1 100644 --- a/src/adhoc_commands.py +++ b/src/adhoc_commands.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## Stephan Erb <steve-e AT h3c.de> ## @@ -55,21 +55,15 @@ class CommandWindow: # an account object self.account = gajim.connections[account] self.jid = jid - - self.pulse_id=None # to satisfy self.setup_pulsing() - self.commandlist=None # a list of (commandname, commanddescription) - - # command's data self.commandnode = commandnode - self.sessionid = None - self.dataform = None - self.allow_stage3_close = False + self.data_form_widget = None # retrieving widgets from xml self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui') self.window = self.xml.get_object('adhoc_commands_window') - self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event) - for name in ('back_button', 'forward_button', + self.window.connect('delete-event', + self.on_adhoc_commands_window_delete_event) + for name in ('restart_button', 'back_button', 'forward_button', 'execute_button', 'close_button', 'stages_notebook', 'retrieving_commands_stage_vbox', 'command_list_stage_vbox', 'command_list_vbox', @@ -78,7 +72,22 @@ class CommandWindow: 'error_description_label'): self.__dict__[name] = self.xml.get_object(name) + self.initiate() + + def initiate(self): + + self.pulse_id = None # to satisfy self.setup_pulsing() + self.commandlist = None # a list of (commandname, commanddescription) + + # command's data + self.sessionid = None + self.dataform = None + self.allow_stage3_close = False + # creating data forms widget + if self.data_form_widget: + self.sending_form_stage_vbox.remove(self.data_form_widget) + self.data_form_widget.destroy() self.data_form_widget = dataforms_widget.DataFormWidget() self.data_form_widget.show() self.sending_form_stage_vbox.pack_start(self.data_form_widget) @@ -94,6 +103,8 @@ class CommandWindow: self.xml.connect_signals(self) self.window.show_all() + self.restart_button.set_sensitive(False) + # These functions are set up by appropriate stageX methods. def stage_finish(self, *anything): pass @@ -130,7 +141,7 @@ class CommandWindow: return self.stage_close_button_clicked(*anything) def on_adhoc_commands_window_destroy(self, *anything): - # TODO: do all actions that are needed to remove this object from memory... + # TODO: do all actions that are needed to remove this object from memory self.remove_pulsing() def on_adhoc_commands_window_delete_event(self, *anything): @@ -167,7 +178,8 @@ class CommandWindow: # setup the callbacks self.stage_finish = self.stage1_finish self.stage_close_button_clicked = self.stage1_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event + self.stage_adhoc_commands_window_delete_event = \ + self.stage1_adhoc_commands_window_delete_event def stage1_finish(self): self.remove_pulsing() @@ -196,8 +208,7 @@ class CommandWindow: assert len(self.commandlist)>0 self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.command_list_stage_vbox)) + self.stages_notebook.page_num(self.command_list_stage_vbox)) self.close_button.set_sensitive(True) self.back_button.set_sensitive(False) @@ -208,7 +219,8 @@ class CommandWindow: first_radio = None for (commandnode, commandname) in self.commandlist: radio = gtk.RadioButton(first_radio, label=commandname) - radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode) + radio.connect("toggled", self.on_command_radiobutton_toggled, + commandnode) if not first_radio: first_radio = radio self.commandnode = commandnode @@ -266,7 +278,8 @@ class CommandWindow: self.stage_forward_button_clicked = self.stage3_forward_button_clicked self.stage_execute_button_clicked = self.stage3_execute_button_clicked self.stage_close_button_clicked = self.stage3_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked + self.stage_adhoc_commands_window_delete_event = \ + self.stage3_close_button_clicked def stage3_finish(self): pass @@ -276,10 +289,10 @@ class CommandWindow: We are in the middle of executing command. Ask user if he really want to cancel the process, then cancel it """ - # this works also as a handler for window_delete_event, so we have to return appropriate - # values + # this works also as a handler for window_delete_event, so we have to + # return appropriate values if self.form_status == 'completed': - if widget!=self.window: + if widget != self.window: self.window.destroy() return False @@ -291,10 +304,10 @@ class CommandWindow: self.allow_stage3_close = True self.window.destroy() - dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \ - gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'), - _('You are in process of executing command. Do you really want to ' - 'cancel it?'), on_response_yes=on_yes) + dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT \ + | gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'), + _('You are in process of executing command. Do you really want to ' + 'cancel it?'), on_response_yes=on_yes) dialog.popup() return True # Block event, don't close window @@ -310,7 +323,7 @@ class CommandWindow: def stage3_submit_form(self, action='execute'): self.data_form_widget.set_sensitive(False) if self.data_form_widget.get_data_form(): - self.data_form_widget.data_form.type='submit' + self.data_form_widget.data_form.type = 'submit' else: self.data_form_widget.hide() @@ -346,9 +359,10 @@ class CommandWindow: self.data_form_widget.set_sensitive(True) try: - self.data_form_widget.data_form=self.dataform + self.data_form_widget.data_form = self.dataform except dataforms.Error: - self.stage5(error=_('Service sent malformed data'), senderror=True) + self.stage5(error=_('Service sent malformed data'), + senderror=True) return self.data_form_widget.show() if self.data_form_widget.title: @@ -362,7 +376,8 @@ class CommandWindow: # actions, actions, actions... self.close_button.set_sensitive(True) self.back_button.set_sensitive(actions.getTag('prev') is not None) - self.forward_button.set_sensitive(actions.getTag('next') is not None) + self.forward_button.set_sensitive( + actions.getTag('next') is not None) self.execute_button.set_sensitive(True) else: self.close_button.set_sensitive(True) @@ -372,11 +387,13 @@ class CommandWindow: if self.form_status == 'completed': self.close_button.set_sensitive(True) + self.restart_button.set_sensitive(True) self.back_button.hide() self.forward_button.hide() self.execute_button.hide() self.close_button.show() - self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked + self.stage_adhoc_commands_window_delete_event = \ + self.stage3_close_button_clicked note = command.getTag('note') if note: @@ -387,6 +404,10 @@ class CommandWindow: self.notes_label.set_no_show_all(True) self.notes_label.hide() + def on_restart_button_clicked(self, widget): + self.commandnode = None + self.initiate() + # stage 4: no commands are exposed def stage4(self): """ @@ -396,8 +417,7 @@ class CommandWindow: self.stage_finish() self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.no_commands_stage_vbox)) + self.stages_notebook.page_num(self.no_commands_stage_vbox)) self.close_button.set_sensitive(True) self.back_button.set_sensitive(False) @@ -442,8 +462,7 @@ class CommandWindow: assert False self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.error_stage_vbox)) + self.stages_notebook.page_num(self.error_stage_vbox)) self.close_button.set_sensitive(True) self.back_button.hide() @@ -481,14 +500,15 @@ class CommandWindow: """ if self.pulse_id: gobject.source_remove(self.pulse_id) - self.pulse_id=None + self.pulse_id = None # handling xml stanzas def request_command_list(self): """ Request the command list. Change stage on delivery """ - query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS) + query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), + queryNS=xmpp.NS_DISCO_ITEMS) query.setQuerynode(xmpp.NS_COMMANDS) def callback(response): @@ -512,7 +532,8 @@ class CommandWindow: self.commandlist = [] self.stage4() else: - self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items] + self.commandlist = [(t.getAttr('node'), t.getAttr('name')) \ + for t in items] self.stage2() self.account.connection.SendAndCallForResponse(query, callback) @@ -533,9 +554,6 @@ class CommandWindow: cmdnode.setAttr('sessionid', self.sessionid) if self.data_form_widget.data_form: -# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form)) - # FIXME: simplified form to send - cmdnode.addChild(node=self.data_form_widget.data_form) def callback(response): @@ -564,6 +582,6 @@ class CommandWindow: self.account.connection.send(stanza) else: - # we did not received any reply from service; FIXME: we should wait and - # then send cancel; for now we do nothing + # we did not received any reply from service; + # FIXME: we should wait and then send cancel; for now we do nothing pass diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py index 33831f827..d59ed09a1 100644 --- a/src/advanced_configuration_window.py +++ b/src/advanced_configuration_window.py @@ -3,8 +3,8 @@ ## ## Copyright (C) 2005 Travis Shirk <travis AT pobox.com> ## Vincent Hanquez <tab AT snarc.org> -## Copyright (C) 2005-2007 Yann Leboulanger <asterix AT lagaule.org> -## Nikos Kouremenos <kourem AT gmail.com> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> ## diff --git a/src/atom_window.py b/src/atom_window.py index 9052ed1ae..70d47e2f2 100644 --- a/src/atom_window.py +++ b/src/atom_window.py @@ -2,7 +2,7 @@ ## src/atom_window.py ## ## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## @@ -53,15 +53,16 @@ class AtomWindow: """ Create new window... only if we have anything to show """ - assert len(self.__class__.entries)>0 + assert len(self.__class__.entries) - self.entry = None # the entry actually displayed + self.entry = None # the entry actually displayed self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui') self.window = self.xml.get_object('atom_entry_window') - for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', - 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', - 'last_modified_label', 'close_button', 'next_button'): + for name in ('new_entry_label', 'feed_title_label', + 'feed_title_eventbox', 'feed_tagline_label', 'entry_title_label', + 'entry_title_eventbox', 'last_modified_label', 'close_button', + 'next_button'): self.__dict__[name] = self.xml.get_object(name) self.displayNextEntry() @@ -83,23 +84,26 @@ class AtomWindow: # fill the fields if newentry.feed_link is not None: self.feed_title_label.set_markup( - u'<span foreground="blue" underline="single">%s</span>' % \ - gobject.markup_escape_text(newentry.feed_title)) + u'<span foreground="blue" underline="single">%s</span>' % \ + gobject.markup_escape_text(newentry.feed_title)) else: self.feed_title_label.set_markup( - gobject.markup_escape_text(newentry.feed_title)) + gobject.markup_escape_text(newentry.feed_title)) self.feed_tagline_label.set_markup( - u'<small>%s</small>' % \ - gobject.markup_escape_text(newentry.feed_tagline)) + u'<small>%s</small>' % \ + gobject.markup_escape_text(newentry.feed_tagline)) - if newentry.uri is not None: - self.entry_title_label.set_markup( + if newentry.title: + if newentry.uri is not None: + self.entry_title_label.set_markup( u'<span foreground="blue" underline="single">%s</span>' % \ gobject.markup_escape_text(newentry.title)) - else: - self.entry_title_label.set_markup( + else: + self.entry_title_label.set_markup( gobject.markup_escape_text(newentry.title)) + else: + self.entry_title_label.set_markup('') self.last_modified_label.set_text(newentry.updated) @@ -114,11 +118,11 @@ class AtomWindow: changed """ count = len(self.__class__.entries) - if count>0: + if count: self.new_entry_label.set_text(i18n.ngettext( - 'You have received new entries (and %d not displayed):', - 'You have received new entries (and %d not displayed):', count, - count, count)) + 'You have received new entries (and %d not displayed):', + 'You have received new entries (and %d not displayed):', count, + count, count)) self.next_button.set_sensitive(True) else: self.new_entry_label.set_text(_('You have received new entry:')) @@ -131,7 +135,7 @@ class AtomWindow: def on_next_button_clicked(self, widget): self.displayNextEntry() - def on_entry_title_button_press_event(self, widget, event): + def on_entry_title_eventbox_button_press_event(self, widget, event): #FIXME: make it using special gtk2.10 widget if event.button == 1: # left click uri = self.entry.uri @@ -139,7 +143,7 @@ class AtomWindow: helpers.launch_browser_mailer('url', uri) return True - def on_feed_title_button_press_event(self, widget, event): + def on_feed_title_eventbox_button_press_event(self, widget, event): #FIXME: make it using special gtk2.10 widget if event.button == 1: # left click uri = self.entry.feed_uri diff --git a/src/cell_renderer_image.py b/src/cell_renderer_image.py index 0b85e8c44..1e5b4bb8e 100644 --- a/src/cell_renderer_image.py +++ b/src/cell_renderer_image.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/cell_renderer_image.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com> diff --git a/src/chat_control.py b/src/chat_control.py index 26b2241ce..8221b898d 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2,8 +2,8 @@ ## src/chat_control.py ## ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> -## Jean-Marie Traissard <jim AT lapin.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Nikos Kouremenos <kourem AT gmail.com> ## Travis Shirk <travis AT pobox.com> ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net> @@ -94,6 +94,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): A base class containing a banner, ConversationTextview, MessageTextView """ + keymap = gtk.gdk.keymap_get_default() + try: + keycode_c = keymap.get_entries_for_keyval(gtk.keysyms.c)[0][0] + except TypeError: + keycode_c = 54 + try: + keycode_ins = keymap.get_entries_for_keyval(gtk.keysyms.Insert)[0][0] + except TypeError: + keycode_ins = 118 def make_href(self, match): url_color = gajim.config.get('urlmsgcolor') url = match.group() @@ -147,7 +156,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self.draw_banner_text() self._update_banner_state_image() gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner', - self) + self) def draw_banner_text(self): """ @@ -231,6 +240,29 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): def status_url_clicked(self, widget, url): helpers.launch_browser_mailer('url', url) + def setup_seclabel(self, combo): + self.seclabel_combo = combo + self.seclabel_combo.hide() + self.seclabel_combo.set_no_show_all(True) + lb = gtk.ListStore(str) + self.seclabel_combo.set_model(lb) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) # padding for status text + self.seclabel_combo.pack_start(cell, True) + # text to show is in in first column of liststore + self.seclabel_combo.add_attribute(cell, 'text', 0) + if gajim.connections[self.account].seclabel_supported: + gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready) + + def on_seclabels_ready(self): + lb = self.seclabel_combo.get_model() + lb.clear() + for label in gajim.connections[self.account].seclabel_catalogues[self.contact.jid][2]: + lb.append([label]) + self.seclabel_combo.set_active(0) + self.seclabel_combo.set_no_show_all(False) + self.seclabel_combo.show_all() + def __init__(self, type_id, parent_win, widget_name, contact, acct, resource=None): # Undo needs this variable to know if space has been pressed. @@ -383,7 +415,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): # instance object (also subclasses, eg. ChatControl or GroupchatControl) gajim.plugin_manager.gui_extension_point('chat_control_base', self) - def set_speller(self): # now set the one the user selected per_type = 'contacts' @@ -419,7 +450,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): i += 1 menu.show_all() - def shutdown(self): # PluginSystem: removing GUI extension points connected with ChatControlBase # instance object @@ -568,8 +598,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self.connect_style_event(widget, opts[0], opts[1]) def _conv_textview_key_press_event(self, widget, event): - if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c, - gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \ + # translate any layout to latin_layout + keymap = gtk.gdk.keymap_get_default() + keycode = keymap.get_entries_for_keyval(event.keyval)[0][0] + if (event.state & gtk.gdk.CONTROL_MASK and keycode in (self.keycode_c, + self.keycode_ins)) or (event.state & gtk.gdk.SHIFT_MASK and \ event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)): return False self.parent_win.notebook.emit('key_press_event', event) @@ -729,6 +762,16 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self.drag_entered_conv = True self.conv_textview.tv.set_editable(True) + def get_seclabel(self): + label = None + if self.seclabel_combo is not None: + idx = self.seclabel_combo.get_active() + if idx != -1: + cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid] + lname = cat[2][idx] + label = cat[1][lname] + return label + def send_message(self, message, keyID='', type_='chat', chatstate=None, msg_id=None, composing_xep=None, resource=None, xhtml=None, callback=None, callback_args=[], process_commands=True): @@ -741,9 +784,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): if process_commands and self.process_as_command(message): return + label = self.get_seclabel() MessageControl.send_message(self, message, keyID, type_=type_, chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, resource=resource, user_nick=self.user_nick, xhtml=xhtml, + label=label, callback=callback, callback_args=callback_args) # Record message history @@ -777,7 +822,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[], count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False, xep0184_id=None, - graphics=True): + graphics=True, displaymarking=None): """ Print 'chat' type messages """ @@ -789,7 +834,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): end = True textview.print_conversation_line(text, jid, kind, name, tim, other_tags_for_name, other_tags_for_time, other_tags_for_text, - subject, old_kind, xhtml, simple=simple, graphics=graphics) + subject, old_kind, xhtml, simple=simple, graphics=graphics, + displaymarking=displaymarking) if xep0184_id is not None: textview.show_xep0184_warning(xep0184_id) @@ -1267,13 +1313,12 @@ class ChatControl(ChatControlBase): A control for standard 1-1 chat """ ( - JINGLE_STATE_NOT_AVAILABLE, - JINGLE_STATE_AVAILABLE, + JINGLE_STATE_NULL, JINGLE_STATE_CONNECTING, JINGLE_STATE_CONNECTION_RECEIVED, JINGLE_STATE_CONNECTED, JINGLE_STATE_ERROR - ) = range(6) + ) = range(5) TYPE_ID = message_control.TYPE_CHAT old_msg_kind = None # last kind of the printed message @@ -1360,9 +1405,11 @@ class ChatControl(ChatControlBase): self._audio_banner_image = self.xml.get_object('audio_banner_image') self._video_banner_image = self.xml.get_object('video_banner_image') self.audio_sid = None - self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE + self.audio_state = self.JINGLE_STATE_NULL + self.audio_available = False self.video_sid = None - self.video_state = self.JINGLE_STATE_NOT_AVAILABLE + self.video_state = self.JINGLE_STATE_NULL + self.video_available = False self.update_toolbar() @@ -1420,6 +1467,15 @@ class ChatControl(ChatControlBase): id_ = widget.connect('released', self.on_num_button_released) self.handlers[id_] = widget + self.dtmf_window = self.xml.get_object('dtmf_window') + id_ = self.dtmf_window.connect('focus-out-event', + self.on_dtmf_window_focus_out_event) + self.handlers[id_] = self.dtmf_window + + widget = self.xml.get_object('dtmf_button') + id_ = widget.connect('clicked', self.on_dtmf_button_clicked) + self.handlers[id_] = widget + widget = self.xml.get_object('mic_hscale') id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed) self.handlers[id_] = widget @@ -1436,6 +1492,7 @@ class ChatControl(ChatControlBase): session = gajim.connections[self.account].find_controlless_session( self.contact.jid, resource) + self.setup_seclabel(self.xml.get_object('label_selector')) if session: session.control = self self.session = session @@ -1488,38 +1545,24 @@ class ChatControl(ChatControlBase): # Jingle detection if self.contact.supports(NS_JINGLE_ICE_UDP) and \ gajim.HAVE_FARSIGHT and self.contact.resource: - if self.contact.supports(NS_JINGLE_RTP_AUDIO): - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - self.set_audio_state('available') - else: - self.set_audio_state('not_available') - - if self.contact.supports(NS_JINGLE_RTP_VIDEO): - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: - self.set_video_state('available') - else: - self.set_video_state('not_available') + self.audio_available = self.contact.supports(NS_JINGLE_RTP_AUDIO) + self.video_available = self.contact.supports(NS_JINGLE_RTP_VIDEO) else: - if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE: - self.set_audio_state('not_available') - if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE: - self.set_video_state('not_available') + if self.video_available or self.audio_available: + self.stop_jingle() + self.video_available = False + self.audio_available = False # Audio buttons - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - self._audio_button.set_sensitive(False) - else: - self._audio_button.set_sensitive(True) + self._audio_button.set_sensitive(self.audio_available) # Video buttons - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: - self._video_button.set_sensitive(False) - else: - self._video_button.set_sensitive(True) + self._video_button.set_sensitive(self.video_available) # Send file if self.contact.supports(NS_FILE) and self.contact.resource: self._send_file_button.set_sensitive(True) + self._send_file_button.set_tooltip_text('') else: self._send_file_button.set_sensitive(False) if not self.contact.supports(NS_FILE): @@ -1554,18 +1597,16 @@ class ChatControl(ChatControlBase): else: img.hide() - # PluginSystem: adding GUI extension point for this ChatControl + # PluginSystem: adding GUI extension point for this ChatControl # instance object gajim.plugin_manager.gui_extension_point('chat_control', self) - def _update_jingle(self, jingle_type): if jingle_type not in ('audio', 'video'): return banner_image = getattr(self, '_' + jingle_type + '_banner_image') state = getattr(self, jingle_type + '_state') - if state in (self.JINGLE_STATE_NOT_AVAILABLE, - self.JINGLE_STATE_AVAILABLE): + if state == self.JINGLE_STATE_NULL: banner_image.hide() else: banner_image.show() @@ -1585,7 +1626,7 @@ class ChatControl(ChatControlBase): def update_audio(self): self._update_jingle('audio') - vbox = self.xml.get_object('audio_vbox') + hbox = self.xml.get_object('audio_buttons_hbox') if self.audio_state == self.JINGLE_STATE_CONNECTED: # Set volume from config input_vol = gajim.config.get('audio_input_volume') @@ -1595,11 +1636,11 @@ class ChatControl(ChatControlBase): self.xml.get_object('mic_hscale').set_value(input_vol) self.xml.get_object('sound_hscale').set_value(output_vol) # Show vbox - vbox.set_no_show_all(False) - vbox.show_all() + hbox.set_no_show_all(False) + hbox.show_all() elif not self.audio_sid: - vbox.set_no_show_all(True) - vbox.hide() + hbox.set_no_show_all(True) + hbox.hide() def update_video(self): self._update_jingle('video') @@ -1617,43 +1658,42 @@ class ChatControl(ChatControlBase): # update MessageWindow._controls self.parent_win.change_jid(self.account, old_full_jid, new_full_jid) + def stop_jingle(self, sid=None, reason=None): + if self.audio_sid and sid in (self.audio_sid, None): + self.close_jingle_content('audio') + if self.video_sid and sid in (self.video_sid, None): + self.close_jingle_content('video') + + def _set_jingle_state(self, jingle_type, state, sid=None, reason=None): if jingle_type not in ('audio', 'video'): return - if state in ('connecting', 'connected', 'stop') and reason: + if state in ('connecting', 'connected', 'stop', 'error') and reason: str = _('%(type)s state : %(state)s, reason: %(reason)s') % { 'type': jingle_type.capitalize(), 'state': state, 'reason': reason} self.print_conversation(str, 'info') - states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE, - 'available': self.JINGLE_STATE_AVAILABLE, - 'connecting': self.JINGLE_STATE_CONNECTING, + states = {'connecting': self.JINGLE_STATE_CONNECTING, 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED, 'connected': self.JINGLE_STATE_CONNECTED, - 'stop': self.JINGLE_STATE_AVAILABLE, + 'stop': self.JINGLE_STATE_NULL, 'error': self.JINGLE_STATE_ERROR} - if state in states: - jingle_state = states[state] - if getattr(self, jingle_type + '_state') == jingle_state: - return - setattr(self, jingle_type + '_state', jingle_state) + jingle_state = states[state] + if getattr(self, jingle_type + '_state') == jingle_state or state == 'error': + return + + if state == 'stop' and getattr(self, jingle_type + '_sid') not in (None, sid): + return - # Destroy existing session with the user when he signs off - # We need to do that before modifying the sid - if state == 'not_available': - gajim.connections[self.account].delete_jingle_session( - self.contact.get_full_jid(), getattr(self, jingle_type + '_sid')) + setattr(self, jingle_type + '_state', jingle_state) - if state in ('not_available', 'available', 'stop'): + if jingle_state == self.JINGLE_STATE_NULL: setattr(self, jingle_type + '_sid', None) if state in ('connection_received', 'connecting'): setattr(self, jingle_type + '_sid', sid) - if state in ('connecting', 'connected', 'connection_received'): - getattr(self, '_' + jingle_type + '_button').set_active(True) - elif state in ('not_available', 'stop'): - getattr(self, '_' + jingle_type + '_button').set_active(False) + getattr(self, '_' + jingle_type + '_button').set_active(jingle_state != self.JINGLE_STATE_NULL) getattr(self, 'update_' + jingle_type)() @@ -1674,19 +1714,21 @@ class ChatControl(ChatControlBase): def on_num_button_released(self, released): self._get_audio_content()._stop_dtmf() - def on_mic_hscale_value_changed(self, widget): - value = widget.get_value() + def on_dtmf_button_clicked(self, widget): + self.dtmf_window.show_all() + + def on_dtmf_window_focus_out_event(self, widget, event): + self.dtmf_window.hide() + + def on_mic_hscale_value_changed(self, widget, value): self._get_audio_content().set_mic_volume(value / 100) # Save volume to config - # FIXME: Putting it here is maybe not the right thing to do? gajim.config.set('audio_input_volume', value) - def on_sound_hscale_value_changed(self, widget): - value = widget.get_value() + def on_sound_hscale_value_changed(self, widget, value): self._get_audio_content().set_out_volume(value / 100) # Save volume to config - # FIXME: Putting it here is maybe not the right thing to do? gajim.config.set('audio_output_volume', value) def on_avatar_eventbox_enter_notify_event(self, widget, event): @@ -1708,6 +1750,8 @@ class ChatControl(ChatControlBase): # do we have something bigger to show? if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h: # wait for 0.5 sec in case we leave earlier + if self.show_bigger_avatar_timeout_id is not None: + gobject.source_remove(self.show_bigger_avatar_timeout_id) self.show_bigger_avatar_timeout_id = gobject.timeout_add(500, self.show_bigger_avatar, widget) @@ -1718,6 +1762,7 @@ class ChatControl(ChatControlBase): # did we add a timeout? if yes remove it if self.show_bigger_avatar_timeout_id is not None: gobject.source_remove(self.show_bigger_avatar_timeout_id) + self.show_bigger_avatar_timeout_id = None def on_avatar_eventbox_button_press_event(self, widget, event): """ @@ -1727,8 +1772,8 @@ class ChatControl(ChatControlBase): menu = gtk.Menu() menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) id_ = menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name()) + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.contact.get_shown_name()) self.handlers[id_] = menuitem menu.append(menuitem) menu.show_all() @@ -1899,12 +1944,16 @@ class ChatControl(ChatControlBase): sid = getattr(self, jingle_type + '_sid') if not sid: return + setattr(self, jingle_type + '_sid', None) + setattr(self, jingle_type + '_state', self.JINGLE_STATE_NULL) session = gajim.connections[self.account].get_jingle_session( self.contact.get_full_jid(), sid) if session: content = session.get_content(jingle_type) if content: session.remove_content(content.creator, content.name) + getattr(self, '_' + jingle_type + '_button').set_active(False) + getattr(self, 'update_' + jingle_type)() def on_jingle_button_toggled(self, widget, jingle_type): img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type], @@ -1913,7 +1962,7 @@ class ChatControl(ChatControlBase): if widget.get_active(): if getattr(self, jingle_type + '_state') == \ - self.JINGLE_STATE_AVAILABLE: + self.JINGLE_STATE_NULL: sid = getattr(gajim.connections[self.account], 'start_' + jingle_type)(self.contact.get_full_jid()) getattr(self, 'set_' + jingle_type + '_state')('connecting', sid) @@ -2069,20 +2118,23 @@ class ChatControl(ChatControlBase): gobject.source_remove(self.possible_inactive_timeout_id) self._schedule_activity_timers() - def _on_sent(id_, contact, message, encrypted, xhtml): + def _on_sent(id_, contact, message, encrypted, xhtml, label): if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts', self.account, 'request_receipt'): xep0184_id = id_ else: xep0184_id = None - + if label: + displaymarking = label.getTag('displaymarking') + else: + displaymarking = None self.print_conversation(message, self.contact.jid, encrypted=encrypted, - xep0184_id=xep0184_id, xhtml=xhtml) + xep0184_id=xep0184_id, xhtml=xhtml, displaymarking=displaymarking) ChatControlBase.send_message(self, message, keyID, type_='chat', chatstate=chatstate_to_send, composing_xep=composing_xep, xhtml=xhtml, callback=_on_sent, - callback_args=[contact, message, encrypted, xhtml], + callback_args=[contact, message, encrypted, xhtml, self.get_seclabel()], process_commands=process_commands) def check_for_possible_paused_chatstate(self, arg): @@ -2171,7 +2223,8 @@ class ChatControl(ChatControlBase): self.session.is_loggable(), self.session and self.session.verified_identity) def print_conversation(self, text, frm='', tim=None, encrypted=False, - subject=None, xhtml=None, simple=False, xep0184_id=None): + subject=None, xhtml=None, simple=False, xep0184_id=None, + displaymarking=None): """ Print a line in the conversation @@ -2234,7 +2287,7 @@ class ChatControl(ChatControlBase): xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml) ChatControlBase.print_conversation_line(self, text, kind, name, tim, subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml, - simple=simple, xep0184_id=xep0184_id) + simple=simple, xep0184_id=xep0184_id, displaymarking=displaymarking) if text.startswith('/me ') or text.startswith('/me\n'): self.old_msg_kind = None else: @@ -2423,9 +2476,8 @@ class ChatControl(ChatControlBase): super(ChatControl, self).shutdown() # PluginSystem: removing GUI extension points connected with ChatControl # instance object - gajim.plugin_manager.remove_gui_extension_point('chat_control', self) + gajim.plugin_manager.remove_gui_extension_point('chat_control', self) # Send 'gone' chatstate - # Send 'gone' chatstate self.send_chatstate('gone', self.contact) self.contact.chatstate = None self.contact.our_chatstate = None @@ -2688,8 +2740,12 @@ class ChatControl(ChatControlBase): kind = 'info' else: kind = 'print_queue' + dm = None + if len(data) > 10: + dm = data[10] self.print_conversation(data[0], kind, tim = data[3], - encrypted = data[4], subject = data[1], xhtml = data[7]) + encrypted = data[4], subject = data[1], xhtml = data[7], + displaymarking=dm) if len(data) > 6 and isinstance(data[6], int): message_ids.append(data[6]) diff --git a/src/command_system/__init__.py b/src/command_system/__init__.py index ff61c186e..2fb336264 100644 --- a/src/command_system/__init__.py +++ b/src/command_system/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,7 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The command system providing scalable, clean and convenient architecture in -combination with declarative way of defining commands and a fair amount of -automatization for routine processes. +The command system providing scalable, clean and convenient architecture +in combination with declarative way of defining commands and a fair +amount of automatization for routine processes. """ diff --git a/src/command_system/dispatching.py b/src/command_system/dispatching.py index 7f365a915..0c613ec15 100644 --- a/src/command_system/dispatching.py +++ b/src/command_system/dispatching.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,9 +14,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The backbone of the command system. Provides automatic dispatching which does -not require explicit registering commands or containers and remains active even -after everything is done, so new commands can be added during the runtime. +The backbone of the command system. Provides automatic dispatching which +does not require explicit registering commands or containers and remains +active even after everything is done, so new commands can be added +during the runtime. """ from types import NoneType diff --git a/src/command_system/errors.py b/src/command_system/errors.py index 4281e9fd7..d3b29e85e 100644 --- a/src/command_system/errors.py +++ b/src/command_system/errors.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -15,8 +15,9 @@ class BaseError(Exception): """ - Common base for errors which relate to a specific command. Encapsulates - everything needed to identify a command, by either its object or name. + Common base for errors which relate to a specific command. + Encapsulates everything needed to identify a command, by either its + object or name. """ def __init__(self, message, command=None, name=None): @@ -44,3 +45,9 @@ class CommandError(BaseError): Used to indicate errors occured during command execution. """ pass + +class NoCommandError(BaseError): + """ + Used to indicate an inability to find the specified command. + """ + pass diff --git a/src/command_system/framework.py b/src/command_system/framework.py index 30f5cd53c..c8c822494 100644 --- a/src/command_system/framework.py +++ b/src/command_system/framework.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,8 +14,9 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -Provides a tiny framework with simple, yet powerful and extensible architecture -to implement commands in a streight and flexible, declarative way. +Provides a tiny framework with simple, yet powerful and extensible +architecture to implement commands in a streight and flexible, +declarative way. """ import re @@ -24,46 +25,47 @@ from inspect import getargspec from dispatching import Dispatcher, HostDispatcher, ContainerDispatcher from mapping import parse_arguments, adapt_arguments -from errors import DefinitionError, CommandError +from errors import DefinitionError, CommandError, NoCommandError class CommandHost(object): """ - Command host is a hub between numerous command processors and command - containers. Aimed to participate in a dispatching process in order to - provide clean and transparent architecture. + Command host is a hub between numerous command processors and + command containers. Aimed to participate in a dispatching process in + order to provide clean and transparent architecture. """ __metaclass__ = HostDispatcher class CommandContainer(object): """ - Command container is an entity which holds defined commands, allowing them - to be dispatched and proccessed correctly. Each command container may be - bound to a one or more command hosts. + Command container is an entity which holds defined commands, + allowing them to be dispatched and proccessed correctly. Each + command container may be bound to a one or more command hosts. - Bounding is controlled by the HOSTS variable, which must be defined in the - body of the command container. This variable should contain a list of hosts - to bound to, as a tuple or list. + Bounding is controlled by the HOSTS variable, which must be defined + in the body of the command container. This variable should contain a + list of hosts to bound to, as a tuple or list. """ __metaclass__ = ContainerDispatcher class CommandProcessor(object): """ - Command processor is an immediate command emitter. It does not participate - in the dispatching process directly, but must define a host to bound to. + Command processor is an immediate command emitter. It does not + participate in the dispatching process directly, but must define a + host to bound to. - Bounding is controlled by the COMMAND_HOST variable, which must be defined - in the body of the command processor. This variable should be set to a - specific command host. + Bounding is controlled by the COMMAND_HOST variable, which must be + defined in the body of the command processor. This variable should + be set to a specific command host. """ - # This defines a command prefix (or an initializer), which should preceede a - # a text in order it to be processed as a command. + # This defines a command prefix (or an initializer), which should + # preceede a a text in order it to be processed as a command. COMMAND_PREFIX = '/' def process_as_command(self, text): """ - Try to process text as a command. Returns True if it has been processed - as a command and False otherwise. + Try to process text as a command. Returns True if it has been + processed as a command and False otherwise. """ prefix = text.startswith(self.COMMAND_PREFIX) length = len(text) > len(self.COMMAND_PREFIX) @@ -97,28 +99,28 @@ class CommandProcessor(object): def command_preprocessor(self, command, name, arguments, args, kwargs): """ - Redefine this method in the subclass to execute custom code before - command gets executed. + Redefine this method in the subclass to execute custom code + before command gets executed. - If returns True then command execution will be interrupted and command - will not be executed. + If returns True then command execution will be interrupted and + command will not be executed. """ pass def command_postprocessor(self, command, name, arguments, args, kwargs, value): """ - Redefine this method in the subclass to execute custom code after - command gets executed. + Redefine this method in the subclass to execute custom code + after command gets executed. """ pass def looks_like_command(self, text, body, name, arguments): """ - This hook is being called before any processing, but after it was - determined that text looks like a command. + This hook is being called before any processing, but after it + was determined that text looks like a command. - If returns value other then None - then further processing will be - interrupted and that value will be used to return from + If returns value other then None - then further processing will + be interrupted and that value will be used to return from process_as_command. """ pass @@ -126,7 +128,7 @@ class CommandProcessor(object): def get_command(self, name): command = Dispatcher.get_command(self.COMMAND_HOST, name) if not command: - raise CommandError("Command does not exist", name=name) + raise NoCommandError("Command does not exist", name=name) return command def list_commands(self): @@ -136,8 +138,9 @@ class CommandProcessor(object): class Command(object): - # These two regular expression patterns control how command documentation - # will be formatted to be transformed to a normal, readable state. + # These two regular expression patterns control how command + # documentation will be formatted to be transformed to a normal, + # readable state. DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\n)', re.MULTILINE) DOC_FORMAT_PATTERN = re.compile(r'(?<!\n)\n(?!\n)', re.MULTILINE) @@ -145,8 +148,8 @@ class Command(object): self.handler = handler self.names = names - # Automatically set all the properties passed to a constructor by the - # command decorator. + # Automatically set all the properties passed to a constructor + # by the command decorator. for key, value in properties.iteritems(): setattr(self, key, value) @@ -154,18 +157,20 @@ class Command(object): try: return self.handler(*args, **kwargs) - # This allows to use a shortcuted way of raising an exception inside a - # handler. That is to raise a CommandError without command or name - # attributes set. They will be set to a corresponding values right here - # in case if they was not set by the one who raised an exception. + # This allows to use a shortcuted way of raising an exception + # inside a handler. That is to raise a CommandError without + # command or name attributes set. They will be set to a + # corresponding values right here in case if they was not set by + # the one who raised an exception. except CommandError, error: if not error.command and not error.name: raise CommandError(error.message, self) raise # This one is a little bit too wide, but as Python does not have - # anything more constrained - there is no other choice. Take a look here - # if command complains about invalid arguments while they are ok. + # anything more constrained - there is no other choice. Take a + # look here if command complains about invalid arguments while + # they are ok. except TypeError: raise CommandError("Command received invalid arguments", self) @@ -185,8 +190,8 @@ class Command(object): def extract_documentation(self): """ - Extract handler's documentation which is a doc-string and transform it - to a usable format. + Extract handler's documentation which is a doc-string and + transform it to a usable format. Transformation is done based on the DOC_STRIP_PATTERN and DOC_FORMAT_PATTERN regular expression patterns. @@ -211,19 +216,19 @@ class Command(object): def extract_specification(self): """ - Extract handler's arguments specification, as it was defined preserving - their order. + Extract handler's arguments specification, as it was defined + preserving their order. """ names, var_args, var_kwargs, defaults = getargspec(self.handler) - # Behavior of this code need to be checked. Might yield incorrect - # results on some rare occasions. + # Behavior of this code need to be checked. Might yield + # incorrect results on some rare occasions. spec_args = names[:-len(defaults) if defaults else len(names)] spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {} - # Removing self from arguments specification. Command handler should - # receive the processors as a first argument, which should be self by - # the canonical means. + # Removing self from arguments specification. Command handler + # should receive the processors as a first argument, which + # should be self by the canonical means. if spec_args.pop(0) != 'self': raise DefinitionError("First argument must be self", self) @@ -231,44 +236,47 @@ class Command(object): def command(*names, **properties): """ - A decorator for defining commands in a declarative way. Provides facilities - for setting command's names and properties. - - Names should contain a set of names (aliases) by which the command can be - reached. If no names are given - the the native name (the one extracted from - the command handler) will be used. - - If include_native=True is given (default) and names is non-empty - then the - native name of the command will be prepended in addition to the given names. - - If usage=True is given (default) - then command help will be appended with - autogenerated usage info, based of the command handler arguments - introspection. - - If source=True is given - then the first argument of the command will - receive the source arguments, as a raw, unprocessed string. The further - mapping of arguments and options will not be affected. - - If raw=True is given - then command considered to be raw and should define - positional arguments only. If it defines only one positional argument - this - argument will receive all the raw and unprocessed arguments. If the command - defines more then one positional argument - then all the arguments except - the last one will be processed normally; the last argument will get what is - left after the processing as raw and unprocessed string. - - If empty=True is given - this will allow to call a raw command without - arguments. - - If extra=True is given - then all the extra arguments passed to a command - will be collected into a sequence and given to the last positional argument. - - If overlap=True is given - then all the extra arguments will be mapped as if - they were values for the keyword arguments. - - If expand_short=True is given (default) - then short, one-letter options - will be expanded to a verbose ones, based of the comparison of the first - letter. If more then one option with the same first letter is given - then - only first one will be used in the expansion. + A decorator for defining commands in a declarative way. Provides + facilities for setting command's names and properties. + + Names should contain a set of names (aliases) by which the command + can be reached. If no names are given - the the native name (the one + extracted from the command handler) will be used. + + If include_native=True is given (default) and names is non-empty - + then the native name of the command will be prepended in addition to + the given names. + + If usage=True is given (default) - then command help will be + appended with autogenerated usage info, based of the command handler + arguments introspection. + + If source=True is given - then the first argument of the command + will receive the source arguments, as a raw, unprocessed string. The + further mapping of arguments and options will not be affected. + + If raw=True is given - then command considered to be raw and should + define positional arguments only. If it defines only one positional + argument - this argument will receive all the raw and unprocessed + arguments. If the command defines more then one positional argument + - then all the arguments except the last one will be processed + normally; the last argument will get what is left after the + processing as raw and unprocessed string. + + If empty=True is given - this will allow to call a raw command + without arguments. + + If extra=True is given - then all the extra arguments passed to a + command will be collected into a sequence and given to the last + positional argument. + + If overlap=True is given - then all the extra arguments will be + mapped as if they were values for the keyword arguments. + + If expand_short=True is given (default) - then short, one-letter + options will be expanded to a verbose ones, based of the comparison + of the first letter. If more then one option with the same first + letter is given - then only first one will be used in the expansion. """ names = list(names) @@ -300,34 +308,32 @@ def command(*names, **properties): def decorator(handler): """ - Decorator which receives handler as a first argument and then wraps it - in the command which then returns back. + Decorator which receives handler as a first argument and then + wraps it in the command which then returns back. """ command = Command(handler, *names, **properties) # Extract and inject a native name if either no other names are - # specified or include_native property is enabled, while making sure it - # is going to be the first one in the list. + # specified or include_native property is enabled, while making + # sure it is going to be the first one in the list. if not names or include_native: names.insert(0, command.native_name) command.names = tuple(names) return command - # Workaround if we are getting called without parameters. Keep in mind that - # in that case - first item in the names will be the handler. + # Workaround if we are getting called without parameters. Keep in + # mind that in that case - first item in the names will be the + # handler. if names and isinstance(names[0], FunctionType): return decorator(names.pop(0)) return decorator -def documentation(text): +def doc(text): """ - This decorator is used to bind a documentation (a help) to a command. - - Though this can be done easily by using doc-strings in a declarative and - Pythonic way - some of Gajim's developers are against it because of the - scaffolding needed to support the tranlation of such documentation. + This decorator is used to bind a documentation (a help) to a + command. """ def decorator(target): if isinstance(target, Command): diff --git a/src/command_system/implementation/__init__.py b/src/command_system/implementation/__init__.py index c77c23e3f..4e179f5ea 100644 --- a/src/command_system/implementation/__init__.py +++ b/src/command_system/implementation/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,6 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The implementation and auxilary systems which implement the standard Gajim -commands and also provide an infrastructure for adding custom commands. +The implementation and auxilary systems which implement the standard +Gajim commands and also provide an infrastructure for adding custom +commands. """ diff --git a/src/command_system/implementation/custom.py b/src/command_system/implementation/custom.py index e4aa32dbf..64b872e54 100644 --- a/src/command_system/implementation/custom.py +++ b/src/command_system/implementation/custom.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,21 +14,22 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The module contains examples of how to create your own commands, by creating a -new command container and definding a set of commands. +The module contains examples of how to create your own commands, by +creating a new command container and definding a set of commands. -Keep in mind that this module is not being loaded, so the code will not be -executed and commands defined here will not be detected. +Keep in mind that this module is not being loaded, so the code will not +be executed and commands defined here will not be detected. """ -from ..framework import CommandContainer, command, documentation +from ..framework import CommandContainer, command, doc from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands class CustomCommonCommands(CommandContainer): """ This command container bounds to all three available in the default - implementation command hosts. This means that commands defined in this - container will be available to all - chat, private chat and a group chat. + implementation command hosts. This means that commands defined in + this container will be available to all - chat, private chat and a + group chat. """ HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands) @@ -39,12 +40,13 @@ class CustomCommonCommands(CommandContainer): First line of the doc string is called a description and will be programmatically extracted and formatted. - After that you can give more help, like explanation of the options. This - one will be programatically extracted and formatted too. + After that you can give more help, like explanation of the + options. This one will be programatically extracted and + formatted too. - After all the documentation - there will be autogenerated (based on the - method signature) usage information appended. You can turn it off - though, if you want. + After all the documentation - there will be autogenerated (based + on the method signature) usage information appended. You can + turn it off though, if you want. """ return "I can't dance, you stupid fuck, I'm just a command system! A cool one, though..." @@ -56,15 +58,16 @@ class CustomChatCommands(CommandContainer): HOSTS = (ChatCommands,) - @documentation(_("The same as using a doc-string, except it supports translation")) + @doc(_("The same as using a doc-string, except it supports translation")) @command def sing(self): return "Are you phreaking kidding me? Buy yourself a damn stereo..." class CustomPrivateChatCommands(CommandContainer): """ - This command container bounds only to the PrivateChatCommands command host. - Therefore command defined here will be available only to a private chat. + This command container bounds only to the PrivateChatCommands + command host. Therefore command defined here will be available only + to a private chat. """ HOSTS = (PrivateChatCommands,) @@ -75,8 +78,9 @@ class CustomPrivateChatCommands(CommandContainer): class CustomGroupChatCommands(CommandContainer): """ - This command container bounds only to the GroupChatCommands command host. - Therefore command defined here will be available only to a group chat. + This command container bounds only to the GroupChatCommands command + host. Therefore command defined here will be available only to a + group chat. """ HOSTS = (GroupChatCommands,) diff --git a/src/command_system/implementation/hosts.py b/src/command_system/implementation/hosts.py index a90dab464..3624da2dc 100644 --- a/src/command_system/implementation/hosts.py +++ b/src/command_system/implementation/hosts.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,29 +14,29 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The module defines a set of command hosts, which are bound to a different -command processors, which are the source of commands. +The module defines a set of command hosts, which are bound to a +different command processors, which are the source of commands. """ from ..framework import CommandHost class ChatCommands(CommandHost): """ - This command host is bound to the command processor which processes commands - from a chat. + This command host is bound to the command processor which processes + commands from a chat. """ pass class PrivateChatCommands(CommandHost): """ - This command host is bound to the command processor which processes commands - from a private chat. + This command host is bound to the command processor which processes + commands from a private chat. """ pass class GroupChatCommands(CommandHost): """ - This command host is bound to the command processor which processes commands - from a group chat. + This command host is bound to the command processor which processes + commands from a group chat. """ pass diff --git a/src/command_system/implementation/middleware.py b/src/command_system/implementation/middleware.py index 2f262f8ed..ed158d5b4 100644 --- a/src/command_system/implementation/middleware.py +++ b/src/command_system/implementation/middleware.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,10 +14,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -Provides a glue to tie command system framework and the actual code where it -would be dropped in. Defines a little bit of scaffolding to support interaction -between the two and a few utility methods so you don't need to dig up the code -itself code to write basic commands. +Provides a glue to tie command system framework and the actual code +where it would be dropped in. Defines a little bit of scaffolding to +support interaction between the two and a few utility methods so you +don't need to dig up the code itself code to write basic commands. """ from types import StringTypes @@ -26,17 +26,18 @@ from traceback import print_exc from common import gajim from ..framework import CommandProcessor -from ..errors import CommandError +from ..errors import CommandError, NoCommandError class ChatCommandProcessor(CommandProcessor): """ - A basic scaffolding to provide convenient interaction between the command - system and chat controls. + A basic scaffolding to provide convenient interaction between the + command system and chat controls. """ def process_as_command(self, text): + self.command_succeeded = False flag = super(ChatCommandProcessor, self).process_as_command(text) - if flag: + if flag and self.command_succeeded: self.add_history(text) self.clear_input() return flag @@ -44,40 +45,49 @@ class ChatCommandProcessor(CommandProcessor): def execute_command(self, name, arguments): try: super(ChatCommandProcessor, self).execute_command(name, arguments) + except NoCommandError, error: + details = dict(name=error.name, message=error.message) + message = "%(name)s: %(message)s\n" % details + message += "Try using the //%(name)s or /say /%(name)s " % details + message += "construct if you intended to send it as a text." + self.echo(message, 'error') except CommandError, error: - self.echo("%s: %s" %(error.name, error.message), 'error') + self.echo("%s: %s" % (error.name, error.message), 'error') except Exception: self.echo("An error occured while trying to execute the command", 'error') print_exc() + else: + self.command_succeeded = True def looks_like_command(self, text, body, name, arguments): - # Command escape stuff ggoes here. If text was prepended by the command - # prefix twice, like //not_a_command (if prefix is set to /) then it - # will be escaped, that is sent just as a regular message with one (only - # one) prefix removed, so message will be /not_a_command. + # Command escape stuff ggoes here. If text was prepended by the + # command prefix twice, like //not_a_command (if prefix is set + # to /) then it will be escaped, that is sent just as a regular + # message with one (only one) prefix removed, so message will be + # /not_a_command. if body.startswith(self.COMMAND_PREFIX): self.send(body) return True def command_preprocessor(self, command, name, arguments, args, kwargs): - # If command argument contain h or help option - forward it to the /help - # command. Dont forget to pass self, as all commands are unbound. And - # also don't forget to print output. + # If command argument contain h or help option - forward it to + # the /help command. Dont forget to pass self, as all commands + # are unbound. And also don't forget to print output. if 'h' in kwargs or 'help' in kwargs: help = self.get_command('help') self.echo(help(self, name)) return True def command_postprocessor(self, command, name, arguments, args, kwargs, value): - # If command returns a string - print it to a user. A convenient and - # sufficient in most simple cases shortcut to a using echo. + # If command returns a string - print it to a user. A convenient + # and sufficient in most simple cases shortcut to a using echo. if value and isinstance(value, StringTypes): self.echo(value) class CommandTools: """ - Contains a set of basic tools and shortcuts you can use in your commands to - performe some simple operations. + Contains a set of basic tools and shortcuts you can use in your + commands to performe some simple operations. """ def echo(self, text, kind='info'): @@ -107,8 +117,8 @@ class CommandTools: def add_history(self, text): """ - Add given text to the input history, so user can scroll through it using - ctrl + up/down arrow keys. + Add given text to the input history, so user can scroll through + it using ctrl + up/down arrow keys. """ self.save_sent_message(text) diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py index 91bfbc8e9..af4ca8ded 100644 --- a/src/command_system/implementation/standard.py +++ b/src/command_system/implementation/standard.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -27,7 +27,7 @@ from common.exceptions import GajimGeneralException from common.logger import Constants from ..errors import CommandError -from ..framework import CommandContainer, command, documentation +from ..framework import CommandContainer, command, doc from ..mapping import generate_usage from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands @@ -38,25 +38,25 @@ lc = Constants() class StandardCommonCommands(CommandContainer): """ - This command container contains standard commands which are common to all - - chat, private chat, group chat. + This command container contains standard commands which are common + to all - chat, private chat, group chat. """ HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands) @command - @documentation(_("Clear the text window")) + @doc(_("Clear the text window")) def clear(self): self.conv_textview.clear() @command - @documentation(_("Hide the chat buttons")) + @doc(_("Hide the chat buttons")) def compact(self): new_status = not self.hide_chat_buttons self.chat_buttons_set_visible(new_status) @command(overlap=True) - @documentation(_("Show help on a given command or a list of available commands if -(-a)ll is given")) + @doc(_("Show help on a given command or a list of available commands if -(-a)ll is given")) def help(self, command=None, all=False): if command: command = self.get_command(command) @@ -83,17 +83,17 @@ class StandardCommonCommands(CommandContainer): self.echo(help(self, 'help')) @command(raw=True) - @documentation(_("Send a message to the contact")) + @doc(_("Send a message to the contact")) def say(self, message): self.send(message) @command(raw=True) - @documentation(_("Send action (in the third person) to the current chat")) + @doc(_("Send action (in the third person) to the current chat")) def me(self, action): self.send("/me %s" % action) @command('lastlog', overlap=True) - @documentation(_("Show logged messages which mention given text")) + @doc(_("Show logged messages which mention given text")) def grep(self, text, limit=None): results = gajim.logger.get_search_results_for_query(self.contact.jid, text, self.account) @@ -129,7 +129,7 @@ class StandardCommonCommands(CommandContainer): self.echo(formatted) @command(raw=True, empty=True) - @documentation(_(""" + @doc(_(""" Set current the status Status can be given as one of the following values: online, away, @@ -142,7 +142,7 @@ class StandardCommonCommands(CommandContainer): connection.change_status(status, message) @command(raw=True, empty=True) - @documentation(_("Set the current status to away")) + @doc(_("Set the current status to away")) def away(self, message): if not message: message = _("Away") @@ -150,35 +150,40 @@ class StandardCommonCommands(CommandContainer): connection.change_status('away', message) @command('back', raw=True, empty=True) - @documentation(_("Set the current status to online")) + @doc(_("Set the current status to online")) def online(self, message): if not message: message = _("Available") for connection in gajim.connections.itervalues(): connection.change_status('online', message) -class StandardChatCommands(CommandContainer): +class StandardCommonChatCommands(CommandContainer): """ - This command container contains standard command which are unique to a chat. + This command container contans standard commands, which are common + to a chat and a private chat only. """ - HOSTS = (ChatCommands,) + HOSTS = (ChatCommands, PrivateChatCommands) + + @command + @doc(_("Toggle the GPG encryption")) + def gpg(self): + self._toggle_gpg() @command - @documentation(_("Send a ping to the contact")) + @doc(_("Send a ping to the contact")) def ping(self): if self.account == gajim.ZEROCONF_ACC_NAME: raise CommandError(_('Command is not supported for zeroconf accounts')) gajim.connections[self.account].sendPing(self.contact) @command - @documentation(_("Sends DTMF events through an open audio session")) + @doc(_("Send DTMF events through an open audio session")) def dtmf(self, events): if not self.audio_sid: raise CommandError(_("There is no open audio session with this contact")) - # Valid values for DTMF tones are *, # or a number - events = [event for event in events - if event in ('*', '#') or event.isdigit()] + # Valid values for DTMF tones are *, # or a number. + events = [e for e in events if e in ('*', '#') or e.isdigit()] if events: session = gajim.connections[self.account].get_jingle_session( self.contact.get_full_jid(), self.audio_sid) @@ -188,10 +193,10 @@ class StandardChatCommands(CommandContainer): raise CommandError(_("No valid DTMF event specified")) @command - @documentation(_("Toggle audio session")) + @doc(_("Toggle audio session")) def audio(self): - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - raise CommandError(_("Video sessions are not available")) + if not self.audio_available: + raise CommandError(_("Audio sessions are not available")) else: # A state of an audio session is toggled by inverting a state of the # appropriate button. @@ -199,9 +204,9 @@ class StandardChatCommands(CommandContainer): self._audio_button.set_active(not state) @command - @documentation(_("Toggle video session")) + @doc(_("Toggle video session")) def video(self): - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: + if not self.video_available: raise CommandError(_("Video sessions are not available")) else: # A state of a video session is toggled by inverting a state of the @@ -209,24 +214,32 @@ class StandardChatCommands(CommandContainer): state = self._video_button.get_active() self._video_button.set_active(not state) +class StandardChatCommands(CommandContainer): + """ + This command container contains standard commands which are unique + to a chat. + """ + + HOSTS = (ChatCommands,) + class StandardPrivateChatCommands(CommandContainer): """ - This command container contains standard command which are unique to a - private chat. + This command container contains standard commands which are unique + to a private chat. """ HOSTS = (PrivateChatCommands,) -class StandardGroupchatCommands(CommandContainer): +class StandardGroupChatCommands(CommandContainer): """ - This command container contains standard command which are unique to a group - chat. + This command container contains standard commands which are unique + to a group chat. """ HOSTS = (GroupChatCommands,) @command(raw=True) - @documentation(_("Change your nickname in a group chat")) + @doc(_("Change your nickname in a group chat")) def nick(self, new_nick): try: new_nick = helpers.parse_resource(new_nick) @@ -236,7 +249,7 @@ class StandardGroupchatCommands(CommandContainer): self.new_nick = new_nick @command('query', raw=True) - @documentation(_("Open a private chat window with a specified occupant")) + @doc(_("Open a private chat window with a specified occupant")) def chat(self, nick): nicks = gajim.contacts.get_nick_list(self.account, self.room_jid) if nick in nicks: @@ -245,7 +258,7 @@ class StandardGroupchatCommands(CommandContainer): raise CommandError(_("Nickname not found")) @command('msg', raw=True) - @documentation(_("Open a private chat window with a specified occupant and send him a message")) + @doc(_("Open a private chat window with a specified occupant and send him a message")) def message(self, nick, a_message): nicks = gajim.contacts.get_nick_list(self.account, self.room_jid) if nick in nicks: @@ -254,7 +267,7 @@ class StandardGroupchatCommands(CommandContainer): raise CommandError(_("Nickname not found")) @command(raw=True, empty=True) - @documentation(_("Display or change a group chat topic")) + @doc(_("Display or change a group chat topic")) def topic(self, new_topic): if new_topic: self.connection.send_gc_subject(self.room_jid, new_topic) @@ -262,13 +275,13 @@ class StandardGroupchatCommands(CommandContainer): return self.subject @command(raw=True, empty=True) - @documentation(_("Invite a user to a room for a reason")) + @doc(_("Invite a user to a room for a reason")) def invite(self, jid, reason): self.connection.send_invite(self.room_jid, jid, reason) return _("Invited %s to %s") % (jid, self.room_jid) @command(raw=True, empty=True) - @documentation(_("Join a group chat given by a jid, optionally using given nickname")) + @doc(_("Join a group chat given by a jid, optionally using given nickname")) def join(self, jid, nick): if not nick: nick = self.nick @@ -285,12 +298,12 @@ class StandardGroupchatCommands(CommandContainer): pass @command('part', 'close', raw=True, empty=True) - @documentation(_("Leave the groupchat, optionally giving a reason, and close tab or window")) + @doc(_("Leave the groupchat, optionally giving a reason, and close tab or window")) def leave(self, reason): self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND, reason) @command(raw=True, empty=True) - @documentation(_(""" + @doc(_(""" Ban user by a nick or a jid from a groupchat If given nickname is not found it will be treated as a jid. @@ -302,14 +315,14 @@ class StandardGroupchatCommands(CommandContainer): self.connection.gc_set_affiliation(self.room_jid, who, 'outcast', reason or str()) @command(raw=True, empty=True) - @documentation(_("Kick user by a nick from a groupchat")) + @doc(_("Kick user by a nick from a groupchat")) def kick(self, who, reason): if not who in gajim.contacts.get_nick_list(self.account, self.room_jid): raise CommandError(_("Nickname not found")) self.connection.gc_set_role(self.room_jid, who, 'none', reason or str()) @command - @documentation(_("Display names of all group chat occupants")) + @doc(_("Display names of all group chat occupants")) def names(self, verbose=False): get_contact = lambda nick: gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) nicks = gajim.contacts.get_nick_list(self.account, self.room_jid) @@ -330,11 +343,11 @@ class StandardGroupchatCommands(CommandContainer): return ', '.join(nicks) @command('ignore', raw=True) - @documentation(_("Forbid an occupant to send you public or private messages")) + @doc(_("Forbid an occupant to send you public or private messages")) def block(self, who): self.on_block(None, who) @command('unignore', raw=True) - @documentation(_("Allow an occupant to send you public or private messages")) + @doc(_("Allow an occupant to send you public or private messages")) def unblock(self, who): self.on_unblock(None, who) diff --git a/src/command_system/mapping.py b/src/command_system/mapping.py index ecf8f0783..8f7a31470 100644 --- a/src/command_system/mapping.py +++ b/src/command_system/mapping.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com> +# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # # 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 @@ -14,17 +14,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The module contains routines to parse command arguments and map them to the -command handler's positonal and keyword arguments. +The module contains routines to parse command arguments and map them to +the command handler's positonal and keyword arguments. -Mapping is done in two stages: 1) parse arguments into positional arguments and -options; 2) adapt them to the specific command handler according to the command -properties. +Mapping is done in two stages: 1) parse arguments into positional +arguments and options; 2) adapt them to the specific command handler +according to the command properties. """ import re from types import BooleanType, UnicodeType -from types import TupleType, ListType from operator import itemgetter from errors import DefinitionError, CommandError @@ -34,28 +33,32 @@ from errors import DefinitionError, CommandError ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)') OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?') -# Option keys needs to be encoded to a specific encoding as Python does not -# allow to expand dictionary with raw unicode strings as keys from a **kwargs. +# Option keys needs to be encoded to a specific encoding as Python does +# not allow to expand dictionary with raw unicode strings as keys from a +# **kwargs. KEY_ENCODING = 'UTF-8' -# Defines how complete representation of command usage (generated based on -# command handler argument specification) will be rendered. +# Defines how complete representation of command usage (generated based +# on command handler argument specification) will be rendered. USAGE_PATTERN = 'Usage: %s %s' def parse_arguments(arguments): """ - Simple yet effective and sufficient in most cases parser which parses - command arguments and returns them as two lists. + Simple yet effective and sufficient in most cases parser which + parses command arguments and returns them as two lists. - First list represents positional arguments as (argument, position), and - second representing options as (key, value, position) tuples, where position - is a (start, end) span tuple of where it was found in the string. + First list represents positional arguments as (argument, position), + and second representing options as (key, value, position) tuples, + where position is a (start, end) span tuple of where it was found in + the string. - Options may be given in --long or -short format. As --option=value or - --option value or -option value. Keys without values will get None as value. + Options may be given in --long or -short format. As --option=value + or --option value or -option value. Keys without values will get + None as value. - Arguments and option values that contain spaces may be given as 'one two - three' or "one two three"; that is between single or double quotes. + Arguments and option values that contain spaces may be given as 'one + two three' or "one two three"; that is between single or double + quotes. """ args, opts = [], [] @@ -90,14 +93,16 @@ def parse_arguments(arguments): position = match.span() args.append((body, position)) - # Primitive but sufficiently effective way of disposing of conflicted - # sectors. Remove any arguments that intersect with options. + # Primitive but sufficiently effective way of disposing of + # conflicted sectors. Remove any arguments that intersect with + # options. for arg, position in args[:]: if intersects_opts(position): args.remove((arg, position)) - # Primitive but sufficiently effective way of disposing of conflicted - # sectors. Remove any options that intersect with arguments. + # Primitive but sufficiently effective way of disposing of + # conflicted sectors. Remove any options that intersect with + # arguments. for key, value, position in opts[:]: if intersects_args(position): opts.remove((key, value, position)) @@ -106,41 +111,40 @@ def parse_arguments(arguments): def adapt_arguments(command, arguments, args, opts): """ - Adapt args and opts got from the parser to a specific handler by means of - arguments specified on command definition. That is transform them to *args - and **kwargs suitable for passing to a command handler. - - Dashes (-) in the option names will be converted to underscores. So you can - map --one-more-option to a one_more_option=None. - - If the initial value of a keyword argument is a boolean (False in most - cases) - then this option will be treated as a switch, that is an option - which does not take an argument. If a switch is followed by an argument - - then this argument will be treated just like a normal positional argument. - - If the initial value of a keyword argument is a sequence, that is a tuple or - list - then a value of this option will be considered correct only if it is - present in the sequence. + Adapt args and opts got from the parser to a specific handler by + means of arguments specified on command definition. That is + transform them to *args and **kwargs suitable for passing to a + command handler. + + Dashes (-) in the option names will be converted to underscores. So + you can map --one-more-option to a one_more_option=None. + + If the initial value of a keyword argument is a boolean (False in + most cases) - then this option will be treated as a switch, that is + an option which does not take an argument. If a switch is followed + by an argument - then this argument will be treated just like a + normal positional argument. """ spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification() norm_kwargs = dict(spec_kwargs) - # Quite complex piece of neck-breaking logic to extract raw arguments if - # there is more, then one positional argument specified by the command. In - # case if it's just one argument which is the collector - this is fairly - # easy. But when it's more then one argument - the neck-breaking logic of - # how to retrieve residual arguments as a raw, all in one piece string, - # kicks in. + # Quite complex piece of neck-breaking logic to extract raw + # arguments if there is more, then one positional argument specified + # by the command. In case if it's just one argument which is the + # collector - this is fairly easy. But when it's more then one + # argument - the neck-breaking logic of how to retrieve residual + # arguments as a raw, all in one piece string, kicks in. if command.raw: if arguments: spec_fix = 1 if command.source else 0 spec_len = len(spec_args) - spec_fix arguments_end = len(arguments) - 1 - # If there are any optional arguments given they should be either an - # unquoted postional argument or part of the raw argument. So we - # find all optional arguments that can possibly be unquoted argument - # and append them as is to the args. + # If there are any optional arguments given they should be + # either an unquoted postional argument or part of the raw + # argument. So we find all optional arguments that can + # possibly be unquoted argument and append them as is to the + # args. for key, value, (start, end) in opts[:spec_len]: if value: end -= len(value) + 1 @@ -149,9 +153,9 @@ def adapt_arguments(command, arguments, args, opts): else: args.append((arguments[start:end], (start, end))) - # We need in-place sort here because after manipulations with - # options order of arguments might be wrong and we just can't have - # more complex logic to not let that happen. + # We need in-place sort here because after manipulations + # with options order of arguments might be wrong and we just + # can't have more complex logic to not let that happen. args.sort(key=itemgetter(1)) if spec_len > 1: @@ -160,27 +164,28 @@ def adapt_arguments(command, arguments, args, opts): except IndexError: raise CommandError("Missing arguments", command) - # The essential point of the whole play. After boundaries are - # being determined (supposingly correct) we separate raw part - # from the rest of arguments, which should be normally - # processed. + # The essential point of the whole play. After + # boundaries are being determined (supposingly correct) + # we separate raw part from the rest of arguments, which + # should be normally processed. raw = arguments[end:] raw = raw.strip() or None if not raw and not command.empty: raise CommandError("Missing arguments", command) - # Discard residual arguments and all of the options as raw - # command does not support options and if an option is given it - # is rather a part of a raw argument. + # Discard residual arguments and all of the options as + # raw command does not support options and if an option + # is given it is rather a part of a raw argument. args = args[:spec_len - 1] opts = [] args.append((raw, (end, arguments_end))) else: - # Substitue all of the arguments with only one, which contain - # raw and unprocessed arguments as a string. And discard all the - # options, as raw command does not support them. + # Substitue all of the arguments with only one, which + # contain raw and unprocessed arguments as a string. And + # discard all the options, as raw command does not + # support them. args = [(arguments, (0, arguments_end))] opts = [] else: @@ -189,16 +194,17 @@ def adapt_arguments(command, arguments, args, opts): else: raise CommandError("Missing arguments", command) - # The first stage of transforming options we have got to a format that can - # be used to associate them with declared keyword arguments. Substituting - # dashes (-) in their names with underscores (_). + # The first stage of transforming options we have got to a format + # that can be used to associate them with declared keyword + # arguments. Substituting dashes (-) in their names with + # underscores (_). for index, (key, value, position) in enumerate(opts): if '-' in key: opts[index] = (key.replace('-', '_'), value, position) # The second stage of transforming options to an associatable state. - # Expanding short, one-letter options to a verbose ones, if corresponding - # optin has been given. + # Expanding short, one-letter options to a verbose ones, if + # corresponding optin has been given. if command.expand_short: expanded = [] for spec_key, spec_value in norm_kwargs.iteritems(): @@ -210,26 +216,27 @@ def adapt_arguments(command, arguments, args, opts): opts[index] = (spec_key, value, position) break - # Detect switches and set their values accordingly. If any of them carries a - # value - append it to args. + # Detect switches and set their values accordingly. If any of them + # carries a value - append it to args. for index, (key, value, position) in enumerate(opts): if isinstance(norm_kwargs.get(key), BooleanType): opts[index] = (key, True, position) if value: args.append((value, position)) - # Sorting arguments and options (just to be sure) in regarding to their - # positions in the string. + # Sorting arguments and options (just to be sure) in regarding to + # their positions in the string. args.sort(key=itemgetter(1)) opts.sort(key=itemgetter(2)) - # Stripping down position information supplied with arguments and options as - # it won't be needed again. + # Stripping down position information supplied with arguments and + # options as it won't be needed again. args = map(lambda (arg, position): arg, args) opts = map(lambda (key, value, position): (key, value), opts) - # If command has extra option enabled - collect all extra arguments and pass - # them to a last positional argument command defines as a list. + # If command has extra option enabled - collect all extra arguments + # and pass them to a last positional argument command defines as a + # list. if command.extra: if not var_args: spec_fix = 1 if not command.source else 2 @@ -240,9 +247,9 @@ def adapt_arguments(command, arguments, args, opts): else: raise DefinitionError("Can not have both, extra and *args") - # Detect if positional arguments overlap keyword arguments. If so and this - # is allowed by command options - then map them directly to their options, - # so they can get propert further processings. + # Detect if positional arguments overlap keyword arguments. If so + # and this is allowed by command options - then map them directly to + # their options, so they can get propert further processings. spec_fix = 1 if command.source else 0 spec_len = len(spec_args) - spec_fix if len(args) > spec_len: @@ -262,49 +269,32 @@ def adapt_arguments(command, arguments, args, opts): if not isinstance(value, BooleanType): raise CommandError("%s: Switch can not take an argument" % key, command) - # Detect every sequence constraint and ensure that if corresponding options - # are given - they contain proper values, within the constraint range. - for key, value in opts: - initial = norm_kwargs.get(key) - if isinstance(initial, (TupleType, ListType)): - if value not in initial: - raise CommandError("%s: Invalid argument" % key, command) - - # If argument to an option constrained by a sequence was not given - then - # it's value should be set to None. - for spec_key, spec_value in spec_kwargs: - if isinstance(spec_value, (TupleType, ListType)): - for key, value in opts: - if spec_key == key: - break - else: - opts.append((spec_key, None)) - - # We need to encode every keyword argument to a simple string, not the - # unicode one, because ** expansion does not support it. + # We need to encode every keyword argument to a simple string, not + # the unicode one, because ** expansion does not support it. for index, (key, value) in enumerate(opts): if isinstance(key, UnicodeType): opts[index] = (key.encode(KEY_ENCODING), value) - # Inject the source arguments as a string as a first argument, if command - # has enabled the corresponding option. + # Inject the source arguments as a string as a first argument, if + # command has enabled the corresponding option. if command.source: args.insert(0, arguments) - # Return *args and **kwargs in the form suitable for passing to a command - # handler and being expanded. + # Return *args and **kwargs in the form suitable for passing to a + # command handler and being expanded. return tuple(args), dict(opts) def generate_usage(command, complete=True): """ - Extract handler's arguments specification and wrap them in a human-readable - format usage information. If complete is given - then USAGE_PATTERN will be - used to render the specification completly. + Extract handler's arguments specification and wrap them in a + human-readable format usage information. If complete is given - then + USAGE_PATTERN will be used to render the specification completly. """ spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification() - # Remove some special positional arguments from the specifiaction, but store - # their names so they can be used for usage info generation. + # Remove some special positional arguments from the specifiaction, + # but store their names so they can be used for usage info + # generation. sp_source = spec_args.pop(0) if command.source else None sp_extra = spec_args.pop() if command.extra else None @@ -317,8 +307,6 @@ def generate_usage(command, complete=True): if isinstance(value, BooleanType): value = str() - elif isinstance(value, (TupleType, ListType)): - value = '={%s}' % ', '.join(value) else: value = '=%s' % value @@ -350,8 +338,8 @@ def generate_usage(command, complete=True): if var_kwargs: usage += (' ' if args else str()) + '[[%s]]' % var_kwargs - # Native name will be the first one if it is included. Otherwise, names will - # be in the order they were specified. + # Native name will be the first one if it is included. Otherwise, + # names will be in the order they were specified. if len(command.names) > 1: names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:])) else: diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py index a5c0c0559..32c1d4452 100644 --- a/src/common/GnuPG.py +++ b/src/common/GnuPG.py @@ -1,9 +1,8 @@ ## src/common/GnuPG.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de> ## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org> ## Jonathan Schleifer <js-gajim AT webkeks.org> diff --git a/src/common/GnuPGInterface.py b/src/common/GnuPGInterface.py index f7f5cb522..4b41a65da 100644 --- a/src/common/GnuPGInterface.py +++ b/src/common/GnuPGInterface.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2001 Frank J. Tobin <ftobin AT neverending.org> ## Copyright (C) 2005 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org> ## ## This file is part of Gajim. diff --git a/src/common/atom.py b/src/common/atom.py index 12101b886..e8531274b 100644 --- a/src/common/atom.py +++ b/src/common/atom.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> ## Tomasz Melcer <liori AT exroot.org> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## diff --git a/src/common/caps_cache.py b/src/common/caps_cache.py index 23c54c279..af69c8aeb 100644 --- a/src/common/caps_cache.py +++ b/src/common/caps_cache.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org> ## Travis Shirk <travis AT pobox.com> -## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> ## Jonathan Schleifer <js-gajim AT webkeks.org> ## Copyright (C) 2008-2009 Stephan Erb <steve-e AT h3c.de> diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 5b18ac480..00a50e6fe 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2005-2006 Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org> ## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org> @@ -129,7 +129,9 @@ def split_db(): print 'spliting database' if os.name == 'nt': try: - OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') + import configpaths + OLD_LOG_DB_FOLDER = os.path.join(configpaths.fse( + os.environ[u'appdata']), u'Gajim') except KeyError: OLD_LOG_DB_FOLDER = u'.' else: @@ -184,7 +186,8 @@ def check_and_possibly_move_config(): if os.name == 'nt': try: - OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') + OLD_LOG_DB_FOLDER = os.path.join(configpaths.fse( + os.environ[u'appdata']), u'Gajim') except KeyError: OLD_LOG_DB_FOLDER = u'.' else: @@ -340,5 +343,10 @@ def check_and_possibly_create_paths(): sys.exit() def create_path(directory): + head, tail = os.path.split(directory) + if not os.path.exists(head): + create_path(head) + if os.path.exists(directory): + return print _('creating %s directory') % directory os.mkdir(directory, 0700) diff --git a/src/common/commands.py b/src/common/commands.py index 88f9f3332..d303de922 100644 --- a/src/common/commands.py +++ b/src/common/commands.py @@ -1,8 +1,8 @@ # -*- coding:utf-8 -*- ## src/common/commands.py ## -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> -## Tomasz Melcer <liori AT exroot.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> ## Stephan Erb <steve-e AT h3c.de> @@ -290,6 +290,41 @@ class ForwardMessagesCommand(AdHocCommand): return False # finish the session +class FwdMsgThenDisconnectCommand(AdHocCommand): + commandnode = 'fwd-msd-disconnect' + commandname = _('Forward unread message then disconnect') + + @staticmethod + def isVisibleFor(samejid): + """ + Change status is visible only if the entity has the same bare jid + """ + return samejid + + def execute(self, request): + account = self.connection.name + # Forward messages + events = gajim.events.get_events(account, types=['chat', 'normal']) + j, resource = gajim.get_room_and_nick_from_fjid(self.jid) + for jid in events: + for event in events[jid]: + self.connection.send_message(j, event.parameters[0], '', + type_=event.type_, subject=event.parameters[1], + resource=resource, forward_from=jid, delayed=event.time_, + now=True) + + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('The status has been changed.')) + + # if going offline, we need to push response so it won't go into + # queue and disappear + self.connection.connection.send(response, now = True) + + # send new status + gajim.interface.roster.send_status(self.connection.name, 'offline', '') + # finish the session + return False + class ConnectionCommands: """ This class depends on that it is a part of Connection() class @@ -299,7 +334,7 @@ class ConnectionCommands: # a list of all commands exposed: node -> command class self.__commands = {} for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand, - LeaveGroupchatsCommand): + LeaveGroupchatsCommand, FwdMsgThenDisconnectCommand): self.__commands[cmdobj.commandnode] = cmdobj # a list of sessions; keys are tuples (jid, sessionid, node) diff --git a/src/common/config.py b/src/common/config.py index 383a289a5..adcd9572a 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/common/config.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005 Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> @@ -277,6 +277,8 @@ class Config: 'audio_output_device': [opt_str, 'autoaudiosink'], 'video_input_device': [opt_str, 'autovideosrc ! videoscale ! ffmpegcolorspace'], 'video_output_device': [opt_str, 'autovideosink'], + 'video_framerate': [opt_str, '', _('Optionally fix jingle output video framerate. Example: 10/1 or 25/2')], + 'video_size': [opt_str, '', _('Optionally resize jingle output video. Example: 320x240')], 'audio_input_volume': [opt_int, 50], 'audio_output_volume': [opt_int, 50], 'use_stun_server': [opt_bool, True, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')], @@ -289,6 +291,7 @@ class Config: 'name': [ opt_str, '', '', True ], 'hostname': [ opt_str, '', '', True ], 'anonymous_auth': [ opt_bool, False ], + 'client_cert': [ opt_str, '', '', True ], 'savepass': [ opt_bool, False ], 'password': [ opt_str, '' ], 'resource': [ opt_str, 'gajim', '', True ], @@ -315,6 +318,7 @@ class Config: 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')], 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ], 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ], + 'warn_when_insecure_password': [ opt_bool, True, _('Show a warning dialog before sending PLAIN password over a plain conenction.') ], 'ssl_fingerprint_sha1': [ opt_str, '', '', True ], 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ], 'use_srv': [ opt_bool, True, '', True ], @@ -451,8 +455,8 @@ class Config: 'urgency_hint': [opt_bool, False], }, {}), 'plugins': ({ - 'active': [opt_bool, False, _('State whether plugins should be activated on exit (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')], - }, {}), + 'active': [opt_bool, False, _('State whether plugins should be activated on exit (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')], + },{}), } statusmsg_default = { diff --git a/src/common/configpaths.py b/src/common/configpaths.py index 5b542e5a2..74027994d 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> ## Junglecow J <junglecow AT gmail.com> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## @@ -157,7 +157,7 @@ class ConfigPaths: self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons'))) self.add('HOME', None, fse(os.path.expanduser('~'))) self.add('PLUGINS_BASE', None, os.path.join(basedir, - windowsify(u'plugins'))) + windowsify(u'plugins'))) try: self.add('TMP', None, fse(tempfile.gettempdir())) except IOError, e: diff --git a/src/common/connection.py b/src/common/connection.py index 3ba5a2311..0e281d5a7 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -2,7 +2,7 @@ ## src/common/connection.py ## ## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org> -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net> ## Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> @@ -135,6 +135,9 @@ class CommonConnection: self.blocked_groups = [] self.blocked_all = False + self.seclabel_supported = False + self.seclabel_catalogues = {} + self.pep_supported = False self.pep = {} # Do we continue connection when we get roster (send presence,get vcard..) @@ -154,6 +157,8 @@ class CommonConnection: self.muc_jid = {} # jid of muc server for each transport type self._stun_servers = [] # STUN servers of our jabber server + self.awaiting_cids = {} # Used for XEP-0231 + self.get_config_values_or_default() def _compute_resource(self): @@ -239,7 +244,7 @@ class CommonConnection: def _prepare_message(self, jid, msg, keyID, type_='chat', subject='', chatstate=None, msg_id=None, composing_xep=None, resource=None, user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None): + label=None, original_message=None, delayed=None, callback=None): if not self.connection or self.connected < 2: return 1 try: @@ -285,33 +290,37 @@ class CommonConnection: gajim.thread_interface(encrypt_thread, [msg, keyID, True], _on_encrypted, []) else: - self._message_encrypted_cb(output, type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, - subject, chatstate, composing_xep, forward_from, - delayed, session, form_node, user_nick, keyID, - callback) + self._message_encrypted_cb(output, type_, msg, + msgtxt, original_message, fjid, resource, + jid, xhtml, subject, chatstate, msg_id, + composing_xep, label, forward_from, delayed, + session, form_node, user_nick, keyID, + callback) self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) else: self._message_encrypted_cb(output, type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, subject, - chatstate, composing_xep, forward_from, delayed, session, - form_node, user_nick, keyID, callback) + original_message, fjid, resource, jid, xhtml, + subject, chatstate, msg_id, composing_xep, label, + forward_from, delayed, session, form_node, + user_nick, keyID, callback) gajim.thread_interface(encrypt_thread, [msg, keyID, False], _on_encrypted, []) return self._message_encrypted_cb(('', error), type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, subject, chatstate, - composing_xep, forward_from, delayed, session, form_node, user_nick, - keyID, callback) + original_message, fjid, resource, jid, xhtml, subject, + chatstate, msg_id, composing_xep, label, forward_from, delayed, + session, form_node, user_nick, keyID, callback) self._on_continue_message(type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, - forward_from, delayed, session, form_node, user_nick, callback) - - def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message, - fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from, - delayed, session, form_node, user_nick, keyID, callback): + resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id, + composing_xep, label, forward_from, delayed, session, form_node, + user_nick, callback) + + def _message_encrypted_cb(self, output, type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id, + composing_xep, label, forward_from, delayed, session, form_node, user_nick, + keyID, callback): msgenc, error = output if msgenc and not error: @@ -321,18 +330,19 @@ class CommonConnection: # one in locale and one en msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ ' (' + msgtxt + ')' - self._on_continue_message(type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, - composing_xep, forward_from, delayed, session, form_node, user_nick, - callback) + self._on_continue_message(type_, msg, msgtxt, original_message, + fjid, resource, jid, xhtml, subject, msgenc, keyID, + chatstate, msg_id, composing_xep, label, forward_from, delayed, + session, form_node, user_nick, callback) return # Encryption failed, do not send message tim = localtime() self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, - forward_from, delayed, session, form_node, user_nick, callback): + resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id, + composing_xep, label, forward_from, delayed, session, form_node, user_nick, + callback): if type_ == 'chat': msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, xhtml=xhtml) @@ -343,11 +353,17 @@ class CommonConnection: else: msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', xhtml=xhtml) + + if msg_id: + msg_iq.setID(msg_id) + if msgenc: msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) if form_node: msg_iq.addChild(node=form_node) + if label: + msg_iq.addChild(node=label) # XEP-0172: user_nickname if user_nick: @@ -430,13 +446,15 @@ class CommonConnection: try: gajim.logger.write(kind, jid, log_msg) except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + self.dispatch('DB_ERROR', (_('Disk Write Error'), + str(e))) except exceptions.DatabaseMalformed: pritext = _('Database Error') sectext = _('The database file (%s) cannot be read. Try to ' 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' ' or remove it (all history will be lost).') % \ common.logger.LOG_DB_PATH + self.dispatch('DB_ERROR', (pritext, sectext)) def ack_subscribed(self, jid): """ @@ -507,11 +525,24 @@ class CommonConnection: def account_changed(self, new_name): self.name = new_name - def request_last_status_time(self, jid, resource): + def request_last_status_time(self, jid, resource, groupchat_jid=None): """ - To be implemented by derivated classes + groupchat_jid is used when we want to send a request to a real jid and + act as if the answer comes from the groupchat_jid """ - raise NotImplementedError + if not gajim.account_is_connected(self.name): + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ + common.xmpp.NS_LAST) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.last_ids.append(id_) + self.connection.send(iq) def request_os_info(self, jid, resource): """ @@ -686,6 +717,8 @@ class Connection(CommonConnection, ConnectionHandlers): 'ping_alive_every_foo_secs') else: self.pingalives = 0 + self.client_cert = gajim.config.get_per('accounts', self.name, + 'client_cert') def check_jid(self, jid): return helpers.parse_jid(jid) @@ -975,9 +1008,10 @@ class Connection(CommonConnection, ConnectionHandlers): proxy['port'] = 3128 if len(login) == 2: - proxy['password'] = login[1] + proxy['pass'] = login[1] + proxy['useauth'] = True else: - proxy['password'] = u'' + proxy['pass'] = u'' except Exception: proxy = None @@ -1284,8 +1318,6 @@ class Connection(CommonConnection, ConnectionHandlers): self.on_connect_auth(con) self.on_connect_auth = None else: - # Forget password, it's wrong - self.password = None gajim.log.debug("Couldn't authenticate to %s" % self._hostname) self.disconnect(on_purpose = True) self.dispatch('STATUS', 'offline') @@ -1302,7 +1334,7 @@ class Connection(CommonConnection, ConnectionHandlers): stanza.setAttr('xml:lang', self.lang) def get_privacy_lists(self): - if not self.connection: + if not gajim.account_is_connected(self.name): return common.xmpp.features_nb.getPrivacyLists(self.connection) @@ -1321,7 +1353,7 @@ class Connection(CommonConnection, ConnectionHandlers): Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to server to detect connection failure at application level """ - if not self.connection: + if not gajim.account_is_connected(self.name): return id_ = self.connection.getAnID() if pingTo: @@ -1349,12 +1381,12 @@ class Connection(CommonConnection, ConnectionHandlers): 'accounts', self.name, 'time_for_ping_alive_answer')) def get_active_default_lists(self): - if not self.connection: + if not gajim.account_is_connected(self.name): return common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection) def del_privacy_list(self, privacy_list): - if not self.connection: + if not gajim.account_is_connected(self.name): return def _on_del_privacy_list_result(result): if result: @@ -1368,22 +1400,22 @@ class Connection(CommonConnection, ConnectionHandlers): _on_del_privacy_list_result) def get_privacy_list(self, title): - if not self.connection: + if not gajim.account_is_connected(self.name): return common.xmpp.features_nb.getPrivacyList(self.connection, title) def set_privacy_list(self, listname, tags): - if not self.connection: + if not gajim.account_is_connected(self.name): return common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags) def set_active_list(self, listname): - if not self.connection: + if not gajim.account_is_connected(self.name): return common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active') def set_default_list(self, listname): - if not self.connection: + if not gajim.account_is_connected(self.name): return common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname) @@ -1426,14 +1458,14 @@ class Connection(CommonConnection, ConnectionHandlers): """ Activate a privacy rule """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') iq.getTag('query').setTag('active', {'name': name}) self.connection.send(iq) def send_invisible_presence(self, msg, signed, initial = False): - if not self.connection: + if not gajim.account_is_connected(self.name): return if not self.privacy_rules_supported: self.dispatch('STATUS', gajim.SHOW_LIST[self.connected]) @@ -1504,7 +1536,7 @@ class Connection(CommonConnection, ConnectionHandlers): def _discover_server_at_connection(self, con): self.connection = con - if not self.connection: + if not gajim.account_is_connected(self.name): return self.connection.set_send_timeout(self.keepalives, self.send_keepalive) self.connection.set_send_timeout2(self.pingalives, self.sendPing) @@ -1530,7 +1562,7 @@ class Connection(CommonConnection, ConnectionHandlers): def send_custom_status(self, show, msg, jid): if not show in gajim.SHOW_LIST: return -1 - if not self.connection: + if not gajim.account_is_connected(self.name): return sshow = helpers.get_xmpp_show(show) if not msg: @@ -1578,21 +1610,22 @@ class Connection(CommonConnection, ConnectionHandlers): self.dispatch('STATUS', show) def send_motd(self, jid, subject = '', msg = '', xhtml = None): - if not self.connection: + if not gajim.account_is_connected(self.name): return msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject, xhtml = xhtml) self.connection.send(msg_iq) - def send_message(self, jid, msg, keyID, type_='chat', subject='', + def send_message(self, jid, msg, keyID=None, type_='chat', subject='', chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None, callback_args=[]): + user_nick=None, xhtml=None, label=None, session=None, forward_from=None, + form_node=None, original_message=None, delayed=None, callback=None, + callback_args=[], now=False): - def cb(jid, msg, keyID, forward_from, session, original_message, subject, - type_, msg_iq): - msg_id = self.connection.send(msg_iq) + def cb(jid, msg, keyID, forward_from, session, original_message, + subject, type_, msg_iq): + msg_id = self.connection.send(msg_iq, now=now) jid = helpers.parse_jid(jid) self.dispatch('MSGSENT', (jid, msg, keyID)) if callback: @@ -1602,16 +1635,16 @@ class Connection(CommonConnection, ConnectionHandlers): subject, type_) self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, - forward_from=forward_from, form_node=form_node, - original_message=original_message, delayed=delayed, callback=cb) + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=user_nick, xhtml=xhtml, label=label, + session=session, forward_from=forward_from, form_node=form_node, + original_message=original_message, delayed=delayed, callback=cb) def send_contacts(self, contacts, jid): """ Send contacts with RosterX (Xep-0144) """ - if not self.connection: + if not gajim.account_is_connected(self.name): return if len(contacts) == 1: msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), @@ -1637,14 +1670,14 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(stanza) def ack_subscribed(self, jid): - if not self.connection: + if not gajim.account_is_connected(self.name): return log.debug('ack\'ing subscription complete for %s' % jid) p = common.xmpp.Presence(jid, 'subscribe') self.connection.send(p) def ack_unsubscribed(self, jid): - if not self.connection: + if not gajim.account_is_connected(self.name): return log.debug('ack\'ing unsubscription complete for %s' % jid) p = common.xmpp.Presence(jid, 'unsubscribe') @@ -1652,7 +1685,7 @@ class Connection(CommonConnection, ConnectionHandlers): def request_subscription(self, jid, msg='', name='', groups=[], auto_auth=False, user_nick=''): - if not self.connection: + if not gajim.account_is_connected(self.name): return log.debug('subscription request for %s' % jid) if auto_auth: @@ -1677,21 +1710,21 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(p) def send_authorization(self, jid): - if not self.connection: + if not gajim.account_is_connected(self.name): return p = common.xmpp.Presence(jid, 'subscribed') p = self.add_sha(p) self.connection.send(p) def refuse_authorization(self, jid): - if not self.connection: + if not gajim.account_is_connected(self.name): return p = common.xmpp.Presence(jid, 'unsubscribed') p = self.add_sha(p) self.connection.send(p) def unsubscribe(self, jid, remove_auth = True): - if not self.connection: + if not gajim.account_is_connected(self.name): return if remove_auth: self.connection.getRoster().delItem(jid) @@ -1704,7 +1737,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.update_contact(jid, '', []) def unsubscribe_agent(self, agent): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) iq.getTag('query').setTag('remove') @@ -1754,31 +1787,12 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection = con common.xmpp.features_nb.getRegInfo(con, self._hostname) - def request_last_status_time(self, jid, resource, groupchat_jid=None): - """ - groupchat_jid is used when we want to send a request to a real jid and - act as if the answer comes from the groupchat_jid - """ - if not self.connection: - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ - common.xmpp.NS_LAST) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.last_ids.append(id_) - self.connection.send(iq) - def request_os_info(self, jid, resource, groupchat_jid=None): """ groupchat_jid is used when we want to send a request to a real jid and act as if the answer comes from the groupchat_jid """ - if not self.connection: + if not gajim.account_is_connected(self.name): return # If we are invisible, do not request if self.connected == gajim.SHOW_LIST.index('invisible'): @@ -1801,7 +1815,7 @@ class Connection(CommonConnection, ConnectionHandlers): groupchat_jid is used when we want to send a request to a real jid and act as if the answer comes from the groupchat_jid """ - if not self.connection: + if not gajim.account_is_connected(self.name): return # If we are invisible, do not request if self.connected == gajim.SHOW_LIST.index('invisible'): @@ -1823,15 +1837,24 @@ class Connection(CommonConnection, ConnectionHandlers): """ Get Gajim settings as described in XEP 0049 """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ='get') iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) iq2.addChild(name='gajim', namespace='gajim:prefs') self.connection.send(iq) + def seclabel_catalogue(self, to, callback): + if not gajim.account_is_connected(self.name): + return + self.seclabel_catalogue_request(to, callback) + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='catalog', namespace=common.xmpp.NS_SECLABEL_CATALOG) + iq2.setAttr('to', to) + self.connection.send(iq) + def _request_bookmarks_xml(self): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ='get') iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) @@ -1849,7 +1872,7 @@ class Connection(CommonConnection, ConnectionHandlers): storage_type can be set to xml to force request to xml storage """ - if not self.connection: + if not gajim.account_is_connected(self.name): return if self.pubsub_supported and storage_type != 'xml': self.send_pb_retrieve('', 'storage:bookmarks') @@ -1866,7 +1889,7 @@ class Connection(CommonConnection, ConnectionHandlers): storage_type can be set to 'pubsub' or 'xml' so store in only one method else it will be stored on both """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'}) for bm in self.bookmarks: @@ -1885,21 +1908,19 @@ class Connection(CommonConnection, ConnectionHandlers): if bm.get('print_status', None): iq2.setTagData('print_status', bm['print_status']) - if self.pubsub_supported and storage_type != 'xml': - if self.pubsub_publish_options_supported: - options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', - attrs={'type': 'submit'}) - f = options.addChild('field', attrs={'var': 'FORM_TYPE', - 'type': 'hidden'}) - f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) - f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) - f.setTagData('value', 'true') - f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) - f.setTagData('value', 'whitelist') - else: - options = None + if self.pubsub_supported and self.pubsub_publish_options_supported and \ + storage_type != 'xml': + options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', + attrs={'type': 'submit'}) + f = options.addChild('field', attrs={'var': 'FORM_TYPE', + 'type': 'hidden'}) + f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) + f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) + f.setTagData('value', 'true') + f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) + f.setTagData('value', 'whitelist') self.send_pb_publish('', 'storage:bookmarks', iq, 'current', - options=options) + options=options) if storage_type != 'pubsub': iqA = common.xmpp.Iq(typ='set') iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) @@ -1911,7 +1932,7 @@ class Connection(CommonConnection, ConnectionHandlers): Get Annonations from storage as described in XEP 0048, and XEP 0145 """ self.annotations = {} - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ='get') iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) @@ -1922,7 +1943,7 @@ class Connection(CommonConnection, ConnectionHandlers): """ Set Annonations in private storage as described in XEP 0048, and XEP 0145 """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ='set') iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) @@ -1939,7 +1960,7 @@ class Connection(CommonConnection, ConnectionHandlers): """ Get metacontacts list from storage as described in XEP 0049 """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ='get') iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) @@ -1953,7 +1974,7 @@ class Connection(CommonConnection, ConnectionHandlers): """ Send meta contacts to the storage namespace """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ='set') iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) @@ -1968,15 +1989,23 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(iq) def send_agent_status(self, agent, ptype): - if not self.connection: + if not gajim.account_is_connected(self.name): return show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) p = common.xmpp.Presence(to = agent, typ = ptype, show = show) p = self.add_sha(p, ptype != 'unavailable') self.connection.send(p) + def send_captcha(self, jid, form_node): + if not gajim.account_is_connected(self.name): + return + iq = common.xmpp.Iq(typ='set', to=jid) + captcha = iq.addChild(name='captcha', namespace=common.xmpp.NS_CAPTCHA) + captcha.addChild(node=form_node) + self.connection.send(iq) + def check_unique_room_id_support(self, server, instance): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'get', to = server) iq.setAttr('id', 'unique1') @@ -1991,7 +2020,7 @@ class Connection(CommonConnection, ConnectionHandlers): def join_gc(self, nick, room_jid, password, change_nick=False): # FIXME: This room JID needs to be normalized; see #1364 - if not self.connection: + if not gajim.account_is_connected(self.name): return show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) if show == 'invisible': @@ -2041,24 +2070,26 @@ class Connection(CommonConnection, ConnectionHandlers): t.setTagData('password', password) self.connection.send(p) - def send_gc_message(self, jid, msg, xhtml = None): - if not self.connection: + def send_gc_message(self, jid, msg, xhtml = None, label = None): + if not gajim.account_is_connected(self.name): return if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): from common.rst_xhtml_generator import create_xhtml xhtml = create_xhtml(msg) msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml) + if label is not None: + msg_iq.addChild(node = label) self.connection.send(msg_iq) self.dispatch('MSGSENT', (jid, msg)) def send_gc_subject(self, jid, subject): - if not self.connection: + if not gajim.account_is_connected(self.name): return msg_iq = common.xmpp.Message(jid, typ = 'groupchat', subject = subject) self.connection.send(msg_iq) def request_gc_config(self, room_jid): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, to = room_jid) @@ -2066,7 +2097,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(iq) def destroy_gc_room(self, room_jid, reason = '', jid = ''): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, to = room_jid) @@ -2113,7 +2144,7 @@ class Connection(CommonConnection, ConnectionHandlers): """ Role is for all the life of the room so it's based on nick """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ common.xmpp.NS_MUC_ADMIN) @@ -2128,7 +2159,7 @@ class Connection(CommonConnection, ConnectionHandlers): """ Affiliation is for all the life of the room so it's based on jid """ - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ common.xmpp.NS_MUC_ADMIN) @@ -2140,7 +2171,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(iq) def send_gc_affiliation_list(self, room_jid, users_dict): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ common.xmpp.NS_MUC_ADMIN) @@ -2153,7 +2184,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(iq) def get_affiliation_list(self, room_jid, affiliation): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ common.xmpp.NS_MUC_ADMIN) @@ -2162,7 +2193,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(iq) def send_gc_config(self, room_jid, form): - if not self.connection: + if not gajim.account_is_connected(self.name): return iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ common.xmpp.NS_MUC_OWNER) @@ -2172,7 +2203,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(iq) def change_password(self, password): - if not self.connection: + if not gajim.account_is_connected(self.name): return hostname = gajim.config.get_per('accounts', self.name, 'hostname') username = gajim.config.get_per('accounts', self.name, 'name') @@ -2182,17 +2213,29 @@ class Connection(CommonConnection, ConnectionHandlers): q.setTagData('password', password) self.connection.send(iq) - def get_password(self, callback): + def get_password(self, callback, type_): + self.pasword_callback = (callback, type_) if self.password: - callback(self.password) + self.set_password(self.password) return - self.pasword_callback = callback self.dispatch('PASSWORD_REQUIRED', None) def set_password(self, password): self.password = password if self.pasword_callback: - self.pasword_callback(password) + callback, type_ = self.pasword_callback + if self._current_type == 'plain' and type_ == 'PLAIN' and \ + gajim.config.get_per('accounts', self.name, + 'warn_when_insecure_password'): + self.dispatch('INSECURE_PASSWORD', None) + return + callback(password) + self.pasword_callback = None + + def accept_insecure_password(self): + if self.pasword_callback: + callback, type_ = self.pasword_callback + callback(self.password) self.pasword_callback = None def unregister_account(self, on_remove_success): diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index b0ba56568..6ae4b70b1 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -6,7 +6,7 @@ ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> ## Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com> ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> ## Jean-Marie Traissard <jim AT lapin.org> @@ -49,7 +49,7 @@ from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub from common.pep import ConnectionPEP from common.protocol.caps import ConnectionCaps -from common.protocol.bytestream import ConnectionBytestream +from common.protocol.bytestream import ConnectionSocks5Bytestream import common.caps_cache as capscache from common.nec import NetworkEvent if gajim.HAVE_FARSIGHT: @@ -342,6 +342,8 @@ class ConnectionDisco: if features.__contains__(common.xmpp.NS_GMAILNOTIFY): gajim.gmail_domains.append(jid) self.request_gmail_notifications() + if features.__contains__(common.xmpp.NS_SECLABEL): + self.seclabel_supported = True for identity in identities: if identity['category'] == 'pubsub' and identity.get('type') == \ 'pep': @@ -353,6 +355,11 @@ class ConnectionDisco: self.pubsub_supported = True if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS): self.pubsub_publish_options_supported = True + else: + # Remove stored bookmarks accessible to everyone. + our_jid = gajim.get_jid_from_account(self.name) + self.send_pb_purge(our_jid, 'storage:bookmarks') + self.send_pb_delete(our_jid, 'storage:bookmarks') if features.__contains__(common.xmpp.NS_BYTESTREAM): our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\ '/' + self.server_resource) @@ -547,8 +554,7 @@ class ConnectionVcard: id_ = iq_obj.getID() gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received', - conn = con, - xmpp_iq = iq_obj)) + conn=con, xmpp_iq=iq_obj)) # Check if we were waiting a timeout for this id found_tim = None @@ -641,9 +647,19 @@ class ConnectionVcard: else: if iq_obj.getErrorCode() not in ('403', '406', '404'): self.private_storage_supported = False + # We can now continue connection by requesting the roster - version = gajim.config.get_per('accounts', self.name, + version = None + if con.Stream.features and con.Stream.features.getTag('ver', + namespace=common.xmpp.NS_ROSTER_VER): + version = gajim.config.get_per('accounts', self.name, 'roster_version') + if version and not gajim.contacts.get_contacts_jid_list( + self.name): + gajim.config.set_per('accounts', self.name, + 'roster_version', '') + version = None + iq_id = self.connection.initRoster(version=version) self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, ) elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED: @@ -669,7 +685,13 @@ class ConnectionVcard: # Ask metacontacts before roster self.get_metacontacts() elif self.awaiting_answers[id_][0] == PEP_CONFIG: + if iq_obj.getType() == 'error': + return + if not iq_obj.getTag('pubsub'): + return conf = iq_obj.getTag('pubsub').getTag('configure') + if not conf: + return node = conf.getAttr('node') form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA) if form_tag: @@ -782,10 +804,42 @@ class ConnectionHandlersBase: # keep the jids we auto added (transports contacts) to not send the # SUBSCRIBED event to gui self.automatically_added = [] + # IDs of jabber:iq:last requests + self.last_ids = [] # keep track of sessions this connection has with other JIDs self.sessions = {} + def _ErrorCB(self, con, iq_obj): + log.debug('ErrorCB') + jid_from = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from) + id_ = unicode(iq_obj.getID()) + if id_ in self.last_ids: + self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, '')) + self.last_ids.remove(id_) + return + + def _LastResultCB(self, con, iq_obj): + log.debug('LastResultCB') + qp = iq_obj.getTag('query') + seconds = qp.getAttr('seconds') + status = qp.getData() + try: + seconds = int(seconds) + except Exception: + return + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + if id_ in self.last_ids: + self.last_ids.remove(id_) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) + def get_sessions(self, jid): """ Get all sessions for the given full jid @@ -915,13 +969,13 @@ class ConnectionHandlersBase: return sess -class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, - ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, - ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): +class ConnectionHandlers(ConnectionVcard, ConnectionSocks5Bytestream, +ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, +ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): def __init__(self): global HAS_IDLE ConnectionVcard.__init__(self) - ConnectionBytestream.__init__(self) + ConnectionSocks5Bytestream.__init__(self) ConnectionCommands.__init__(self) ConnectionPubSub.__init__(self) ConnectionPEP.__init__(self, account=self.name, dispatcher=self, @@ -936,8 +990,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, # keep the latest subscribed event for each jid to prevent loop when we # acknowledge presences self.subscribed_events = {} - # IDs of jabber:iq:last requests - self.last_ids = [] # IDs of jabber:iq:version requests self.version_ids = [] # IDs of urn:xmpp:time requests @@ -960,10 +1012,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, if not self.connection or self.connected < 2: return if answer == 'yes': - self.connection.send(iq_obj.buildReply('result')) + confirm = iq_obj.getTag('confirm') + reply = iq_obj.buildReply('result') + if iq_obj.getName() == 'message': + reply.addChild(node=confirm) + self.connection.send(reply) elif answer == 'no': err = common.xmpp.Error(iq_obj, - common.xmpp.protocol.ERR_NOT_AUTHORIZED) + common.xmpp.protocol.ERR_NOT_AUTHORIZED) self.connection.send(err) def _HttpAuthCB(self, con, iq_obj): @@ -981,6 +1037,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, def _ErrorCB(self, con, iq_obj): log.debug('ErrorCB') + ConnectionHandlersBase._ErrorCB(self, con, iq_obj) jid_from = helpers.get_full_jid_from_iq(iq_obj) jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from) id_ = unicode(iq_obj.getID()) @@ -988,10 +1045,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, self.dispatch('OS_INFO', (jid_stripped, resource, '', '')) self.version_ids.remove(id_) return - if id_ in self.last_ids: - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, '')) - self.last_ids.remove(id_) - return if id_ in self.entity_time_ids: self.dispatch('ENTITY_TIME', (jid_stripped, resource, '')) self.entity_time_ids.remove(id_) @@ -1029,6 +1082,33 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, annotation = note.getData() self.annotations[jid] = annotation + def _SecLabelCB(self, con, iq_obj): + """ + Security Label callback, used for catalogues. + """ + log.debug('SecLabelCB') + query = iq_obj.getTag('catalog') + to = query.getAttr('to') + items = query.getTags('securitylabel') + labels = {} + ll = [] + for item in items: + label = item.getTag('displaymarking').getData() + labels[label] = item + ll.append(label) + if to not in self.seclabel_catalogues: + self.seclabel_catalogues[to] = [[], None, None] + self.seclabel_catalogues[to][1] = labels + self.seclabel_catalogues[to][2] = ll + for callback in self.seclabel_catalogues[to][0]: + callback() + self.seclabel_catalogues[to][0] = [] + + def seclabel_catalogue_request(self, to, callback): + if to not in self.seclabel_catalogues: + self.seclabel_catalogues[to] = [[], None, None] + self.seclabel_catalogues[to][0].append(callback) + def _parse_bookmarks(self, storage, storage_type): """ storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks @@ -1132,26 +1212,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, self.connection.send(iq_obj) raise common.xmpp.NodeProcessed - def _LastResultCB(self, con, iq_obj): - log.debug('LastResultCB') - qp = iq_obj.getTag('query') - seconds = qp.getAttr('seconds') - status = qp.getData() - try: - seconds = int(seconds) - except Exception: - return - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - if id_ in self.last_ids: - self.last_ids.remove(id_) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) - def _VersionResultCB(self, con, iq_obj): log.debug('VersionResultCB') client_info = '' @@ -1373,13 +1433,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, log.debug('MessageCB') gajim.nec.push_incoming_event(NetworkEvent('raw-message-received', - conn = con, - xmpp_msg = msg, - account = self.name)) + conn=con, xmpp_msg=msg, account=self.name)) mtype = msg.getType() - # check if the message is a roster item exchange (XEP-0144) if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX): self._rosterItemExchangeCB(con, msg) @@ -1440,7 +1497,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, thread_id = msg.getThread() - if not mtype: + if not mtype or mtype not in ('chat', 'groupchat', 'error'): mtype = 'normal' msgtxt = msg.getBody() @@ -1449,6 +1506,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO) session = None + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, self.name) + if not gc_control and \ + jid in gajim.interface.minimized_controls[self.name]: + gc_control = gajim.interface.minimized_controls[self.name][jid] + + if gc_control and jid == frm: # message from a gc without a resource + mtype = 'groupchat' + if mtype != 'groupchat': session = self.get_or_create_session(frm, thread_id) @@ -1573,16 +1638,65 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, gajim.logger.write('error', frm, error_msg, tim=tim, subject=subject) except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e))) except exceptions.DatabaseMalformed: pritext = _('Database Error') sectext = _('The database file (%s) cannot be read. Try to repair ' 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) + self.dispatch('DB_ERROR', (pritext, sectext)) self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, tim, session)) + def _on_bob_received(self, conn, result, cid): + """ + Called when we receive BoB data + """ + if cid not in self.awaiting_cids: + return + + if result.getType() == 'result': + data = msg.getTags('data', namespace=common.xmpp.NS_BOB) + if data.getAttr('cid') == cid: + for func in self.awaiting_cids[cid]: + cb = func[0] + args = func[1] + pos = func[2] + bob_data = data.getData() + def recurs(node, cid, data): + if node.getData() == 'cid:' + cid: + node.setData(data) + else: + for child in node.getChildren(): + recurs(child, cid, data) + recurs(args[pos], cid, bob_data) + cb(*args) + del self.awaiting_cids[cid] + return + + # An error occured, call callback without modifying data. + for func in self.awaiting_cids[cid]: + cb = func[0] + args = func[1] + cb(*args) + del self.awaiting_cids[cid] + + def get_bob_data(self, cid, to, callback, args, position): + """ + Request for BoB (XEP-0231) and when data will arrive, call callback + with given args, after having replaced cid by it's data in + args[position] + """ + if cid in self.awaiting_cids: + self.awaiting_cids[cid].appends((callback, args, position)) + else: + self.awaiting_cids[cid] = [(callback, args, position)] + iq = common.xmpp.Iq(to=to, typ='get') + data = iq.addChild(name='data', attrs={'cid': cid}, + namespace=common.xmpp.NS_BOB) + self.connection.SendAndCallForResponse(iq, self._on_bob_received, + {'cid': cid}) + # process and dispatch a groupchat message def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim): has_timestamp = bool(msg.timestamp) @@ -1603,12 +1717,35 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode)) return - # Ignore message from room in which we are not + displaymarking = None + seclabel = msg.getTag('securitylabel') + if seclabel and seclabel.getNamespace() == common.xmpp.NS_SECLABEL: + displaymarking = seclabel.getTag('displaymarking') # Ignore message from room in which we are not if jid not in self.last_history_time: return - self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), - statusCode)) + captcha = msg.getTag('captcha', namespace=common.xmpp.NS_CAPTCHA) + if captcha: + captcha = captcha.getTag('x', namespace=common.xmpp.NS_DATA) + for field in captcha.getTags('field'): + for media in field.getTags('media'): + for uri in media.getTags('uri'): + uri_data = uri.getData() + if uri_data.startswith('cid:'): + uri_data = uri_data[4:] + found = False + for data in msg.getTags('data', + namespace=common.xmpp.NS_BOB): + if data.getAttr('cid') == uri_data: + uri.setData(data.getData()) + found = True + if not found: + self.get_bob_data(uri_data, frm, + self.dispatch_gc_message, [msg, frm, msgtxt, + jid, tim], 0) + return + self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, + msg.getXHTML(), statusCode, displaymarking, captcha)) tim_int = int(float(mktime(tim))) if gajim.config.should_log(self.name, jid) and not \ @@ -1624,13 +1761,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, self.last_history_time[jid] = mktime(tim) except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e))) except exceptions.DatabaseMalformed: pritext = _('Database Error') sectext = _('The database file (%s) cannot be read. Try to repair ' 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) + self.dispatch('DB_ERROR', (pritext, sectext)) def dispatch_invite_message(self, invite, frm): item = invite.getTag('invite') @@ -1654,7 +1791,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, Called when we receive a presence """ gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received', - conn=con, xmpp_pres=prs)) + conn=con, xmpp_pres=prs)) ptype = prs.getType() if ptype == 'available': ptype = None @@ -1769,34 +1906,41 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, prio, keyID, timestamp, None)) elif (errcode == '503'): - # maximum user number reached - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Maximum number of users for %s has been reached') % \ - room_jid)) + if gc_control is None or gc_control.autorejoin is None: + # maximum user number reached + self.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Maximum number of users for %s has been ' + 'reached') % room_jid)) elif (errcode == '401') or (errcon == 'not-authorized'): # password required to join self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick)) elif (errcode == '403') or (errcon == 'forbidden'): # we are banned - self.dispatch('ERROR', (_('Unable to join group chat'), - _('You are banned from group chat %s.') % room_jid)) + self.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('You are banned from group chat %s.') % room_jid)) elif (errcode == '404') or (errcon in ('item-not-found', 'remote-server-not-found')): if gc_control is None or gc_control.autorejoin is None: # group chat does not exist - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Group chat %s does not exist.') % room_jid)) + self.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Group chat %s does not exist.') % room_jid)) elif (errcode == '405') or (errcon == 'not-allowed'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Group chat creation is restricted.'))) + self.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Group chat creation is restricted.'))) elif (errcode == '406') or (errcon == 'not-acceptable'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Your registered nickname must be used in group chat %s.') \ - % room_jid)) + self.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Your registered nickname must be used in group chat ' + '%s.') % room_jid)) elif (errcode == '407') or (errcon == 'registration-required'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('You are not in the members list in groupchat %s.') % \ - room_jid)) + self.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('You are not in the members list in groupchat %s.') %\ + room_jid)) elif (errcode == '409') or (errcon == 'conflict'): # nick conflict room_jid = gajim.get_room_from_fjid(who) @@ -1820,14 +1964,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, try: gajim.logger.write('gcstatus', who, st, show) except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + self.dispatch('DB_ERROR', (_('Disk Write Error'), + str(e))) except exceptions.DatabaseMalformed: pritext = _('Database Error') sectext = _('The database file (%s) cannot be read. Try to ' 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' ' or remove it (all history will be lost).') % \ common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) + self.dispatch('DB_ERROR', (pritext, sectext)) if avatar_sha or avatar_sha == '': if avatar_sha == '': # contact has no avatar @@ -1970,14 +2115,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, try: gajim.logger.write('status', jid_stripped, status, show) except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e))) except exceptions.DatabaseMalformed: pritext = _('Database Error') sectext = _('The database file (%s) cannot be read. Try to ' 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' 'or remove it (all history will be lost).') % \ common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) + self.dispatch('DB_ERROR', (pritext, sectext)) our_jid = gajim.get_jid_from_account(self.name) if jid_stripped == our_jid and resource == self.server_resource: # We got our own presence @@ -2281,6 +2426,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, common.xmpp.NS_MUC_ADMIN) con.RegisterHandler('iq', self._PrivateCB, 'result', common.xmpp.NS_PRIVATE) + con.RegisterHandler('iq', self._SecLabelCB, 'result', + common.xmpp.NS_SECLABEL_CATALOG) con.RegisterHandler('iq', self._HttpAuthCB, 'get', common.xmpp.NS_HTTP_AUTH) con.RegisterHandler('iq', self._CommandExecuteCB, 'set', diff --git a/src/common/contacts.py b/src/common/contacts.py index edd9c22e3..e115ef46d 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -4,7 +4,7 @@ ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net> ## Tomasz Melcer <liori AT exroot.org> @@ -496,11 +496,8 @@ class Contacts(): return self._contacts.keys() def get_contacts_jid_list(self): - contacts = self._contacts.keys() - for jid in self._contacts.keys(): - if self._contacts[jid][0].is_groupchat(): - contacts.remove(jid) - return contacts + return [jid for jid, contact in self._contacts.iteritems() if not + contact[0].is_groupchat()] def get_contact_from_full_jid(self, fjid): """ diff --git a/src/common/dataforms.py b/src/common/dataforms.py index 4620556c4..fea7187ea 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -3,7 +3,7 @@ ## src/common/dataforms.py ## ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de> ## ## This file is part of Gajim. @@ -227,6 +227,86 @@ class DataField(ExtendedNode): return locals() + @nested_property + def media(): + """ + Media data + """ + def fget(self): + media = self.getTag('media', namespace=xmpp.NS_DATA_MEDIA) + if media: + return Media(media) + + def fset(self, value): + fdel(self) + self.addChild(node=value) + + def fdel(self): + t = self.getTag('media') + if t is not None: + self.delChild(t) + + return locals() + +class Uri(xmpp.Node): + def __init__(self, uri_tag): + xmpp.Node.__init__(self, node=uri_tag) + + @nested_property + def type_(): + """ + uri type + """ + def fget(self): + return self.getAttr('type') + + def fset(self, value): + self.setAttr('type', value) + + def fdel(self): + self.delAttr('type') + + return locals() + + @nested_property + def uri_data(): + """ + uri data + """ + def fget(self): + return self.getData() + + def fset(self, value): + self.setData(value) + + def fdel(self): + self.setData(None) + + return locals() + +class Media(xmpp.Node): + def __init__(self, media_tag): + xmpp.Node.__init__(self, node=media_tag) + + @nested_property + def uris(): + """ + URIs of the media element. + """ + def fget(self): + return map(Uri, self.getTags('uri')) + + def fset(self, value): + fdel(self) + for uri in values: + self.addChild(node=uri) + + def fdel(self, value): + for element in self.getTags('uri'): + self.delChild(element) + + return locals() + class BooleanField(DataField): @nested_property def value(): @@ -543,12 +623,13 @@ class SimpleDataForm(DataForm, DataRecord): if f.required: # Keep all required fields continue - if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \ - len(f.values) == 0): + if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') \ + and len(f.values) == 0): to_be_removed.append(f) else: del f.label del f.description + del f.media for f in to_be_removed: c.delChild(f) return c diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py index d1e8fe381..389c16f18 100644 --- a/src/common/dbus_support.py +++ b/src/common/dbus_support.py @@ -4,7 +4,7 @@ ## Copyright (C) 2005 Andrew Sayman <lorien420 AT myrealbox.com> ## Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> ## Stefan Bethge <stefan AT lanpartei.de> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> @@ -42,6 +42,7 @@ except ImportError: else: try: # test if dbus-x11 is installed + bus = dbus.SystemBus() bus = dbus.SessionBus() supported = True # does user have D-Bus bindings? except dbus.DBusException: @@ -49,6 +50,12 @@ else: if not os.name == 'nt': # only say that to non Windows users print _('D-Bus does not run correctly on this machine') print _('D-Bus capabilities of Gajim cannot be used') + except exceptions.SystemBusNotPresent: + print _('D-Bus does not run correctly on this machine: system bus not ' + 'present') + except exceptions.SessionBusNotPresent: + print _('D-Bus does not run correctly on this machine: session bus not ' + 'present') class SystemBus: """ diff --git a/src/common/defs.py b/src/common/defs.py index 348010bff..5eb2a8084 100644 --- a/src/common/defs.py +++ b/src/common/defs.py @@ -2,8 +2,8 @@ ## src/common/defs.py ## ## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> -## Jean-Marie Traissard <jim AT lapin.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com> ## Tomasz Melcer <liori AT exroot.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> diff --git a/src/common/dh.py b/src/common/dh.py index 29f59a083..827d87806 100644 --- a/src/common/dh.py +++ b/src/common/dh.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/common/dh.py ## -## Copyright (C) 2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> ## ## This file is part of Gajim. diff --git a/src/common/events.py b/src/common/events.py index 9e8ae059f..f51d64fb4 100644 --- a/src/common/events.py +++ b/src/common/events.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com> ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de> ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> diff --git a/src/common/exceptions.py b/src/common/exceptions.py index 6e1df2e36..122262418 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -2,7 +2,7 @@ ## src/common/exceptions.py ## ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com> ## diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py index f84d63c3d..e997ec6bf 100755 --- a/src/common/fuzzyclock.py +++ b/src/common/fuzzyclock.py @@ -2,7 +2,7 @@ ## src/common/fuzzyclock.py ## ## Copyright (C) 2006 Christoph Neuroth <delmonico AT gmx.net> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2009 Benjamin Richter <br AT waldteufel-online.net> ## diff --git a/src/common/gajim.py b/src/common/gajim.py index c8646aec3..cfd4419af 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/common/gajim.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> @@ -91,7 +91,7 @@ DATA_DIR = gajimpaths['DATA'] ICONS_DIR = gajimpaths['ICONS'] HOME_DIR = gajimpaths['HOME'] PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'], - gajimpaths['PLUGINS_USER']] + gajimpaths['PLUGINS_USER']] PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR'] try: @@ -186,12 +186,6 @@ else: # read. HAVE_LATEX = False -HAVE_INDICATOR = True -try: - import indicate -except ImportError: - HAVE_INDICATOR = False - HAVE_FARSIGHT = True try: import farsight, gst @@ -204,7 +198,7 @@ gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE, 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog', 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN, - xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX] + xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL] # Optional features gajim supports per account gajim_optional_features = {} diff --git a/src/common/helpers.py b/src/common/helpers.py index 51b6f49ad..367520d75 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/common/helpers.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Alex Mauer <hawke AT hawkesnest.net> @@ -29,6 +29,7 @@ ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## +import sys import re import locale import os @@ -220,9 +221,9 @@ def get_uf_show(show, use_mnemonic = False): uf_show = _('Free for Chat') elif show == 'online': if use_mnemonic: - uf_show = _('_Available') + uf_show = Q_('?user status:_Available') else: - uf_show = _('Available') + uf_show = Q_('?user status:Available') elif show == 'connecting': uf_show = _('Connecting') elif show == 'away': @@ -1341,11 +1342,11 @@ def get_subscription_request_msg(account=None): our_jid = gajim.get_jid_from_account(account) vcard = gajim.connections[account].get_cached_vcard(our_jid) name = '' - if 'N' in vcard: - if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']: - name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY'] - if not name: - if 'FN' in vcard: + if vcard: + if 'N' in vcard: + if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']: + name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY'] + if not name and 'FN' in vcard: name = vcard['FN'] nick = gajim.nicks[account] if name and nick: @@ -1353,4 +1354,4 @@ def get_subscription_request_msg(account=None): elif nick: name = nick s = Template(s).safe_substitute({'name': name}) - return s + return s diff --git a/src/common/i18n.py b/src/common/i18n.py index 4497a56f0..3ff764c99 100644 --- a/src/common/i18n.py +++ b/src/common/i18n.py @@ -1,9 +1,8 @@ # -*- coding:utf-8 -*- ## src/common/i18n.py ## -## Copyright (C) 2003-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2004 Vincent Hanquez <tab AT snarc.org> -## Copyright (C) 2004-2007 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2009 Benjamin Richter <br AT waldteufel-online.net> ## @@ -68,20 +67,25 @@ if gettext._translations: else: _translation = gettext.NullTranslations() -def Q_(s): - # Qualified translatable strings - # Some strings are too ambiguous to be easily translated. - # so we must use as: - # s = Q_('?vcard:Unknown') - # widget.set_text(s) - # Q_() removes the ?vcard: - # but gettext while parsing the file detects ?vcard:Unknown as a whole string. - # translator can either put the ?vcard: part or no (easier for him or her to no) - # nothing fails - s = _(s) - if s[0] == '?': - s = s[s.find(':')+1:] # remove ?abc: part - return s +def Q_(text): + """ + Translate the given text, optionally qualified with a special + construction, which will help translators to disambiguate between + same terms, but in different contexts. + + When translated text is returned - this rudimentary construction + will be stripped off, if it's present. + + Here is the construction to use: + Q_("?vcard:Unknown") + + Everything between ? and : - is the qualifier to convey the context + to the translators. Everything after : - is the text itself. + """ + text = _(text) + if text.startswith('?'): + qualifier, text = text.split(':', 1) + return text def ngettext(s_sing, s_plural, n, replace_sing = None, replace_plural = None): """ diff --git a/src/common/jingle.py b/src/common/jingle.py index 8f87504c8..da2a5d031 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -33,7 +33,7 @@ Handles the jingle signalling protocol import xmpp import helpers -from jingle_session import JingleSession +from jingle_session import JingleSession, JingleStates from jingle_rtp import JingleAudio, JingleVideo @@ -43,32 +43,23 @@ class ConnectionJingle(object): """ def __init__(self): - # dictionary: (jid, sessionid) => JingleSession object + # dictionary: sessionid => JingleSession object self.__sessions = {} # dictionary: (jid, iq stanza id) => JingleSession object, # one time callbacks self.__iq_responses = {} - def add_jingle(self, jingle): - """ - Add a jingle session to a jingle stanza dispatcher - - jingle - a JingleSession object. - """ - self.__sessions[(jingle.peerjid, jingle.sid)] = jingle - - def delete_jingle_session(self, peerjid, sid): + def delete_jingle_session(self, sid): """ Remove a jingle session from a jingle stanza dispatcher """ - key = (peerjid, sid) - if key in self.__sessions: + if sid in self.__sessions: #FIXME: Move this elsewhere? - for content in self.__sessions[key].contents.values(): + for content in self.__sessions[sid].contents.values(): content.destroy() - self.__sessions[key].callbacks = [] - del self.__sessions[key] + self.__sessions[sid].callbacks = [] + del self.__sessions[sid] def _JingleCB(self, con, stanza): """ @@ -94,13 +85,16 @@ class ConnectionJingle(object): sid = jingle.getAttr('sid') # do we need to create a new jingle object - if (jid, sid) not in self.__sessions: + if sid not in self.__sessions: #TODO: tie-breaking and other things... newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) - self.add_jingle(newjingle) + self.__sessions[sid] = newjingle # we already have such session in dispatcher... - self.__sessions[(jid, sid)].on_stanza(stanza) + self.__sessions[sid].on_stanza(stanza) + # Delete invalid/unneeded sessions + if sid in self.__sessions and self.__sessions[sid].state == JingleStates.ended: + self.delete_jingle_session(sid) raise xmpp.NodeProcessed @@ -112,7 +106,7 @@ class ConnectionJingle(object): jingle.add_content('voice', JingleAudio(jingle)) else: jingle = JingleSession(self, weinitiate=True, jid=jid) - self.add_jingle(jingle) + self.__sessions[jingle.sid] = jingle jingle.add_content('voice', JingleAudio(jingle)) jingle.start_session() return jingle.sid @@ -125,15 +119,29 @@ class ConnectionJingle(object): jingle.add_content('video', JingleVideo(jingle)) else: jingle = JingleSession(self, weinitiate=True, jid=jid) - self.add_jingle(jingle) + self.__sessions[jingle.sid] = jingle jingle.add_content('video', JingleVideo(jingle)) jingle.start_session() return jingle.sid + + def iter_jingle_sessions(self, jid, sid=None, media=None): + if sid: + return (session for session in self.__sessions.values() if session.sid == sid) + sessions = (session for session in self.__sessions.values() if session.peerjid == jid) + if media: + if media not in ('audio', 'video'): + return tuple() + else: + return (session for session in sessions if session.get_content(media)) + else: + return sessions + + def get_jingle_session(self, jid, sid=None, media=None): if sid: - if (jid, sid) in self.__sessions: - return self.__sessions[(jid, sid)] + if sid in self.__sessions: + return self.__sessions[sid] else: return None elif media: diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index f4022cc8e..af94561c2 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -127,6 +127,11 @@ class JingleContent(object): content.addChild(node=self.transport.make_transport([candidate])) self.session.send_transport_info(content) + def send_description_info(self): + content = self.__content() + self._fill_content(content) + self.session.send_description_info(content) + def __fill_jingle_stanza(self, stanza, content, error, action): """ Add our things to session-initiate stanza diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index 32d9398a9..536fda8d6 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -15,6 +15,8 @@ Handles Jingle RTP sessions (XEP 0167) """ +from collections import deque + import gobject import socket @@ -28,6 +30,10 @@ from jingle_transport import JingleTransportICEUDP from jingle_content import contents, JingleContent, JingleContentSetupException +import logging +log = logging.getLogger('gajim.c.jingle_rtp') + + class JingleRTPContent(JingleContent): def __init__(self, session, media, transport=None): if transport is None: @@ -38,6 +44,10 @@ class JingleRTPContent(JingleContent): self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO, 'video': farsight.MEDIA_TYPE_VIDEO}[media] + self.pipeline = None + self.src_bin = None + self.stream_failed_once = False + self.candidates_ready = False # True when local candidates are prepared self.callbacks['session-initiate'] += [self.__on_remote_codecs] @@ -89,8 +99,7 @@ class JingleRTPContent(JingleContent): farsight.DIRECTION_RECV, 'nice', params) def is_ready(self): - return (JingleContent.is_ready(self) and self.candidates_ready - and self.p2psession.get_property('codecs-ready')) + return (JingleContent.is_ready(self) and self.candidates_ready) def make_bin_from_config(self, config_key, pipeline, text): pipeline = pipeline % gajim.config.get(config_key) @@ -117,15 +126,16 @@ class JingleRTPContent(JingleContent): Send several DTMF tones """ if self._dtmf_running: - raise Exception # TODO: Proper exception + raise Exception("There is a DTMF batch already running") + events = deque(events) self._dtmf_running = True - self._start_dtmf(events.pop(0)) + self._start_dtmf(events.popleft()) gobject.timeout_add(500, self._next_dtmf, events) def _next_dtmf(self, events): self._stop_dtmf() if events: - self._start_dtmf(events.pop(0)) + self._start_dtmf(events.popleft()) gobject.timeout_add(500, self._next_dtmf, events) else: self._dtmf_running = False @@ -161,14 +171,14 @@ class JingleRTPContent(JingleContent): def _on_gst_message(self, bus, message): if message.type == gst.MESSAGE_ELEMENT: name = message.structure.get_name() + log.debug('gst element message: %s: %s' % (name, message)) if name == 'farsight-new-active-candidate-pair': pass elif name == 'farsight-recv-codecs-changed': pass elif name == 'farsight-codecs-changed': - if self.is_ready(): - self.session.on_session_state_changed(self) - # TODO: description-info + if self.sent and self.p2psession.get_property('codecs-ready'): + self.send_description_info() elif name == 'farsight-local-candidates-prepared': self.candidates_ready = True if self.is_ready(): @@ -176,22 +186,53 @@ class JingleRTPContent(JingleContent): elif name == 'farsight-new-local-candidate': candidate = message.structure['candidate'] self.transport.candidates.append(candidate) - if self.candidates_ready: + if self.sent: # FIXME: Is this case even possible? self.send_candidate(candidate) elif name == 'farsight-component-state-changed': state = message.structure['state'] - print message.structure['component'], state if state == farsight.STREAM_STATE_FAILED: reason = xmpp.Node('reason') reason.setTag('failed-transport') - self.session._session_terminate(reason) + self.session.remove_content(self.creator, self.name, reason) elif name == 'farsight-error': - print 'Farsight error #%d!' % message.structure['error-no'] - print 'Message: %s' % message.structure['error-msg'] - print 'Debug: %s' % message.structure['debug-msg'] + log.error('Farsight error #%d!\nMessage: %s\nDebug: %s' % ( + message.structure['error-no'], + message.structure['error-msg'], + message.structure['debug-msg'])) + elif message.type == gst.MESSAGE_ERROR: + # TODO: Fix it to fallback to videotestsrc anytime an error occur, + # or raise an error, Jingle way + # or maybe one-sided stream? + if not self.stream_failed_once: + self.session.connection.dispatch('ERROR', + (_("GStreamer error"), + _("Error: %s\nDebug: %s" % (message.structure['gerror'], + message.structure['debug'])))) + + sink_pad = self.p2psession.get_property('sink-pad') + + # Remove old source + self.src_bin.get_pad('src').unlink(sink_pad) + self.src_bin.set_state(gst.STATE_NULL) + self.pipeline.remove(self.src_bin) + + if not self.stream_failed_once: + # Add fallback source + self.src_bin = self.get_fallback_src() + self.pipeline.add(self.src_bin) + self.src_bin.get_pad('src').link(sink_pad) + self.stream_failed_once = True else: - print name + reason = xmpp.Node('reason') + reason.setTag('failed-application') + self.session.remove_content(self.creator, self.name, reason) + + # Start playing again + self.pipeline.set_state(gst.STATE_PLAYING) + + def get_fallback_src(self): + return gst.element_factory_make('fakesrc') def __on_content_accept(self, stanza, content, error, action): if self.accepted: @@ -291,19 +332,19 @@ class JingleAudio(JingleRTPContent): # the local parts # TODO: Add queues? - src_bin = self.make_bin_from_config('audio_input_device', + self.src_bin = self.make_bin_from_config('audio_input_device', '%s ! audioconvert', _("audio input")) self.sink = self.make_bin_from_config('audio_output_device', 'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output")) - self.mic_volume = src_bin.get_by_name('gajim_vol') + self.mic_volume = self.src_bin.get_by_name('gajim_vol') self.out_volume = self.sink.get_by_name('gajim_out_vol') # link gst elements - self.pipeline.add(self.sink, src_bin) + self.pipeline.add(self.sink, self.src_bin) - src_bin.get_pad('src').link(self.p2psession.get_property( + self.src_bin.get_pad('src').link(self.p2psession.get_property( 'sink-pad')) self.p2pstream.connect('src-pad-added', self._on_src_pad_added) @@ -323,24 +364,42 @@ class JingleVideo(JingleRTPContent): JingleRTPContent.setup_stream(self) # the local parts - src_bin = self.make_bin_from_config('video_input_device', - '%s ! videoscale ! ffmpegcolorspace', _("video input")) + if gajim.config.get('video_framerate'): + framerate = 'videorate ! video/x-raw-yuv,framerate=%s ! ' % \ + gajim.config.get('video_framerate') + else: + framerate = '' + try: + w, h = gajim.config.get('video_size').split('x') + except: + w = h = None + if w and h: + video_size = 'video/x-raw-yuv,width=%s,height=%s ! ' % (w, h) + else: + video_size = '' + self.src_bin = self.make_bin_from_config('video_input_device', + '%%s ! %svideoscale ! %sffmpegcolorspace' % (framerate, video_size), + _("video input")) #caps = gst.element_factory_make('capsfilter') #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240')) - self.pipeline.add(src_bin)#, caps) + self.pipeline.add(self.src_bin)#, caps) #src_bin.link(caps) self.sink = self.make_bin_from_config('video_output_device', 'videoscale ! ffmpegcolorspace ! %s', _("video output")) self.pipeline.add(self.sink) - src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad')) + self.src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad')) self.p2pstream.connect('src-pad-added', self._on_src_pad_added) # The following is needed for farsight to process ICE requests: self.pipeline.set_state(gst.STATE_PLAYING) + def get_fallback_src(self): + # TODO: Use avatar? + pipeline = 'videotestsrc is-live=true ! video/x-raw-yuv,framerate=10/1 ! ffmpegcolorspace' + return gst.parse_bin_from_description(pipeline, True) def get_content(desc): if desc['media'] == 'audio': diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index fd5fec1ef..4849f7fa8 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -68,8 +68,9 @@ class JingleSession(object): self.connection = con # connection to use # our full jid #FIXME: Get rid of gajim here? - self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \ - con.server_resource + self.ourjid = gajim.get_jid_from_account(self.connection.name) + if con.server_resource: + self.ourjid = self.ourjid + '/' + con.server_resource self.peerjid = jid # jid we connect to # jid we use as the initiator self.initiator = weinitiate and self.ourjid or self.peerjid @@ -183,17 +184,18 @@ class JingleSession(object): # The content is from us, accept it content.accepted = True - def remove_content(self, creator, name): + def remove_content(self, creator, name, reason=None): """ - We do not need this now + Remove the content `name` created by `creator` + by sending content-remove, or by sending session-terminate if + there is no content left. """ - #TODO: if (creator, name) in self.contents: content = self.contents[(creator, name)] if len(self.contents) > 1: - self.__content_remove(content) + self.__content_remove(content, reason) self.contents[(creator, name)].destroy() - if len(self.contents) == 0: + if not self.contents: self.end_session() def modify_content(self, creator, name, *someother): @@ -262,6 +264,12 @@ class JingleSession(object): jingle.addChild(node=content) self.connection.connection.send(stanza) + def send_description_info(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('description-info') + jingle.addChild(node=content) + self.connection.connection.send(stanza) + def on_stanza(self, stanza): """ A callback for ConnectionJingle. It gets stanza, then tries to send it to @@ -277,7 +285,7 @@ class JingleSession(object): # it's a jingle action action = jingle.getAttr('action') if action not in self.callbacks: - self.__send_error(stanza, 'bad_request') + self.__send_error(stanza, 'bad-request') return # FIXME: If we aren't initiated and it's not a session-initiate... if action != 'session-initiate' and self.state == JingleStates.ended: @@ -310,17 +318,15 @@ class JingleSession(object): def __on_error(self, stanza, jingle, error, action): # FIXME text = error.getTagData('text') - jingle_error = None - xmpp_error = None + error_name = None for child in error.getChildren(): if child.getNamespace() == xmpp.NS_JINGLE_ERRORS: - jingle_error = child.getName() + error_name = child.getName() + break elif child.getNamespace() == xmpp.NS_STANZAS: - xmpp_error = child.getName() - self.__dispatch_error(xmpp_error, jingle_error, text) + error_name = child.getName() + self.__dispatch_error(error_name, text, error.getAttribute('type')) # FIXME: Not sure when we would want to do that... - if xmpp_error == 'item-not-found': - self.connection.delete_jingle_session(self.peerjid, self.sid) def __on_transport_replace(self, stanza, jingle, error, action): for content in jingle.iterTags('content'): @@ -354,7 +360,7 @@ class JingleSession(object): # TODO: ringing, active, (un)hold, (un)mute payload = jingle.getPayload() if payload: - self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info') + self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify') raise xmpp.NodeProcessed def __on_content_remove(self, stanza, jingle, error, action): @@ -425,14 +431,24 @@ class JingleSession(object): # Jingle with unknown entities, it SHOULD return a <service-unavailable/> # error. + # Check if there's already a session with this user: + for session in self.connection.iter_jingle_sessions(self.peerjid): + if not session is self: + reason = xmpp.Node('reason') + alternative_session = reason.setTag('alternative-session') + alternative_session.setTagData('sid', session.sid) + self.__ack(stanza, jingle, error, action) + self._session_terminate(reason) + raise xmpp.NodeProcessed + # Lets check what kind of jingle session does the peer want - contents, contents_rejected, reason = self.__parse_contents(jingle) + contents, contents_rejected, reason_txt = self.__parse_contents(jingle) # If there's no content we understand... if not contents: # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate reason = xmpp.Node('reason') - reason.setTag(reason) + reason.setTag(reason_txt) self.__ack(stanza, jingle, error, action) self._session_terminate(reason) raise xmpp.NodeProcessed @@ -450,14 +466,19 @@ class JingleSession(object): for content in jingle.iterTags('content'): name = content['name'] creator = content['creator'] - cn = self.contents[(creator, name)] - cn.on_stanza(stanza, content, error, action) + if (creator, name) not in self.contents: + text = 'Content %s (created by %s) does not exist' % (name, creator) + self.__send_error(stanza, 'bad-request', text=text, type_='_modify') + raise xmpp.NodeProcessed + else: + cn = self.contents[(creator, name)] + cn.on_stanza(stanza, content, error, action) def __on_session_terminate(self, stanza, jingle, error, action): - self.connection.delete_jingle_session(self.peerjid, self.sid) + self.connection.delete_jingle_session(self.sid) reason, text = self.__reason_from_stanza(jingle) if reason not in ('success', 'cancel', 'decline'): - self.__dispatch_error(reason, reason, text) + self.__dispatch_error(reason, text) if text: text = '%s (%s)' % (reason, text) else: @@ -496,7 +517,7 @@ class JingleSession(object): reasons.add('failed-application') else: contents_rejected.append((element['name'], 'peer')) - failed.add('unsupported-applications') + reasons.add('unsupported-applications') failure_reason = None @@ -509,16 +530,15 @@ class JingleSession(object): return (contents, contents_rejected, failure_reason) - def __dispatch_error(self, error, jingle_error=None, text=None): - if jingle_error: - error = jingle_error + def __dispatch_error(self, error=None, text=None, type_=None): if text: text = '%s (%s)' % (error, text) - else: - text = error - self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text)) + if type_ != 'modify': + self.connection.dispatch('JINGLE_ERROR', + (self.peerjid, self.sid, text or error)) def __reason_from_stanza(self, stanza): + # TODO: Move to GUI? reason = 'success' reasons = ['success', 'busy', 'cancel', 'connectivity-error', 'decline', 'expired', 'failed-application', 'failed-transport', @@ -534,7 +554,7 @@ class JingleSession(object): break return (reason, text) - def __make_jingle(self, action): + def __make_jingle(self, action, reason=None): stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid)) attrs = {'action': action, 'sid': self.sid} @@ -543,16 +563,21 @@ class JingleSession(object): elif action == 'session-accept': attrs['responder'] = self.responder jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) + if reason is not None: + jingle.addChild(node=reason) return stanza, jingle - def __send_error(self, stanza, error, jingle_error=None, text=None): - err = xmpp.Error(stanza, '%s %s' % (xmpp.NS_STANZAS, error)) + def __send_error(self, stanza, error, jingle_error=None, text=None, type_=None): + err_stanza = xmpp.Error(stanza, '%s %s' % (xmpp.NS_STANZAS, error)) + err = err_stanza.getTag('error') + if type_: + err.setAttr('type', type_) if jingle_error: err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS) if text: err.setTagData('text', text) - self.connection.connection.send(err) - self.__dispatch_error(error, jingle_error, text) + self.connection.connection.send(err_stanza) + self.__dispatch_error(jingle_error or error, text, type_) def __append_content(self, jingle, content): """ @@ -596,19 +621,19 @@ class JingleSession(object): def _session_terminate(self, reason=None): assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('session-terminate') - if reason is not None: - jingle.addChild(node=reason) + stanza, jingle = self.__make_jingle('session-terminate', reason=reason) self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent') - self.connection.connection.send(stanza) + if self.connection.connection and self.connection.connected >= 2: + self.connection.connection.send(stanza) + # TODO: Move to GUI? reason, text = self.__reason_from_stanza(jingle) if reason not in ('success', 'cancel', 'decline'): - self.__dispatch_error(reason, reason, text) + self.__dispatch_error(reason, text) if text: text = '%s (%s)' % (reason, text) else: text = reason - self.connection.delete_jingle_session(self.peerjid, self.sid) + self.connection.delete_jingle_session(self.sid) self.connection.dispatch('JINGLE_DISCONNECTED', (self.peerjid, self.sid, None, text)) @@ -640,9 +665,9 @@ class JingleSession(object): def __content_modify(self): assert self.state != JingleStates.ended - def __content_remove(self, content): + def __content_remove(self, content, reason=None): assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-remove') + stanza, jingle = self.__make_jingle('content-remove', reason=reason) self.__append_content(jingle, content) self.connection.connection.send(stanza) # TODO: this will fail if content is not an RTP content diff --git a/src/common/latex.py b/src/common/latex.py index a95f1f9f7..95e0b09de 100644 --- a/src/common/latex.py +++ b/src/common/latex.py @@ -5,7 +5,7 @@ ## Copyright (C) 2005-2006 Alex Mauer <hawke AT hawkesnest.net> ## Travis Shirk <travis AT pobox.com> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> diff --git a/src/common/location_listener.py b/src/common/location_listener.py index 37a2e4612..af0aca6eb 100644 --- a/src/common/location_listener.py +++ b/src/common/location_listener.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ## src/common/location_listener.py ## -## Copyright (C) 2009 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2009-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## @@ -18,6 +18,8 @@ ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## +from datetime import datetime + from common import gajim from common import pep from common import dbus_support @@ -37,47 +39,47 @@ class LocationListener: self._data = {} def get_data(self): - self._get_address() - self._get_position() - - def _get_address(self): bus = dbus.SessionBus() - if 'org.freedesktop.Geoclue.Master' not in bus.list_names(): - self._on_geoclue_address_changed() + try: + # Initializes Geoclue. + obj = bus.get_object('org.freedesktop.Geoclue.Master', + '/org/freedesktop/Geoclue/Master') + # get MasterClient path + path = obj.Create() + # get MasterClient + cli = bus.get_object('org.freedesktop.Geoclue.Master', path) + cli.SetRequirements(1, 0, True, 1023) + + self._get_address(cli) + self._get_position(cli) + except: + self._on_geoclue_position_changed() return - obj = bus.get_object('org.freedesktop.Geoclue.Master', - '/org/freedesktop/Geoclue/Master') - # get MasterClient path - path = obj.Create() - # get MasterClient - cli = bus.get_object('org.freedesktop.Geoclue.Master', path) + + + def _get_address(self, cli): + bus = dbus.SessionBus() cli.AddressStart() # Check that there is a provider name, description, service, path = cli.GetAddressProvider() if path: - timestamp, address, accuracy = cli.GetAddress() + provider = bus.get_object(service, path) + timestamp, address, accuracy = provider.GetAddress() self._on_geoclue_address_changed(timestamp, address, accuracy) - def _get_position(self): + def _get_position(self, cli): bus = dbus.SessionBus() - if 'org.freedesktop.Geoclue.Master' not in bus.list_names(): - self._on_geoclue_position_changed() - return - obj = bus.get_object('org.freedesktop.Geoclue.Master', - '/org/freedesktop/Geoclue/Master') - # get MasterClient path - path = obj.Create() - # get MasterClient - cli = bus.get_object('org.freedesktop.Geoclue.Master', path) cli.PositionStart() # Check that there is a provider name, description, service, path = cli.GetPositionProvider() if path: - fields, timestamp, lat, lon, alt, accuray = cli.GetPosition() + provider = bus.get_object(service, path) + fields, timestamp, lat, lon, alt, accuracy = provider.GetPosition() self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt, accuracy) def start(self): + self.location_info = {} self.get_data() bus = dbus.SessionBus() # Geoclue @@ -96,7 +98,7 @@ class LocationListener: 'region', 'street']: self._data[field] = address.get(field, None) if timestamp: - self._data['timestamp'] = timestamp + self._data['timestamp'] = self._timestamp_to_utc(timestamp) if accuracy: # in PEP it's horizontal accuracy self._data['accuracy'] = accuracy[1] @@ -110,7 +112,7 @@ class LocationListener: if _dict[field] is not None: self._data[field] = _dict[field] if timestamp: - self._data['timestamp'] = timestamp + self._data['timestamp'] = self._timestamp_to_utc(timestamp) if accuracy: # in PEP it's horizontal accuracy self._data['accuracy'] = accuracy[1] @@ -123,10 +125,21 @@ class LocationListener: continue if not gajim.config.get_per('accounts', acct, 'publish_location'): continue - if gajim.connections[acct].location_info == self._data: + if self.location_info == self._data: continue + if 'timestamp' in self.location_info and 'timestamp' in self._data: + last_data = self.location_info.copy() + del last_data['timestamp'] + new_data = self._data.copy() + del new_data['timestamp'] + if last_data == new_data: + continue gajim.connections[acct].send_location(self._data) - gajim.connections[acct].location_info = self._data + self.location_info = self._data.copy() + + def _timestamp_to_utc(self, timestamp): + time = datetime.utcfromtimestamp(timestamp) + return time.strftime('%Y-%m-%dT%H:%MZ') def enable(): listener = LocationListener.get() diff --git a/src/common/logger.py b/src/common/logger.py index b1079690a..c1228c639 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/common/logger.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py index 1707e1961..8678b919b 100644 --- a/src/common/multimedia_helpers.py +++ b/src/common/multimedia_helpers.py @@ -31,11 +31,14 @@ class DeviceManager(object): element = gst.element_factory_make(name, '%spresencetest' % name) if isinstance(element, gst.interfaces.PropertyProbe): element.set_state(gst.STATE_READY) + element.probe_property_name('device') devices = element.probe_get_values_name('device') if devices: self.devices[text % _(' Default device')] = pipe % name for device in devices: + element.set_state(gst.STATE_NULL) element.set_property('device', device) + element.set_state(gst.STATE_READY) device_name = element.get_property('device-name') self.devices[text % device_name] = pipe % '%s device=%s' % (name, device) element.set_state(gst.STATE_NULL) @@ -76,10 +79,10 @@ class VideoInputManager(DeviceManager): self.devices = {} # Test src self.detect_element('videotestsrc', _('Video test'), - '%s is-live=true') + '%s is-live=true ! video/x-raw-yuv,framerate=10/1') # Auto src self.detect_element('autovideosrc', _('Autodetect')) - # V4L2 src ; TODO: Figure out why it doesn't work + # V4L2 src self.detect_element('v4l2src', _('V4L2: %s')) # Funny things, just to test... # self.devices['GOOM'] = 'audiotestsrc ! goom' @@ -92,8 +95,7 @@ class VideoOutputManager(DeviceManager): # Fake video output self.detect_element('fakesink', _('Fake audio output')) # Auto sink - self.detect_element('autovideosink', _('Autodetect')) - # xvimage sink self.detect_element('xvimagesink', _('X Window System (X11/XShm/Xv): %s')) # ximagesink self.detect_element('ximagesink', _('X Window System (without Xv)')) + self.detect_element('autovideosink', _('Autodetect')) diff --git a/src/common/optparser.py b/src/common/optparser.py index b90304b4d..e89548163 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -2,7 +2,7 @@ ## src/common/optparser.py ## ## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org> -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> @@ -115,13 +115,16 @@ class OptionsParser: gajim.config.foreach(self.write_line, f) except IOError, e: return str(e) + f.flush() + os.fsync(f.fileno()) f.close() if os.path.exists(self.__filename): - # win32 needs this - try: - os.remove(self.__filename) - except Exception: - pass + if os.name == 'nt': + # win32 needs this + try: + os.remove(self.__filename) + except Exception: + pass try: os.rename(self.__tempfile, self.__filename) except IOError, e: @@ -704,7 +707,9 @@ class OptionsParser: """ Remove hardcoded ../data/sounds from config """ - dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR) + dirs = ['../data', gajim.gajimpaths.data_root, gajim.DATA_DIR] + if os.name != 'nt': + dirs.append(os.path.expanduser(u'~/.gajim')) for evt in gajim.config.get_per('soundevents'): path = gajim.config.get_per('soundevents', evt, 'path') # absolute and relative passes are necessary diff --git a/src/common/passwords.py b/src/common/passwords.py index 15607976d..1cebfee55 100644 --- a/src/common/passwords.py +++ b/src/common/passwords.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Gustavo J. A. M. Carneiro <gjcarneiro AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## Julien Pivotto <roidelapluie AT gmail.com> ## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de> diff --git a/src/common/pep.py b/src/common/pep.py index b417b77e0..255d0fe3a 100644 --- a/src/common/pep.py +++ b/src/common/pep.py @@ -2,7 +2,7 @@ ## src/common/pep.py ## ## Copyright (C) 2007 Piotr Gaczkowski <doomhammerng AT gmail.com> -## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> ## Jean-Marie Traissard <jim AT lapin.org> ## Jonathan Schleifer <js-common.gajim AT webkeks.org> @@ -544,7 +544,7 @@ class ConnectionPEP(object): items = event_tag.getTag('items') if items: for item in items.getTags('item'): - entry = item.getTag('entry') + entry = item.getTag('entry', namespace=xmpp.NS_ATOM) if entry: # for each entry in feed (there shouldn't be more than one, # but to be sure... diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index f70b50aa5..8277ed710 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -6,7 +6,7 @@ ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> ## Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com> ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> ## Jean-Marie Traissard <jim AT lapin.org> @@ -75,6 +75,190 @@ class ConnectionBytestream: def __init__(self): self.files_props = {} + def _ft_get_our_jid(self): + our_jid = gajim.get_jid_from_account(self.name) + resource = self.server_resource + return our_jid + '/' + resource + + def _ft_get_receiver_jid(self, file_props): + return file_props['receiver'].jid + '/' + file_props['receiver'].resource + + def _ft_get_from(self, iq_obj): + return helpers.get_full_jid_from_iq(iq_obj) + + def _ft_get_streamhost_jid_attr(self, streamhost): + return helpers.parse_jid(streamhost.getAttr('jid')) + + def send_file_request(self, file_props): + """ + Send iq for new FT request + """ + if not self.connection or self.connected < 2: + return + file_props['sender'] = self._ft_get_our_jid() + fjid = self._ft_get_receiver_jid(file_props) + iq = xmpp.Iq(to=fjid, typ='set') + iq.setID(file_props['sid']) + self.files_props[file_props['sid']] = file_props + si = iq.setTag('si', namespace=xmpp.NS_SI) + si.setAttr('profile', xmpp.NS_FILE) + si.setAttr('id', file_props['sid']) + file_tag = si.setTag('file', namespace=xmpp.NS_FILE) + file_tag.setAttr('name', file_props['name']) + file_tag.setAttr('size', file_props['size']) + desc = file_tag.setTag('desc') + if 'desc' in file_props: + desc.setData(file_props['desc']) + file_tag.setTag('range') + feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) + _feature = xmpp.DataForm(typ='form') + feature.addChild(node=_feature) + field = _feature.setField('stream-method') + field.setAttr('type', 'list-single') + field.addOption(xmpp.NS_BYTESTREAM) + self.connection.send(iq) + + def send_file_approval(self, file_props): + """ + Send iq, confirming that we want to download the file + """ + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') + iq.setAttr('id', file_props['request-id']) + si = iq.setTag('si', namespace=xmpp.NS_SI) + if 'offset' in file_props and file_props['offset']: + file_tag = si.setTag('file', namespace=xmpp.NS_FILE) + range_tag = file_tag.setTag('range') + range_tag.setAttr('offset', file_props['offset']) + feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) + _feature = xmpp.DataForm(typ='submit') + feature.addChild(node=_feature) + field = _feature.setField('stream-method') + field.delAttr('type') + field.setValue(xmpp.NS_BYTESTREAM) + self.connection.send(iq) + + def send_file_rejection(self, file_props, code='403', typ=None): + """ + Inform sender that we refuse to download the file + + typ is used when code = '400', in this case typ can be 'strean' for + invalid stream or 'profile' for invalid profile + """ + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error') + iq.setAttr('id', file_props['request-id']) + if code == '400' and typ in ('stream', 'profile'): + name = 'bad-request' + text = '' + else: + name = 'forbidden' + text = 'Offer Declined' + err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text) + if code == '400' and typ in ('stream', 'profile'): + if typ == 'stream': + err.setTag('no-valid-streams', namespace=xmpp.NS_SI) + else: + err.setTag('bad-profile', namespace=xmpp.NS_SI) + iq.addChild(node=err) + self.connection.send(iq) + + def _siResultCB(self, con, iq_obj): + file_props = self.files_props.get(iq_obj.getAttr('id')) + if not file_props: + return + if 'request-id' in file_props: + # we have already sent streamhosts info + return + file_props['receiver'] = self._ft_get_from(iq_obj) + si = iq_obj.getTag('si') + file_tag = si.getTag('file') + range_tag = None + if file_tag: + range_tag = file_tag.getTag('range') + if range_tag: + offset = range_tag.getAttr('offset') + if offset: + file_props['offset'] = int(offset) + length = range_tag.getAttr('length') + if length: + file_props['length'] = int(length) + feature = si.setTag('feature') + if feature.getNamespace() != xmpp.NS_FEATURE: + return + form_tag = feature.getTag('x') + form = xmpp.DataForm(node=form_tag) + field = form.getField('stream-method') + if field.getValue() != xmpp.NS_BYTESTREAM: + return + self._send_socks5_info(file_props) + raise xmpp.NodeProcessed + + def _siSetCB(self, con, iq_obj): + jid = self._ft_get_from(iq_obj) + file_props = {'type': 'r'} + file_props['sender'] = jid + file_props['request-id'] = unicode(iq_obj.getAttr('id')) + si = iq_obj.getTag('si') + profile = si.getAttr('profile') + mime_type = si.getAttr('mime-type') + if profile != xmpp.NS_FILE: + self.send_file_rejection(file_props, code='400', typ='profile') + raise xmpp.NodeProcessed + feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE) + if not feature_tag: + return + form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA) + if not form_tag: + return + form = dataforms.ExtendForm(node=form_tag) + for f in form.iter_fields(): + if f.var == 'stream-method' and f.type == 'list-single': + values = [o[1] for o in f.options] + if xmpp.NS_BYTESTREAM in values: + break + else: + self.send_file_rejection(file_props, code='400', typ='stream') + raise xmpp.NodeProcessed + file_tag = si.getTag('file') + for attribute in file_tag.getAttrs(): + if attribute in ('name', 'size', 'hash', 'date'): + val = file_tag.getAttr(attribute) + if val is None: + continue + file_props[attribute] = val + file_desc_tag = file_tag.getTag('desc') + if file_desc_tag is not None: + file_props['desc'] = file_desc_tag.getData() + + if mime_type is not None: + file_props['mime-type'] = mime_type + file_props['receiver'] = self._ft_get_our_jid() + file_props['sid'] = unicode(si.getAttr('id')) + file_props['transfered_size'] = [] + gajim.socks5queue.add_file_props(self.name, file_props) + self.dispatch('FILE_REQUEST', (jid, file_props)) + raise xmpp.NodeProcessed + + def _siErrorCB(self, con, iq_obj): + si = iq_obj.getTag('si') + profile = si.getAttr('profile') + if profile != xmpp.NS_FILE: + return + file_props = self.files_props.get(iq_obj.getAttr('id')) + if not file_props: + return + jid = self._ft_get_from(iq_obj) + file_props['error'] = -3 + self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) + raise xmpp.NodeProcessed + +class ConnectionSocks5Bytestream(ConnectionBytestream): + def send_success_connect_reply(self, streamhost): """ Send reply to the initiator of FT that we made a connection @@ -252,92 +436,6 @@ class ConnectionBytestream: else: return [] - def send_file_rejection(self, file_props, code='403', typ=None): - """ - Inform sender that we refuse to download the file - - typ is used when code = '400', in this case typ can be 'strean' for - invalid stream or 'profile' for invalid profile - """ - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error') - iq.setAttr('id', file_props['request-id']) - if code == '400' and typ in ('stream', 'profile'): - name = 'bad-request' - text = '' - else: - name = 'forbidden' - text = 'Offer Declined' - err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text) - if code == '400' and typ in ('stream', 'profile'): - if typ == 'stream': - err.setTag('no-valid-streams', namespace=xmpp.NS_SI) - else: - err.setTag('bad-profile', namespace=xmpp.NS_SI) - iq.addChild(node=err) - self.connection.send(iq) - - def send_file_approval(self, file_props): - """ - Send iq, confirming that we want to download the file - """ - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') - iq.setAttr('id', file_props['request-id']) - si = iq.setTag('si', namespace=xmpp.NS_SI) - if 'offset' in file_props and file_props['offset']: - file_tag = si.setTag('file', namespace=xmpp.NS_FILE) - range_tag = file_tag.setTag('range') - range_tag.setAttr('offset', file_props['offset']) - feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) - _feature = xmpp.DataForm(typ='submit') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.delAttr('type') - field.setValue(xmpp.NS_BYTESTREAM) - self.connection.send(iq) - - def _ft_get_our_jid(self): - our_jid = gajim.get_jid_from_account(self.name) - resource = self.server_resource - return our_jid + '/' + resource - - def _ft_get_receiver_jid(self, file_props): - return file_props['receiver'].jid + '/' + file_props['receiver'].resource - - def send_file_request(self, file_props): - """ - Send iq for new FT request - """ - if not self.connection or self.connected < 2: - return - file_props['sender'] = self._ft_get_our_jid() - fjid = self._ft_get_receiver_jid(file_props) - iq = xmpp.Iq(to=fjid, typ='set') - iq.setID(file_props['sid']) - self.files_props[file_props['sid']] = file_props - si = iq.setTag('si', namespace=xmpp.NS_SI) - si.setAttr('profile', xmpp.NS_FILE) - si.setAttr('id', file_props['sid']) - file_tag = si.setTag('file', namespace=xmpp.NS_FILE) - file_tag.setAttr('name', file_props['name']) - file_tag.setAttr('size', file_props['size']) - desc = file_tag.setTag('desc') - if 'desc' in file_props: - desc.setData(file_props['desc']) - file_tag.setTag('range') - feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) - _feature = xmpp.DataForm(typ='form') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.setAttr('type', 'list-single') - field.addOption(xmpp.NS_BYTESTREAM) - self.connection.send(iq) - def _result_socks5_sid(self, sid, hash_id): """ Store the result of SHA message from auth @@ -406,9 +504,6 @@ class ConnectionBytestream: self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) raise xmpp.NodeProcessed - def _ft_get_from(self, iq_obj): - return helpers.get_full_jid_from_iq(iq_obj) - def _bytestreamSetCB(self, con, iq_obj): target = unicode(iq_obj.getAttr('to')) id_ = unicode(iq_obj.getAttr('id')) @@ -466,9 +561,6 @@ class ConnectionBytestream: gajim.socks5queue.activate_proxy(host['idx']) raise xmpp.NodeProcessed - def _ft_get_streamhost_jid_attr(self, streamhost): - return helpers.parse_jid(streamhost.getAttr('jid')) - def _bytestreamResultCB(self, con, iq_obj): frm = self._ft_get_from(iq_obj) real_id = unicode(iq_obj.getAttr('id')) @@ -504,7 +596,7 @@ class ConnectionBytestream: raise xmpp.NodeProcessed if real_id.startswith('au_'): - if 'stopped' in file and file_props['stopped']: + if 'stopped' in file_props and file_props['stopped']: self.remove_transfer(file_props) else: gajim.socks5queue.send_file(file_props, self.name) @@ -516,6 +608,9 @@ class ConnectionBytestream: if proxyhost['jid'] == jid: proxy = proxyhost + if 'stopped' in file_props and file_props['stopped']: + self.remove_transfer(file_props) + raise xmpp.NodeProcessed if proxy is not None: file_props['streamhost-used'] = True if 'streamhosts' not in file_props: @@ -530,10 +625,7 @@ class ConnectionBytestream: raise xmpp.NodeProcessed else: - if 'stopped' in file_props and file_props['stopped']: - self.remove_transfer(file_props) - else: - gajim.socks5queue.send_file(file_props, self.name) + gajim.socks5queue.send_file(file_props, self.name) if 'fast' in file_props: fasts = file_props['fast'] if len(fasts) > 0: @@ -542,98 +634,7 @@ class ConnectionBytestream: raise xmpp.NodeProcessed - def _siResultCB(self, con, iq_obj): - file_props = self.files_props.get(iq_obj.getAttr('id')) - if not file_props: - return - if 'request-id' in file_props: - # we have already sent streamhosts info - return - file_props['receiver'] = self._ft_get_from(iq_obj) - si = iq_obj.getTag('si') - file_tag = si.getTag('file') - range_tag = None - if file_tag: - range_tag = file_tag.getTag('range') - if range_tag: - offset = range_tag.getAttr('offset') - if offset: - file_props['offset'] = int(offset) - length = range_tag.getAttr('length') - if length: - file_props['length'] = int(length) - feature = si.setTag('feature') - if feature.getNamespace() != xmpp.NS_FEATURE: - return - form_tag = feature.getTag('x') - form = xmpp.DataForm(node=form_tag) - field = form.getField('stream-method') - if field.getValue() != xmpp.NS_BYTESTREAM: - return - self._send_socks5_info(file_props) - raise xmpp.NodeProcessed - - def _siSetCB(self, con, iq_obj): - jid = self._ft_get_from(iq_obj) - file_props = {'type': 'r'} - file_props['sender'] = jid - file_props['request-id'] = unicode(iq_obj.getAttr('id')) - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - mime_type = si.getAttr('mime-type') - if profile != xmpp.NS_FILE: - self.send_file_rejection(file_props, code='400', typ='profile') - raise xmpp.NodeProcessed - feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE) - if not feature_tag: - return - form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA) - if not form_tag: - return - form = dataforms.ExtendForm(node=form_tag) - for f in form.iter_fields(): - if f.var == 'stream-method' and f.type == 'list-single': - values = [o[1] for o in f.options] - if xmpp.NS_BYTESTREAM in values: - break - else: - self.send_file_rejection(file_props, code='400', typ='stream') - raise xmpp.NodeProcessed - file_tag = si.getTag('file') - for attribute in file_tag.getAttrs(): - if attribute in ('name', 'size', 'hash', 'date'): - val = file_tag.getAttr(attribute) - if val is None: - continue - file_props[attribute] = val - file_desc_tag = file_tag.getTag('desc') - if file_desc_tag is not None: - file_props['desc'] = file_desc_tag.getData() - - if mime_type is not None: - file_props['mime-type'] = mime_type - file_props['receiver'] = self._ft_get_our_jid() - file_props['sid'] = unicode(si.getAttr('id')) - file_props['transfered_size'] = [] - gajim.socks5queue.add_file_props(self.name, file_props) - self.dispatch('FILE_REQUEST', (jid, file_props)) - raise xmpp.NodeProcessed - - def _siErrorCB(self, con, iq_obj): - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - if profile != xmpp.NS_FILE: - return - file_props = self.files_props.get(iq_obj.getAttr('id')) - if not file_props: - return - jid = self._ft_get_from(iq_obj) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) - raise xmpp.NodeProcessed - - -class ConnectionBytestreamZeroconf(ConnectionBytestream): +class ConnectionSocks5BytestreamZeroconf(ConnectionSocks5Bytestream): def _ft_get_from(self, iq_obj): return unicode(iq_obj.getFrom()) diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py index e30af586d..5176b460d 100644 --- a/src/common/proxy65_manager.py +++ b/src/common/proxy65_manager.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Jean-Marie Traissard <jim AT lapin.org> -## Copyright (C) 2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## @@ -83,6 +83,8 @@ class Proxy65Manager: host = item.getAttr('host') port = item.getAttr('port') jid = item.getAttr('jid') + if not host or not port or not jid: + self.proxies[proxy]._on_connect_failure() self.proxies[proxy].resolve_result(host, port, jid) # we can have only one streamhost raise common.xmpp.NodeProcessed @@ -113,8 +115,7 @@ class ProxyResolver: self.host = str(host) self.port = int(port) self.jid = unicode(jid) - self.state = S_RESOLVED - #FIXME: re-enable proxy testing + self.state = S_INITIAL log.info('start resolving %s:%s' % (self.host, self.port)) self.receiver_tester = ReceiverTester(self.host, self.port, self.jid, self.sid, self.sender_jid, self._on_receiver_success, diff --git a/src/common/pubsub.py b/src/common/pubsub.py index 2a75d34a9..7cd83ff0f 100644 --- a/src/common/pubsub.py +++ b/src/common/pubsub.py @@ -2,7 +2,7 @@ ## src/common/pubsub.py ## ## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de> ## @@ -109,25 +109,37 @@ class ConnectionPubSub: self.connection.send(query) - def send_pb_delete(self, jid, node): + def send_pb_purge(self, jid, node): + """ + Purge node: Remove all items + """ + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) + d = d.addChild('purge', {'node': node}) + + self.connection.send(query) + + def send_pb_delete(self, jid, node, on_ok=None, on_fail=None): """ Delete node """ if not self.connection or self.connected < 2: return query = xmpp.Iq('set', to=jid) - d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) d = d.addChild('delete', {'node': node}) def response(con, resp, jid, node): - if resp.getType() == 'result': - self.dispatch('PUBSUB_NODE_REMOVED', (jid, node)) - else: + if resp.getType() == 'result' and on_ok: + on_ok(jid, node) + elif on_fail: msg = resp.getErrorMsg() - self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg)) + on_fail(jid, node, msg) self.connection.SendAndCallForResponse(query, response, {'jid': jid, - 'node': node}) + 'node': node}) def send_pb_create(self, jid, node, configure = False, configure_form = None): """ diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py index 09f28a504..45e07c72b 100644 --- a/src/common/rst_xhtml_generator.py +++ b/src/common/rst_xhtml_generator.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Santiago Gala ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## ## This file is part of Gajim. diff --git a/src/common/sleepy.py b/src/common/sleepy.py index 7f115da21..f32ebf807 100644 --- a/src/common/sleepy.py +++ b/src/common/sleepy.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/common/sleepy.py ## -## Copyright (C) 2003-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Mateusz Biliński <mateusz AT bilinski.it> diff --git a/src/common/socks5.py b/src/common/socks5.py index bfeda862e..4d99cdb21 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index 129f60ea3..028bee596 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -1,9 +1,9 @@ # -*- coding:utf-8 -*- ## src/common/stanza_session.py ## +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com> -## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org> -## Brendan Taylor <whateley AT gmail.com> +## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> ## Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py index 2b982d091..670526bf9 100644 --- a/src/common/xmpp/auth_nb.py +++ b/src/common/xmpp/auth_nb.py @@ -29,6 +29,8 @@ import random import itertools import dispatcher_nb import hashlib +import hmac +import hashlib import logging log = logging.getLogger('gajim.c.x.auth_nb') @@ -113,6 +115,8 @@ def challenge_splitter(data): quotes_open = False return dict_ +def scram_parse(chatter): + return dict(s.split('=', 1) for s in chatter.split(',')) class SASL(PlugIn): """ @@ -187,7 +191,7 @@ class SASL(PlugIn): """ if not feats.getTag('mechanisms', namespace=NS_SASL): self.startsasl='not-supported' - log.error('SASL not supported by server') + log.info('SASL not supported by server') return self.mecs = [] for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( @@ -209,9 +213,12 @@ class SASL(PlugIn): raise NodeProcessed if "EXTERNAL" in self.mecs: self.mecs.remove('EXTERNAL') - node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'EXTERNAL'}, - payload=[base64.encodestring('%s@%s' % (self.username, - self._owner.Server)).replace('\n', '')]) + sasl_data = u'%s@%s' % (self.username, self._owner.Server) + sasl_data = sasl_data.encode('utf-8').encode('base64').replace( + '\n', '') + node = Node('auth', attrs={'xmlns': NS_SASL, + 'mechanism': 'EXTERNAL'}, payload=[sasl_data]) + self.mechanism = 'EXTERNAL' self.startsasl = SASL_IN_PROCESS self._owner.send(str(node)) raise NodeProcessed @@ -231,6 +238,13 @@ class SASL(PlugIn): raise NodeProcessed except kerberos.GSSError, e: log.info('GSSAPI authentication failed: %s' % str(e)) + if 'SCRAM-SHA-1' in self.mecs: + self.mecs.remove('SCRAM-SHA-1') + self.mechanism = 'SCRAM-SHA-1' + self._owner._caller.get_password(self.set_password, self.mechanism) + self.scram_step = 0 + self.startsasl = SASL_IN_PROCESS + raise NodeProcessed if 'DIGEST-MD5' in self.mecs: self.mecs.remove('DIGEST-MD5') node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) @@ -241,12 +255,12 @@ class SASL(PlugIn): if 'PLAIN' in self.mecs: self.mecs.remove('PLAIN') self.mechanism = 'PLAIN' - self._owner._caller.get_password(self.set_password) + self._owner._caller.get_password(self.set_password, self.mechanism) self.startsasl = SASL_IN_PROCESS raise NodeProcessed self.startsasl = SASL_FAILURE - log.error('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN ' - 'mecanisms.') + log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and ' + 'PLAIN mecanisms.') if self.on_sasl: self.on_sasl() return @@ -264,7 +278,7 @@ class SASL(PlugIn): reason = challenge.getChildren()[0] except Exception: reason = challenge - log.error('Failed SASL authentification: %s' % reason) + log.info('Failed SASL authentification: %s' % reason) if len(self.mecs) > 0: # There are other mechanisms to test self.MechanismHandler() @@ -273,6 +287,8 @@ class SASL(PlugIn): self.on_sasl() raise NodeProcessed elif challenge.getName() == 'success': + # TODO: Need to validate any data-with-success. + # TODO: Important for DIGEST-MD5 and SCRAM. self.startsasl = SASL_SUCCESS log.info('Successfully authenticated with remote server.') handlers = self._owner.Dispatcher.dumpHandlers() @@ -310,9 +326,73 @@ class SASL(PlugIn): response = kerberos.authGSSClientResponse(self.gss_vc) if not response: response = '' - self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, + self._owner.send(Node('response', attrs={'xmlns': NS_SASL}, payload=response).__str__()) raise NodeProcessed + if self.mechanism == 'SCRAM-SHA-1': + hashfn = hashlib.sha1 + + def HMAC(k, s): + return hmac.HMAC(key=k, msg=s, digestmod=hashfn).digest() + + def XOR(x, y): + r = (chr(ord(px) ^ ord(py)) for px, py in zip(x, y)) + return ''.join(r) + + def Hi(s, salt, iters): + ii = 1 + try: + s = s.encode('utf-8') + except: + pass + ui_1 = HMAC(s, salt + '\0\0\0\01') + ui = ui_1 + for i in range(iters - 1): + ii += 1 + ui_1 = HMAC(s, ui_1) + ui = XOR(ui, ui_1) + return ui + + def H(s): + return hashfn(s).digest() + + def scram_base64(s): + return ''.join(s.encode('base64').split('\n')) + + if self.scram_step == 0: + self.scram_step = 1 + self.scram_soup += ',' + data + ',' + data = scram_parse(data) + # TODO: Should check cnonce here. + # TODO: Channel binding data goes in here too. + r = 'c=' + scram_base64(self.scram_gs2) + r += ',r=' + data['r'] + self.scram_soup += r + salt = data['s'].decode('base64') + iter = int(data['i']) + SaltedPassword = Hi(self.password, salt, iter) + # TODO: Could cache this, along with salt+iter. + ClientKey = HMAC(SaltedPassword, 'Client Key') + StoredKey = H(ClientKey) + ClientSignature = HMAC(StoredKey, self.scram_soup) + ClientProof = XOR(ClientKey, ClientSignature) + r += ',p=' + scram_base64(ClientProof) + ServerKey = HMAC(SaltedPassword, 'Server Key') + self.scram_ServerSignature = HMAC(ServerKey, self.scram_soup) + sasl_data = scram_base64(r) + node = Node('response', attrs={'xmlns': NS_SASL}, + payload=[sasl_data]) + self._owner.send(str(node)) + raise NodeProcessed + + if self.scram_step == 1: + data = scram_parse(data) + if data['v'].decode('base64') != self.scram_ServerSignature: + # TODO: Not clear what to do here - need to abort. + raise Exception + node = Node('response', attrs={'xmlns': NS_SASL}); + self._owner.send(str(node)) + raise NodeProcessed # magic foo... chal = challenge_splitter(data) @@ -335,31 +415,39 @@ class SASL(PlugIn): self.resp['digest-uri'] = 'xmpp/' + self._owner.Server self.resp['charset'] = 'utf-8' # Password is now required - self._owner._caller.get_password(self.set_password) + self._owner._caller.get_password(self.set_password, self.mechanism) elif 'rspauth' in chal: self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) else: self.startsasl = SASL_FAILURE - log.error('Failed SASL authentification: unknown challenge') + log.info('Failed SASL authentification: unknown challenge') if self.on_sasl: self.on_sasl() raise NodeProcessed + @staticmethod + def _convert_to_iso88591(string): + try: + string = string.decode('utf-8').encode('iso-8859-1') + except UnicodeEncodeError: + pass + return string + def set_password(self, password): - if password is None: - self.password = '' - else: - self.password = password - if self.mechanism == 'DIGEST-MD5': - def convert_to_iso88591(string): - try: - string = string.decode('utf-8').encode('iso-8859-1') - except UnicodeEncodeError: - pass - return string - hash_username = convert_to_iso88591(self.resp['username']) - hash_realm = convert_to_iso88591(self.resp['realm']) - hash_password = convert_to_iso88591(self.password) + self.password = '' if password is None else password + if self.mechanism == 'SCRAM-SHA-1': + nonce = ''.join('%x' % randint(0, 2 ** 28) for randint in \ + itertools.repeat(random.randint, 7)) + self.scram_soup = 'n=' + self.username + ',r=' + nonce + self.scram_gs2 = 'n,,' # No CB yet. + sasl_data = (self.scram_gs2 + self.scram_soup).encode('base64').\ + replace('\n', '') + node = Node('auth', attrs={'xmlns': NS_SASL, + 'mechanism': self.mechanism}, payload=[sasl_data]) + elif self.mechanism == 'DIGEST-MD5': + hash_username = self._convert_to_iso88591(self.resp['username']) + hash_realm = self._convert_to_iso88591(self.resp['realm']) + hash_password = self._convert_to_iso88591(self.password) A1 = C([H(C([hash_username, hash_realm, hash_password])), self.resp['nonce'], self.resp['cnonce']]) A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) @@ -377,8 +465,7 @@ class SASL(PlugIn): '\r', '').replace('\n', '') node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) elif self.mechanism == 'PLAIN': - sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ - self._owner.Server, self.username, self.password) + sasl_data = u'\x00%s\x00%s' % (self.username, self.password) sasl_data = sasl_data.encode('utf-8').encode('base64').replace( '\n', '') node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, @@ -419,7 +506,7 @@ class NonBlockingNonSASL(PlugIn): def _on_username(self, resp): if not isResultNode(resp): - log.error('No result node arrived! Aborting...') + log.info('No result node arrived! Aborting...') return self.on_auth(None) iq=Iq(typ='set', node=resp) @@ -450,7 +537,7 @@ class NonBlockingNonSASL(PlugIn): query.setTagData('hash', hash_) self._method='0k' else: - log.warn("Sequre methods unsupported, performing plain text \ + log.warn("Secure methods unsupported, performing plain text \ authentication") query.setTagData('password', self.password) self._method = 'plain' @@ -464,7 +551,7 @@ class NonBlockingNonSASL(PlugIn): self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\ '/'+self.owner.Resource return self.on_auth(self._method) - log.error('Authentication failed!') + log.info('Authentication failed!') return self.on_auth(None) @@ -496,7 +583,7 @@ class NonBlockingBind(PlugIn): attributes accordingly """ if not feats.getTag('bind', namespace=NS_BIND): - log.error('Server does not requested binding.') + log.info('Server does not requested binding.') # we try to bind resource anyway #self.bound='failure' self.bound = [] @@ -548,10 +635,10 @@ class NonBlockingBind(PlugIn): func=self._on_session) return if resp: - log.error('Binding failed: %s.' % resp.getTag('error')) + log.info('Binding failed: %s.' % resp.getTag('error')) self.on_bound(None) else: - log.error('Binding failed: timeout expired.') + log.info('Binding failed: timeout expired.') self.on_bound(None) def _on_session(self, resp): diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py index ea0e43fb1..44ea3ba3e 100644 --- a/src/common/xmpp/client_nb.py +++ b/src/common/xmpp/client_nb.py @@ -362,6 +362,9 @@ class NonBlockingClient: supported and desired. """ self.stream_started = True + if not hasattr(self, 'onreceive'): + # we may already have been disconnected + return self.onreceive(None) if self.connected == 'plain': @@ -372,7 +375,7 @@ class NonBlockingClient: # try to negotiate TLS if self.incoming_stream_version() != '1.0': # if stream version is less than 1.0, we can't do more - log.warn('While connecting with type = "tls": stream version ' + + log.info('While connecting with type = "tls": stream version ' + 'is less than 1.0') self._on_connect() return @@ -382,7 +385,7 @@ class NonBlockingClient: log.info('TLS supported by remote server. Requesting TLS start.') self._tls_negotiation_handler() else: - log.warn('While connecting with type = "tls": TLS unsupported ' + + log.info('While connecting with type = "tls": TLS unsupported ' + 'by remote server') self._on_connect() diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py index f5972e3ce..54274928a 100644 --- a/src/common/xmpp/dispatcher_nb.py +++ b/src/common/xmpp/dispatcher_nb.py @@ -469,10 +469,6 @@ class XMPPDispatcher(PlugIn): # we have released dispatcher, so self._owner has no methods if not res: return - if 'remove_timeout' in self._owner.__dict__: - # When we receive data after we started disconnecting, Transport may - # already be plugged out - self._owner.remove_timeout() for (_id, _iq) in self._expected.items(): if _iq is None: # If the expected Stanza would have arrived, ProcessNonBlocking diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py index 5284ad8c7..1b0bccb04 100644 --- a/src/common/xmpp/idlequeue.py +++ b/src/common/xmpp/idlequeue.py @@ -215,7 +215,7 @@ class IdleQueue: # (timeout, boolean) # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (200, False) + PROCESS_TIMEOUT = (100, False) def __init__(self): self.queue = {} diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 26f795966..ceaa49c9a 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -28,13 +28,16 @@ NS_ADDRESS ='http://jabber.org/protocol/address' NS_AGENTS ='jabber:iq:agents' NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' +NS_ATOM ='http://www.w3.org/2005/Atom' NS_AUTH ='jabber:iq:auth' NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' +NS_BOB ='urn:xmpp:bob' #XEP-0231 NS_BROWSE ='jabber:iq:browse' NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 +NS_CAPTCHA ='urn:xmpp:captcha' # XEP-0158 NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 NS_CLIENT ='jabber:client' @@ -44,6 +47,7 @@ NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0' NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138 NS_CONFERENCE ='jabber:x:conference' NS_DATA ='jabber:x:data' # XEP-0004 +NS_DATA_MEDIA ='urn:xmpp:media-element' # XEP-0221 NS_DELAY ='jabber:x:delay' NS_DELAY2 ='urn:xmpp:delay' NS_DIALBACK ='jabber:server:dialback' @@ -96,8 +100,11 @@ NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' NS_REGISTER ='jabber:iq:register' NS_ROSTER ='jabber:iq:roster' NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 +NS_ROSTER_VER ='urn:xmpp:features:rosterver' # XEP-0273 NS_RPC ='jabber:iq:rpc' # XEP-0009 NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' +NS_SECLABEL ='urn:xmpp:sec-label:0' +NS_SECLABEL_CATALOG ='urn:xmpp:sec-label:catalog:0' NS_SEARCH ='jabber:iq:search' NS_SERVER ='jabber:server' NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py index e90a1820a..c5a7bb463 100644 --- a/src/common/xmpp/roster_nb.py +++ b/src/common/xmpp/roster_nb.py @@ -38,7 +38,7 @@ class NonBlockingRoster(PlugIn): internal representation of contacts in roster """ - def __init__(self, version=''): + def __init__(self, version=None): """ Init internal variables """ @@ -60,7 +60,8 @@ class NonBlockingRoster(PlugIn): return iq = Iq('get', NS_ROSTER) - iq.setTagAttr('query', 'ver', self.version) + if self.version is not None: + iq.setTagAttr('query', 'ver', self.version) id_ = self._owner.getAnID() iq.setID(id_) self._owner.send(iq) @@ -341,6 +342,9 @@ class NonBlockingRoster(PlugIn): self._owner.Dispatcher.ProcessNonBlocking(data) if not self.set: return + if not self._owner: + # Connection has been closed by receiving a <stream:error> for ex, + return self._owner.onreceive(None) if self.on_ready: self.on_ready(self) diff --git a/src/common/xmpp/stringprepare.py b/src/common/xmpp/stringprepare.py index 5d0610d4b..bae16fcec 100644 --- a/src/common/xmpp/stringprepare.py +++ b/src/common/xmpp/stringprepare.py @@ -2,7 +2,7 @@ ## src/common/xmpp/stringprepare.py ## ## Copyright (C) 2001-2005 Twisted Matrix Laboratories -## Copyright (C) 2005-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de> ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> ## diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py index 9c54eb07d..1cbb99c57 100644 --- a/src/common/xmpp/tls_nb.py +++ b/src/common/xmpp/tls_nb.py @@ -349,8 +349,44 @@ class NonBlockingTLS(PlugIn): def _startSSL_pyOpenSSL(self): log.debug("_startSSL_pyOpenSSL called") tcpsock = self._owner - # See http://docs.python.org/dev/library/ssl.html - tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + # NonBlockingHTTPBOSH instance has no attribute _owner + if hasattr(tcpsock, '_owner') and tcpsock._owner._caller.client_cert \ + and os.path.exists(tcpsock._owner._caller.client_cert): + conn = tcpsock._owner._caller + # FIXME make a checkbox for Client Cert / SSLv23 / TLSv1 + # If we are going to use a client cert/key pair for authentication, + # we choose TLSv1 method. + tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) + log.debug('Using client cert and key from %s' % conn.client_cert) + try: + p12 = OpenSSL.crypto.load_pkcs12(open(conn.client_cert).read()) + except OpenSSL.crypto.Error, exception_obj: + log.warning('Unable to load client pkcs12 certificate from ' + 'file %s: %s ... Is it a valid PKCS12 cert?' % \ + (conn.client_cert, exception_obj.args)) + except: + log.warning('Unknown error while loading certificate from file ' + '%s' % conn.client_cert) + else: + log.info('PKCS12 Client cert loaded OK') + try: + tcpsock._sslContext.use_certificate(p12.get_certificate()) + tcpsock._sslContext.use_privatekey(p12.get_privatekey()) + log.info('p12 cert and key loaded') + except OpenSSL.crypto.Error, exception_obj: + log.warning('Unable to extract client certificate from ' + 'file %s' % conn.client_cert) + except Exception, msg: + log.warning('Unknown error extracting client certificate ' + 'from file %s: %s' % (conn.client_cert, msg)) + else: + log.info('client cert and key loaded OK') + else: + # See http://docs.python.org/dev/library/ssl.html + tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + tcpsock._sslContext.set_options(OpenSSL.SSL.OP_NO_SSLv2 | \ + OpenSSL.SSL.OP_NO_TICKET) + tcpsock.ssl_errnum = 0 tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback) diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index 8d0368abc..2b0a57c9d 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -223,7 +223,7 @@ class NonBlockingTransport(PlugIn): if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'): self.on_receive = self._owner.Dispatcher.ProcessNonBlocking else: - log.warning('No Dispatcher plugged. Received data will not be processed') + log.warn('No Dispatcher plugged. Received data will not be processed') self.on_receive = None return self.on_receive = recv_handler @@ -342,6 +342,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject): # variable for errno symbol that will be found from exception raised # from connect() errnum = 0 + errstr = str() # set timeout for TCP connecting - if nonblocking connect() fails, pollend # is called. If if succeeds pollout is called. @@ -350,8 +351,8 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject): try: self._sock.setblocking(False) self._sock.connect((self.server, self.port)) - except Exception, (errnum, errstr): - pass + except Exception, exc: + errnum, errstr = exc.args if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): # connecting in progress @@ -453,7 +454,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject): self._sock.shutdown(socket.SHUT_RDWR) self._sock.close() except socket.error, (errnum, errstr): - log.error('Error while disconnecting socket: %s' % errstr) + log.info('Error while disconnecting socket: %s' % errstr) self.fd = -1 NonBlockingTransport.disconnect(self, do_callback) @@ -542,7 +543,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject): readable=True) self.raise_event(DATA_SENT, sent_data) - except socket.error, e: + except Exception: log.error('_do_send:', exc_info=True) traceback.print_exc() self.disconnect() diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py index adc3f45d0..ffad257c3 100644 --- a/src/common/zeroconf/client_zeroconf.py +++ b/src/common/zeroconf/client_zeroconf.py @@ -30,6 +30,8 @@ from common.xmpp.protocol import * import socket import errno import sys +import string +from random import Random import logging log = logging.getLogger('gajim.c.z.client_zeroconf') @@ -317,6 +319,9 @@ class P2PClient(IdleObject): common.xmpp.NS_BYTESTREAM) self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get', common.xmpp.NS_DISCO_ITEMS) + self.RegisterHandler('iq', self._caller._JingleCB, 'result') + self.RegisterHandler('iq', self._caller._JingleCB, 'error') + self.RegisterHandler('iq', self._caller._JingleCB, 'set', common.xmpp.NS_JINGLE) class P2PConnection(IdleObject, PlugIn): def __init__(self, sock_hash, _sock, host=None, port=None, caller=None, @@ -412,7 +417,6 @@ class P2PConnection(IdleObject, PlugIn): If supplied data is unicode string, encode it to UTF-8. """ - print 'ici' if self.state <= 0: return @@ -726,7 +730,8 @@ class ClientZeroconf: def send(self, stanza, is_message=False, now=False, on_ok=None, on_not_ok=None): stanza.setFrom(self.roster.zeroconf.name) - to = stanza.getTo() + to = unicode(stanza.getTo()) + to = gajim.get_jid_without_resource(to) try: item = self.roster[to] @@ -759,6 +764,12 @@ class ClientZeroconf: P2PClient(None, item['address'], item['port'], self, [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) + def getAnID(self): + """ + Generate a random id + """ + return ''.join(Random().sample(string.letters + string.digits, 6)) + def RegisterDisconnectHandler(self, handler): """ Register handler that will be called on disconnect diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py index 9d9f8356a..ace1b771e 100644 --- a/src/common/zeroconf/connection_handlers_zeroconf.py +++ b/src/common/zeroconf/connection_handlers_zeroconf.py @@ -35,7 +35,7 @@ from common import gajim from common.zeroconf import zeroconf from common.commands import ConnectionCommands from common.pep import ConnectionPEP -from common.protocol.bytestream import ConnectionBytestreamZeroconf +from common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf import logging log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf') @@ -70,12 +70,14 @@ class ConnectionVcard(connection_handlers.ConnectionVcard): pass -class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestreamZeroconf, -ConnectionCommands, ConnectionPEP, connection_handlers.ConnectionHandlersBase): +class ConnectionHandlersZeroconf(ConnectionVcard, +ConnectionSocks5BytestreamZeroconf, ConnectionCommands, ConnectionPEP, +connection_handlers.ConnectionHandlersBase, connection_handlers.ConnectionJingle): def __init__(self): ConnectionVcard.__init__(self) - ConnectionBytestreamZeroconf.__init__(self) + ConnectionSocks5BytestreamZeroconf.__init__(self) ConnectionCommands.__init__(self) + connection_handlers.ConnectionJingle.__init__(self) connection_handlers.ConnectionHandlersBase.__init__(self) try: diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index 6076f8b13..484b810a4 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -7,11 +7,9 @@ ## - Travis Shirk <travis@pobox.com> ## - Stefan Bethge <stefan@lanpartei.de> ## -## Copyright (C) 2003-2004 Yann Leboulanger <asterix@lagaule.org> -## Vincent Hanquez <tab@snarc.org> -## Copyright (C) 2006 Yann Leboulanger <asterix@lagaule.org> -## Vincent Hanquez <tab@snarc.org> -## Nikos Kouremenos <nkour@jabber.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix@lagaule.org> +## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org> +## Copyright (C) 2006 Nikos Kouremenos <nkour@jabber.org> ## Dimitur Kirov <dkirov@gmail.com> ## Travis Shirk <travis@pobox.com> ## Norman Rasmussen <norman@rasmussen.co.za> @@ -311,8 +309,9 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf): def send_message(self, jid, msg, keyID, type_='chat', subject='', chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None, callback_args=[]): + user_nick=None, xhtml=None, label=None, session=None, forward_from=None, + form_node=None, original_message=None, delayed=None, callback=None, + callback_args=[], now=True): def on_send_ok(msg_id): self.dispatch('MSGSENT', (jid, msg, keyID)) diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py index b26f984a2..1e57c0176 100644 --- a/src/common/zeroconf/zeroconf_bonjour.py +++ b/src/common/zeroconf/zeroconf_bonjour.py @@ -274,9 +274,9 @@ class Zeroconf: def disconnect(self): if self.connected: self.connected = False - self.browse_sdRef.close() - self.remove_announce() - + if hasattr(self, 'browse_sdRef'): + self.browse_sdRef.close() + self.remove_announce() def browse_domain(self, domain=None): gajim.log.debug('starting to browse') diff --git a/src/config.py b/src/config.py index e2253f49b..56266a07c 100644 --- a/src/config.py +++ b/src/config.py @@ -2,7 +2,7 @@ ## src/config.py ## ## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org> -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com> ## Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> @@ -429,27 +429,45 @@ class PreferencesWindow: buf.connect('changed', self.on_msg_textview_changed) ### Audio / Video tab ### - def create_av_combobox(opt_name, device_dict): + def create_av_combobox(opt_name, device_dict, config_name=None, + key=None): combobox = self.xml.get_object(opt_name + '_combobox') cell = gtk.CellRendererText() combobox.pack_start(cell, True) combobox.add_attribute(cell, 'text', 0) model = gtk.ListStore(str, str) combobox.set_model(model) + if config_name: + config = gajim.config.get(config_name) + else: + config = gajim.config.get(opt_name + '_device') - for index, (name, value) in enumerate(sorted(device_dict.iteritems())): + for index, (name, value) in enumerate(sorted(device_dict.\ + iteritems(), key=key)): model.append((name, value)) - if gajim.config.get(opt_name + '_device') == value: + if config == value: combobox.set_active(index) if HAS_GST: create_av_combobox('audio_input', AudioInputManager().get_devices()) - create_av_combobox('audio_output', AudioOutputManager().get_devices()) + create_av_combobox('audio_output', AudioOutputManager().get_devices( + )) create_av_combobox('video_input', VideoInputManager().get_devices()) - create_av_combobox('video_output', VideoOutputManager().get_devices()) + create_av_combobox('video_output', VideoOutputManager().get_devices( + )) + + create_av_combobox('video_framerate', {_('Default'): '', + '15fps': '15/1', '10fps': '10/1', '5fps': '5/1', + '2.5fps': '5/2'}, 'video_framerate', key=lambda x: -1 if \ + not x[1] else float(x[0][:-3])) + create_av_combobox('video_size', {_('Default'): '', + '800x600': '800x600', '640x480': '640x480', + '320x240': '320x240'}, 'video_size', key=lambda x: -1 if \ + not x[1] else int(x[0][:3])) + else: for opt_name in ('audio_input', 'audio_output', 'video_input', - 'video_output'): + 'video_output', 'video_framerate', 'video_size'): combobox = self.xml.get_object(opt_name + '_combobox') combobox.set_sensitive(False) @@ -1076,23 +1094,29 @@ class PreferencesWindow: def on_msg_treemodel_row_deleted(self, model, path): self.save_status_messages(model) - def on_av_combobox_changed(self, combobox, opt_name): + def on_av_combobox_changed(self, combobox, config_name): model = combobox.get_model() active = combobox.get_active() device = model[active][1].decode('utf-8') - gajim.config.set(opt_name + '_device', device) + gajim.config.set(config_name, device) def on_audio_input_combobox_changed(self, widget): - self.on_av_combobox_changed(widget, 'audio_input') + self.on_av_combobox_changed(widget, 'audio_input_device') def on_audio_output_combobox_changed(self, widget): - self.on_av_combobox_changed(widget, 'audio_output') + self.on_av_combobox_changed(widget, 'audio_output_device') def on_video_input_combobox_changed(self, widget): - self.on_av_combobox_changed(widget, 'video_input') + self.on_av_combobox_changed(widget, 'video_input_device') def on_video_output_combobox_changed(self, widget): - self.on_av_combobox_changed(widget, 'video_output') + self.on_av_combobox_changed(widget, 'video_output_device') + + def on_video_framerate_combobox_changed(self, widget): + self.on_av_combobox_changed(widget, 'video_framerate') + + def on_video_size_combobox_changed(self, widget): + self.on_av_combobox_changed(widget, 'video_size') def on_stun_checkbutton_toggled(self, widget): self.on_checkbutton_toggled(widget, 'use_stun_server', @@ -1612,13 +1636,15 @@ class AccountsWindow: focused_widget = self.window.get_focus() focused_widget_name = focused_widget.get_name() if focused_widget_name in ('jid_entry1', 'resource_entry1', - 'custom_port_entry'): + 'custom_port_entry', 'cert_entry1'): if focused_widget_name == 'jid_entry1': func = self.on_jid_entry1_focus_out_event elif focused_widget_name == 'resource_entry1': func = self.on_resource_entry1_focus_out_event elif focused_widget_name == 'custom_port_entry': func = self.on_custom_port_entry_focus_out_event + elif focused_widget_name == 'cert_entry1': + func = self.on_cert_entry1_focus_out_event if func(focused_widget, None): # Error detected in entry, don't change account, re-put cursor on # previous row @@ -1641,6 +1667,22 @@ class AccountsWindow: self.init_account() self.update_proxy_list() + def on_browse_for_client_cert_button_clicked(self, widget, data=None): + def on_ok(widget, path_to_clientcert_file): + self.dialog.destroy() + if not path_to_clientcert_file: + return + self.xml.get_object('cert_entry1').set_text(path_to_clientcert_file) + gajim.config.set_per('accounts', self.current_account, + 'client_cert', path_to_clientcert_file) + + def on_cancel(widget): + self.dialog.destroy() + + path_to_clientcert_file = self.xml.get_object('cert_entry1').get_text() + self.dialog = dialogs.ClientCertChooserDialog(path_to_clientcert_file, + on_ok, on_cancel) + def update_proxy_list(self): if self.current_account: our_proxy = gajim.config.get_per('accounts', self.current_account, @@ -1796,10 +1838,14 @@ class AccountsWindow: # Account tab self.draw_normal_jid() self.xml.get_object('resource_entry1').set_text(gajim.config.get_per( - 'accounts', account, 'resource')) + 'accounts', account, 'resource')) + + client_cert = gajim.config.get_per('accounts', account, 'client_cert') + self.xml.get_object('cert_entry1').set_text(client_cert) + self.xml.get_object('adjust_priority_with_status_checkbutton1').\ - set_active(gajim.config.get_per('accounts', account, - 'adjust_priority_with_status')) + set_active(gajim.config.get_per('accounts', account, + 'adjust_priority_with_status')) spinbutton = self.xml.get_object('priority_spinbutton1') if gajim.config.get('enable_negative_priority'): spinbutton.set_range(-128, 127) @@ -1893,9 +1939,9 @@ class AccountsWindow: return win_opened = False - if gajim.interface.msg_win_mgr.get_controls(acct = account): + if gajim.interface.msg_win_mgr.get_controls(acct=account): win_opened = True - else: + elif account in gajim.interface.instances: for key in gajim.interface.instances[account]: if gajim.interface.instances[account][key] and key != \ 'remove_account': @@ -1903,10 +1949,13 @@ class AccountsWindow: break # Detect if we have opened windows for this account def remove(account): - if 'remove_account' in gajim.interface.instances[account]: + if account in gajim.interface.instances and \ + 'remove_account' in gajim.interface.instances[account]: gajim.interface.instances[account]['remove_account'].window.\ - present() + present() else: + if not account in gajim.interface.instances: + gajim.interface.instances[account] = {} gajim.interface.instances[account]['remove_account'] = \ RemoveAccountWindow(account) if win_opened: @@ -2071,6 +2120,15 @@ class AccountsWindow: gajim.config.set_per('accounts', self.current_account, 'hostname', jid_splited[1]) + def on_cert_entry1_focus_out_event(self, widget, event): + if self.ignore_events: + return + client_cert = widget.get_text() + if self.option_changed('client_cert', client_cert): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'client_cert', + client_cert) + def on_anonymous_checkbutton1_toggled(self, widget): if self.ignore_events: return @@ -2871,12 +2929,19 @@ class RemoveAccountWindow: def on_remove_button_clicked(self, widget): def remove(): - if gajim.connections[self.account].connected and \ + if self.account in gajim.connections and \ + gajim.connections[self.account].connected and \ not self.remove_and_unregister_radiobutton.get_active(): # change status to offline only if we will not remove this JID from # server gajim.connections[self.account].change_status('offline', 'offline') if self.remove_and_unregister_radiobutton.get_active(): + if not self.account in gajim.connections: + dialogs.ErrorDialog( + _('Account is disabled'), + _('To unregister from a server, account must be ' + 'enabled.')) + return if not gajim.connections[self.account].password: def on_ok(passphrase, checked): if passphrase == -1: @@ -2896,11 +2961,12 @@ class RemoveAccountWindow: else: self._on_remove_success(True) - if gajim.connections[self.account].connected: + if self.account in gajim.connections and \ + gajim.connections[self.account].connected: dialogs.ConfirmationDialog( - _('Account "%s" is connected to the server') % self.account, - _('If you remove it, the connection will be lost.'), - on_response_ok=remove) + _('Account "%s" is connected to the server') % self.account, + _('If you remove it, the connection will be lost.'), + on_response_ok=remove) else: remove() @@ -2920,29 +2986,31 @@ class RemoveAccountWindow: on_response_ok=self.on_remove_responce_ok, is_modal=False) return # Close all opened windows - gajim.interface.roster.close_all(self.account, force = True) - gajim.connections[self.account].disconnect(on_purpose = True) - del gajim.connections[self.account] + gajim.interface.roster.close_all(self.account, force=True) + if self.account in gajim.connections: + gajim.connections[self.account].disconnect(on_purpose=True) + del gajim.connections[self.account] gajim.logger.remove_roster(gajim.get_jid_from_account(self.account)) gajim.config.del_per('accounts', self.account) gajim.interface.save_config() del gajim.interface.instances[self.account] - del gajim.interface.minimized_controls[self.account] - del gajim.nicks[self.account] - del gajim.block_signed_in_notifications[self.account] - del gajim.groups[self.account] - gajim.contacts.remove_account(self.account) - del gajim.gc_connected[self.account] - del gajim.automatic_rooms[self.account] - del gajim.to_be_removed[self.account] - del gajim.newly_added[self.account] - del gajim.sleeper_state[self.account] - del gajim.encrypted_chats[self.account] - del gajim.last_message_time[self.account] - del gajim.status_before_autoaway[self.account] - del gajim.transport_avatar[self.account] - del gajim.gajim_optional_features[self.account] - del gajim.caps_hash[self.account] + if self.account in gajim.nicks: + del gajim.interface.minimized_controls[self.account] + del gajim.nicks[self.account] + del gajim.block_signed_in_notifications[self.account] + del gajim.groups[self.account] + gajim.contacts.remove_account(self.account) + del gajim.gc_connected[self.account] + del gajim.automatic_rooms[self.account] + del gajim.to_be_removed[self.account] + del gajim.newly_added[self.account] + del gajim.sleeper_state[self.account] + del gajim.encrypted_chats[self.account] + del gajim.last_message_time[self.account] + del gajim.status_before_autoaway[self.account] + del gajim.transport_avatar[self.account] + del gajim.gajim_optional_features[self.account] + del gajim.caps_hash[self.account] if len(gajim.connections) >= 2: # Do not merge accounts if only one exists gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') else: @@ -3306,18 +3374,17 @@ class AccountCreationWizardWindow: self.window.set_transient_for(gajim.interface.roster.window) completion = gtk.EntryCompletion() + completion1 = gtk.EntryCompletion() # Connect events from comboboxentry.child server_comboboxentry = self.xml.get_object('server_comboboxentry') entry = server_comboboxentry.child entry.connect('key_press_event', - self.on_server_comboboxentry_key_press_event, server_comboboxentry) + self.on_server_comboboxentry_key_press_event, server_comboboxentry) entry.set_completion(completion) # Do the same for the other server comboboxentry server_comboboxentry1 = self.xml.get_object('server_comboboxentry1') entry = server_comboboxentry1.child - entry.connect('key_press_event', - self.on_server_comboboxentry_key_press_event, server_comboboxentry1) - entry.set_completion(completion) + entry.set_completion(completion1) self.update_proxy_list() @@ -3331,6 +3398,8 @@ class AccountCreationWizardWindow: completion.set_model(servers_model) completion.set_text_column(0) + completion1.set_model(servers_model) + completion1.set_text_column(0) # Put servers into comboboxentries server_comboboxentry.set_model(servers_model) @@ -3347,9 +3416,9 @@ class AccountCreationWizardWindow: self.advanced_button = self.xml.get_object('advanced_button') self.finish_label = self.xml.get_object('finish_label') self.go_online_checkbutton = self.xml.get_object( - 'go_online_checkbutton') + 'go_online_checkbutton') self.show_vcard_checkbutton = self.xml.get_object( - 'show_vcard_checkbutton') + 'show_vcard_checkbutton') self.progressbar = self.xml.get_object('progressbar') # some vars @@ -3359,33 +3428,33 @@ class AccountCreationWizardWindow: self.xml.connect_signals(self) self.window.show_all() gajim.ged.register_event_handler('NEW_ACC_CONNECTED', ged.CORE, - self.new_acc_connected) + self.new_acc_connected) gajim.ged.register_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE, - self.new_acc_not_connected) + self.new_acc_not_connected) gajim.ged.register_event_handler('ACC_OK', ged.CORE, self.acc_is_ok) gajim.ged.register_event_handler('ACC_NOT_OK', ged.CORE, - self.acc_is_not_ok) + self.acc_is_not_ok) def on_wizard_window_destroy(self, widget): page = self.notebook.get_current_page() if page in (4, 5) and self.account in gajim.connections: - # connection instance is saved in gajim.connections and we canceled the - # addition of the account + # connection instance is saved in gajim.connections and we canceled + # the addition of the account del gajim.connections[self.account] if self.account in gajim.config.get_per('accounts'): gajim.config.del_per('accounts', self.account) gajim.ged.remove_event_handler('NEW_ACC_CONNECTED', ged.CORE, - self.new_acc_connected) + self.new_acc_connected) gajim.ged.remove_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE, - self.new_acc_not_connected) + self.new_acc_not_connected) gajim.ged.remove_event_handler('ACC_OK', ged.CORE, self.acc_is_ok) gajim.ged.remove_event_handler('ACC_NOT_OK', ged.CORE, - self.acc_is_not_ok) + self.acc_is_not_ok) del gajim.interface.instances['account_creation_wizard'] def on_register_server_features_button_clicked(self, widget): helpers.launch_browser_mailer('url', - 'http://www.jabber.org/network/oldnetwork.shtml') + 'http://www.jabber.org/network/oldnetwork.shtml') def on_save_password_checkbutton_toggled(self, widget): self.xml.get_object('password_entry').grab_focus() @@ -3417,7 +3486,8 @@ class AccountCreationWizardWindow: active = widget.get_active() self.xml.get_object('username_entry').set_sensitive(not active) self.xml.get_object('password_entry').set_sensitive(not active) - self.xml.get_object('save_password_checkbutton').set_sensitive(not active) + self.xml.get_object('save_password_checkbutton').set_sensitive( + not active) def show_finish_page(self): self.cancel_button.hide() @@ -3425,16 +3495,16 @@ class AccountCreationWizardWindow: self.forward_button.hide() if self.modify: finish_text = '<big><b>%s</b></big>\n\n%s' % ( - _('Account has been added successfully'), - _('You can set advanced account options by pressing the ' - 'Advanced button, or later by choosing the Accounts menu item ' - 'under the Edit menu from the main window.')) + _('Account has been added successfully'), + _('You can set advanced account options by pressing the ' + 'Advanced button, or later by choosing the Accounts menu item ' + 'under the Edit menu from the main window.')) else: finish_text = '<big><b>%s</b></big>\n\n%s' % ( - _('Your new account has been created successfully'), - _('You can set advanced account options by pressing the Advanced ' - 'button, or later by choosing the Accounts menu item under the Edit' - ' menu from the main window.')) + _('Your new account has been created successfully'), + _('You can set advanced account options by pressing the ' + 'Advanced button, or later by choosing the Accounts menu item ' + 'under the Edit menu from the main window.')) self.finish_label.set_markup(finish_text) self.finish_button.show() self.finish_button.set_property('has-default', True) @@ -3465,21 +3535,22 @@ class AccountCreationWizardWindow: elif cur_page == 1: # We are adding an existing account - anonymous = self.xml.get_object('anonymous_checkbutton1').get_active() + anonymous = self.xml.get_object('anonymous_checkbutton1').\ + get_active() username = self.xml.get_object('username_entry').get_text().decode( - 'utf-8').strip() + 'utf-8').strip() if not username and not anonymous: pritext = _('Invalid username') sectext = _( - 'You must provide a username to configure this account.') + 'You must provide a username to configure this account.') dialogs.ErrorDialog(pritext, sectext) return - server = self.xml.get_object('server_comboboxentry').child.get_text().\ - decode('utf-8').strip() + server = self.xml.get_object('server_comboboxentry').child.\ + get_text().decode('utf-8').strip() savepass = self.xml.get_object('save_password_checkbutton').\ - get_active() + get_active() password = self.xml.get_object('password_entry').get_text().decode( - 'utf-8') + 'utf-8') jid = username + '@' + server # check if jid is conform to RFC and stringprep it @@ -3504,12 +3575,12 @@ class AccountCreationWizardWindow: self.show_finish_page() elif cur_page == 2: # We are creating a new account - server = self.xml.get_object('server_comboboxentry1').child.get_text()\ - .decode('utf-8') + server = self.xml.get_object('server_comboboxentry1').child.\ + get_text().decode('utf-8') if not server: dialogs.ErrorDialog(_('Invalid server'), - _('Please provide a server on which you want to register.')) + _('Please provide a server on which you want to register.')) return self.account = server i = 1 @@ -3527,17 +3598,17 @@ class AccountCreationWizardWindow: config['proxy'] = proxy config['use_custom_host'] = self.xml.get_object( - 'custom_host_port_checkbutton').get_active() + 'custom_host_port_checkbutton').get_active() custom_port = self.xml.get_object('custom_port_entry').get_text() try: custom_port = int(custom_port) except Exception: dialogs.ErrorDialog(_('Invalid entry'), - _('Custom port must be a port number.')) + _('Custom port must be a port number.')) return config['custom_port'] = custom_port config['custom_host'] = self.xml.get_object( - 'custom_host_entry').get_text().decode('utf-8') + 'custom_host_entry').get_text().decode('utf-8') if self.xml.get_object('anonymous_checkbutton2').get_active(): self.modify = True @@ -3548,7 +3619,7 @@ class AccountCreationWizardWindow: self.back_button.hide() self.forward_button.hide() self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + self.update_progressbar) # Get form from serveur con = connection.Connection(self.account) gajim.connections[self.account] = con @@ -3557,7 +3628,7 @@ class AccountCreationWizardWindow: checked = self.xml.get_object('ssl_checkbutton').get_active() if checked: hostname = gajim.connections[self.account].new_account_info[ - 'hostname'] + 'hostname'] # Check if cert is already in file certs = '' if os.path.isfile(gajim.MY_CACERTS): @@ -3566,14 +3637,15 @@ class AccountCreationWizardWindow: f.close() if self.ssl_cert in certs: dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + _('This certificate is already in file %s, so it\'s ' + 'not added again.') % gajim.MY_CACERTS) else: f = open(gajim.MY_CACERTS, 'a') f.write(hostname + '\n') f.write(self.ssl_cert + '\n\n') f.close() gajim.connections[self.account].new_account_info[ - 'ssl_fingerprint_sha1'] = self.ssl_fingerprint + 'ssl_fingerprint_sha1'] = self.ssl_fingerprint self.notebook.set_current_page(4) # show fom page elif cur_page == 4: if self.is_form: @@ -3581,14 +3653,15 @@ class AccountCreationWizardWindow: else: form = self.data_form_widget.get_infos() gajim.connections[self.account].send_new_account_infos(form, - self.is_form) + self.is_form) self.xml.get_object('form_vbox').remove(self.data_form_widget) - self.xml.get_object('progressbar_label').set_markup('<b>Account is being created</b>\n\nPlease wait...') + self.xml.get_object('progressbar_label').set_markup( + '<b>Account is being created</b>\n\nPlease wait...') self.notebook.set_current_page(5) # show creating page self.back_button.hide() self.forward_button.hide() self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + self.update_progressbar) def update_proxy_list(self): proxies_combobox = self.xml.get_object('proxies_combobox') @@ -3605,10 +3678,11 @@ class AccountCreationWizardWindow: gajim.interface.instances['manage_proxies'].window.present() else: gajim.interface.instances['manage_proxies'] = \ - ManageProxiesWindow() + ManageProxiesWindow() def on_custom_host_port_checkbutton_toggled(self, widget): - self.xml.get_object('custom_host_hbox').set_sensitive(widget.get_active()) + self.xml.get_object('custom_host_hbox').set_sensitive(widget.\ + get_active()) def update_progressbar(self): self.progressbar.pulse() @@ -3639,13 +3713,16 @@ class AccountCreationWizardWindow: if ssl_msg: # An SSL warning occured, show it hostname = gajim.connections[self.account].new_account_info['hostname'] - self.xml.get_object('ssl_label').set_markup(_('<b>Security Warning</b>' - '\n\nThe authenticity of the %(hostname)s SSL certificate could be ' - 'invalid.\nSSL Error: %(error)s\n' - 'Do you still want to connect to this server?') % { - 'hostname': hostname, 'error': ssl_msg}) + self.xml.get_object('ssl_label').set_markup(_( + '<b>Security Warning</b>' + '\n\nThe authenticity of the %(hostname)s SSL certificate could' + ' be invalid.\nSSL Error: %(error)s\n' + 'Do you still want to connect to this server?') % { + 'hostname': hostname, 'error': ssl_msg}) if ssl_err in (18, 27): - text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint + text = _('Add this certificate to the list of trusted ' + 'certificates.\nSHA1 fingerprint of the certificate:\n%s') \ + % ssl_fingerprint self.xml.get_object('ssl_checkbutton').set_label(text) else: self.xml.get_object('ssl_checkbutton').set_no_show_all(True) @@ -3675,7 +3752,7 @@ class AccountCreationWizardWindow: img = self.xml.get_object('finish_image') img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) finish_text = '<big><b>%s</b></big>\n\n%s' % ( - _('An error occurred during account creation'), reason) + _('An error occurred during account creation'), reason) self.finish_label.set_markup(finish_text) self.notebook.set_current_page(6) # show finish page @@ -3708,8 +3785,8 @@ class AccountCreationWizardWindow: gajim.config.del_per('accounts', self.account) img = self.xml.get_object('finish_image') img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) - finish_text = '<big><b>%s</b></big>\n\n%s' % (_('An error occurred during ' - 'account creation'), reason) + finish_text = '<big><b>%s</b></big>\n\n%s' % (_( + 'An error occurred during account creation'), reason) self.finish_label.set_markup(finish_text) self.notebook.set_current_page(6) # show finish page @@ -3721,8 +3798,7 @@ class AccountCreationWizardWindow: gajim.interface.instances['accounts'].window.present() else: gajim.interface.instances['accounts'] = AccountsWindow() - gajim.interface.instances['accounts'].select_account( - self.account) + gajim.interface.instances['accounts'].select_account(self.account) self.window.destroy() def on_finish_button_clicked(self, widget): @@ -3776,7 +3852,7 @@ class AccountCreationWizardWindow: def save_account(self, login, server, savepass, password, anonymous=False): if self.account in gajim.connections: dialogs.ErrorDialog(_('Account name is in use'), - _('You already have an account using this name.')) + _('You already have an account using this name.')) return con = connection.Connection(self.account) con.password = password @@ -3800,11 +3876,11 @@ class AccountCreationWizardWindow: # update variables gajim.interface.instances[self.account] = {'infos': {}, 'disco': {}, - 'gc_config': {}, 'search': {}, 'online_dialog': {}} + 'gc_config': {}, 'search': {}, 'online_dialog': {}} gajim.interface.minimized_controls[self.account] = {} gajim.connections[self.account].connected = 0 gajim.connections[self.account].keepalives = gajim.config.get_per( - 'accounts', self.account, 'keep_alive_every_foo_secs') + 'accounts', self.account, 'keep_alive_every_foo_secs') gajim.groups[self.account] = {} gajim.contacts.add_account(self.account) gajim.gc_connected[self.account] = {} @@ -3824,7 +3900,8 @@ class AccountCreationWizardWindow: if 'accounts' in gajim.interface.instances: gajim.interface.instances['accounts'].init_accounts() # refresh roster - if len(gajim.connections) >= 2: # Do not merge accounts if only one exists + if len(gajim.connections) >= 2: + # Do not merge accounts if only one exists gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') else: gajim.interface.roster.regroup = False diff --git a/src/conversation_textview.py b/src/conversation_textview.py index c0f5b83ed..46e60dfee 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -5,7 +5,7 @@ ## Copyright (C) 2005-2006 Alex Mauer <hawke AT hawkesnest.net> ## Travis Shirk <travis AT pobox.com> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> @@ -326,6 +326,7 @@ class ConversationTextview(gobject.GObject): tag.set_property('underline', pango.UNDERLINE_SINGLE) buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) + self.displaymarking_tags = {} tag = buffer_.create_tag('xep0184-warning') @@ -703,6 +704,7 @@ class ConversationTextview(gobject.GObject): size = 2 * size - 1 self.marks_queue = Queue.Queue(size) self.focus_out_end_mark = None + self.just_cleared = True def visit_url_from_menuitem(self, widget, link): """ @@ -1167,11 +1169,12 @@ class ConversationTextview(gobject.GObject): buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') + self.just_cleared = False def print_conversation_line(self, text, jid, kind, name, tim, other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[], subject=None, old_kind=None, xhtml=None, - simple=False, graphics=True): + simple=False, graphics=True, displaymarking=None): """ Print 'chat' type messages """ @@ -1236,6 +1239,9 @@ class ConversationTextview(gobject.GObject): tim_format = self.get_time_to_show(tim) buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n', 'time_sometimes') + # If there's a displaymarking, print it here. + if displaymarking: + self.print_displaymarking(displaymarking) # kind = info, we print things as if it was a status: same color, ... if kind in ('error', 'info'): kind = 'status' @@ -1246,7 +1252,7 @@ class ConversationTextview(gobject.GObject): text_tags.append(other_text_tag) else: # not status nor /me if gajim.config.get('chat_merge_consecutive_nickname'): - if kind != old_kind: + if kind != old_kind or self.just_cleared: self.print_name(name, kind, other_tags_for_name) else: self.print_real_text(gajim.config.get( @@ -1269,6 +1275,7 @@ class ConversationTextview(gobject.GObject): else: gobject.idle_add(self.scroll_to_end) + self.just_cleared = False buffer_.end_user_action() def get_time_to_show(self, tim): @@ -1306,6 +1313,19 @@ class ConversationTextview(gobject.GObject): elif text.startswith('/me ') or text.startswith('/me\n'): return kind + def print_displaymarking(self, displaymarking): + bgcolor = displaymarking.getAttr('bgcolor') or '#FFF' + fgcolor = displaymarking.getAttr('fgcolor') or '#000' + text = displaymarking.getData() + if text: + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + tag = self.displaymarking_tags.setdefault(bgcolor + '/' + fgcolor, + buffer_.create_tag(None, background=bgcolor, foreground=fgcolor)) + buffer_.insert_with_tags(end_iter, '[' + text + ']', tag) + end_iter = buffer_.get_end_iter() + buffer_.insert_with_tags(end_iter, ' ') + def print_name(self, name, kind, other_tags_for_name): if name: buffer_ = self.tv.get_buffer() diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py index 5fa9a2397..27466f892 100644 --- a/src/dataforms_widget.py +++ b/src/dataforms_widget.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/dataforms_widget.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org> ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> ## @@ -27,6 +27,7 @@ multiple - these which may contain more data (with <reported/> element).''' import gtk import gobject +import base64 import gtkgui_helpers import dialogs @@ -400,7 +401,7 @@ class SingleForm(gtk.Table, object): check.set_active(value in field.values) check.connect('toggled', self.on_list_multi_checkbutton_toggled, field, value) - widget.pack_start(check, expand=False) + widget.pack_start(check, expand=False) else: # more than 5 options: show combobox def on_list_multi_treeview_changed(selection, f): @@ -428,9 +429,9 @@ class SingleForm(gtk.Table, object): commonwidget = False xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', - 'item_list_table') - widget = xml.get_object('item_list_table') - treeview = xml.get_object('item_treeview') + 'multiple_form_hbox') + widget = xml.get_object('multiple_form_hbox') + treeview = xml.get_object('records_treeview') listmodel = gtk.ListStore(str) for value in field.iter_values(): @@ -529,6 +530,25 @@ class SingleForm(gtk.Table, object): self.attach(label, 0, 1, linecounter, linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL) + if field.media is not None: + for uri in field.media.uris: + if uri.type_.startswith('image/'): + try: + img_data = base64.decodestring(uri.uri_data) + pixbuf_l = gtk.gdk.PixbufLoader() + pixbuf_l.write(img_data) + pixbuf_l.close() + media = gtk.image_new_from_pixbuf(pixbuf_l.\ + get_pixbuf()) + except Exception: + media = gtk.Label(_('Unable to load image')) + else: + media = gtk.Label(_('Media type not supported: %s') % \ + uri.type_) + linecounter += 1 + self.attach(media, 0, 1, linecounter, linecounter+1, + xoptions=gtk.FILL, yoptions=gtk.FILL) + if commonwidget: assert widget is not None widget.set_sensitive(readwrite) diff --git a/src/dialogs.py b/src/dialogs.py index a77701b71..a1adc58bb 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -2,7 +2,7 @@ ## src/dialogs.py ## ## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org> -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Travis Shirk <travis AT pobox.com> @@ -38,6 +38,7 @@ import vcard import conversation_textview import message_control import dataforms_widget +import disco from random import randrange from common import pep @@ -992,7 +993,7 @@ _('Please fill in the data of the contact you want to add in account %s') % acco if self.account: message_buffer = self.message_textview.get_buffer() message_buffer.set_text(helpers.get_subscription_request_msg( - self.account)) + self.account)) def on_add_new_contact_window_destroy(self, widget): if self.account: @@ -1163,7 +1164,7 @@ class AboutDialog: dlg.set_transient_for(gajim.interface.roster.window) dlg.set_name('Gajim') dlg.set_version(gajim.version) - s = u'Copyright © 2003-2009 Gajim Team' + s = u'Copyright © 2003-2010 Gajim Team' dlg.set_copyright(s) copying_file_path = self.get_path('COPYING') if copying_file_path: @@ -1241,8 +1242,9 @@ class AboutDialog: class Dialog(gtk.Dialog): def __init__(self, parent, title, buttons, default=None, - on_response_ok=None, on_response_cancel=None): - gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR) + on_response_ok=None, on_response_cancel=None): + gtk.Dialog.__init__(self, title, parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR) self.user_response_ok = on_response_ok self.user_response_cancel = on_response_cancel @@ -1479,8 +1481,8 @@ class InformationDialog(HigDialog): """ def __init__(self, pritext, sectext=''): - HigDialog.__init__(self, None, - gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext) + HigDialog.__init__(self, None, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, + pritext, sectext) self.set_modal(False) self.set_transient_for(gajim.interface.roster.window) self.popup() @@ -1490,9 +1492,11 @@ class ErrorDialog(HigDialog): HIG compliant error dialog """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__( self, None, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext) + def __init__(self, pritext, sectext='', on_response_ok=None, + on_response_cancel=None): + HigDialog.__init__( self, None, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + pritext, sectext, on_response_ok=on_response_ok, + on_response_cancel=on_response_cancel) self.popup() class YesNoDialog(HigDialog): @@ -1594,7 +1598,7 @@ class ConfirmationDialogCheck(ConfirmationDialog): """ return self.checkbutton.get_active() -class ConfirmationDialogDubbleCheck(ConfirmationDialog): +class ConfirmationDialogDoubleCheck(ConfirmationDialog): """ HIG compliant confirmation dialog with 2 checkbuttons """ @@ -1869,10 +1873,15 @@ class ChangeNickDialog(InputDialogCheck): Class for changing room nickname in case of conflict """ - def __init__(self, account, room_jid, title, prompt, check_text=None): + def __init__(self, account, room_jid, title, prompt, check_text=None, + change_nick=False): + """ + change_nick must be set to True when we are already occupant of the room + and we are changing our nick + """ InputDialogCheck.__init__(self, title, '', checktext=check_text, input_str='', is_modal=True, ok_handler=None, cancel_handler=None) - self.room_queue = [(account, room_jid, prompt)] + self.room_queue = [(account, room_jid, prompt, change_nick)] self.check_next() def on_input_dialog_delete_event(self, widget, event): @@ -1901,7 +1910,8 @@ class ChangeNickDialog(InputDialogCheck): if 'change_nick_dialog' in gajim.interface.instances: del gajim.interface.instances['change_nick_dialog'] return - self.account, self.room_jid, self.prompt = self.room_queue.pop(0) + self.account, self.room_jid, self.prompt, self.change_nick = \ + self.room_queue.pop(0) self.setup_dialog() if gajim.new_room_nick is not None and not gajim.gc_connected[ @@ -1930,7 +1940,7 @@ class ChangeNickDialog(InputDialogCheck): if is_checked: gajim.new_room_nick = nick gajim.connections[self.account].join_gc(nick, self.room_jid, None, - change_nick=True) + change_nick=self.change_nick) if gajim.gc_connected[self.account][self.room_jid]: # We are changing nick, we will change self.nick when we receive # presence that inform that it works @@ -1945,9 +1955,9 @@ class ChangeNickDialog(InputDialogCheck): self.gc_control.new_nick = '' self.check_next() - def add_room(self, account, room_jid, prompt): - if (account, room_jid, prompt) not in self.room_queue: - self.room_queue.append((account, room_jid, prompt)) + def add_room(self, account, room_jid, prompt, change_nick=False): + if (account, room_jid, prompt, change_nick) not in self.room_queue: + self.room_queue.append((account, room_jid, prompt, change_nick)) class InputTextDialog(CommonInputDialog): """ @@ -1969,9 +1979,9 @@ class InputTextDialog(CommonInputDialog): start_iter, end_iter = self.input_buffer.get_bounds() return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8') -class DubbleInputDialog: +class DoubleInputDialog: """ - Class for Dubble Input dialog + Class for Double Input dialog """ def __init__(self, title, label_str1, label_str2, input_str1=None, @@ -2156,7 +2166,6 @@ class JoinGroupchatWindow: self._nickname_entry = self.xml.get_object('nickname_entry') self._password_entry = self.xml.get_object('password_entry') - self._room_jid_entry.set_text(room_jid) self._nickname_entry.set_text(nick) if password: self._password_entry.set_text(password) @@ -2171,6 +2180,13 @@ class JoinGroupchatWindow: title = _('Join Group Chat') self.window.set_title(title) + self.server_comboboxentry = self.xml.get_object('server_comboboxentry') + self.server_model = self.server_comboboxentry.get_model() + server_list = [] + # get the muc server of our server + if 'jabber' in gajim.connections[account].muc_jid: + server_list.append(gajim.connections[account].muc_jid['jabber']) + self.recently_combobox = self.xml.get_object('recently_combobox') liststore = gtk.ListStore(str) self.recently_combobox.set_model(liststore) @@ -2180,6 +2196,16 @@ class JoinGroupchatWindow: self.recently_groupchat = gajim.config.get('recently_groupchat').split() for g in self.recently_groupchat: self.recently_combobox.append_text(g) + server = gajim.get_server_from_jid(g) + if server not in server_list and not server.startswith('irc'): + server_list.append(server) + + for s in server_list: + self.server_model.append([s]) + self.server_comboboxentry.set_active(0) + + self._set_room_jid(room_jid) + if len(self.recently_groupchat) == 0: self.recently_combobox.set_sensitive(False) elif room_jid == '': @@ -2220,8 +2246,16 @@ class JoinGroupchatWindow: else: if widget in self._empty_required_widgets: self._empty_required_widgets.remove(widget) - if len(self._empty_required_widgets) == 0 and self.account: + if not self._empty_required_widgets and self.account: self.xml.get_object('join_button').set_sensitive(True) + text = self._room_jid_entry.get_text() + if widget == self._room_jid_entry and '@' in text: + # Don't allow @ char in room entry + room_jid, server = text.split('@', 1) + self._room_jid_entry.set_text(room_jid) + if server: + self.server_comboboxentry.child.set_text(server) + self.server_comboboxentry.grab_focus() def on_account_combobox_changed(self, widget): model = widget.get_model() @@ -2229,11 +2263,30 @@ class JoinGroupchatWindow: self.account = model[iter_][0].decode('utf-8') self.on_required_entry_changed(self._nickname_entry) + def _set_room_jid(self, room_jid): + room, server = gajim.get_name_and_server_from_jid(room_jid) + self._room_jid_entry.set_text(room) + self.server_comboboxentry.child.set_text(server) + def on_recently_combobox_changed(self, widget): model = widget.get_model() iter_ = widget.get_active_iter() room_jid = model[iter_][0].decode('utf-8') - self._room_jid_entry.set_text(room_jid) + self._set_room_jid(room_jid) + + def on_browse_rooms_button_clicked(self, widget): + server = self.server_comboboxentry.child.get_text().decode('utf-8') + if server in gajim.interface.instances[self.account]['disco']: + gajim.interface.instances[self.account]['disco'][server].window.\ + present() + else: + try: + # Object will add itself to the window dict + disco.ServiceDiscoveryWindow(self.account, server, + initial_identities=[{'category': 'conference', + 'type': 'text'}]) + except GajimGeneralException: + pass def on_cancel_button_clicked(self, widget): """ @@ -2258,7 +2311,9 @@ class JoinGroupchatWindow: 'groupchat.')) return nickname = self._nickname_entry.get_text().decode('utf-8') - room_jid = self._room_jid_entry.get_text().decode('utf-8') + server = self.server_comboboxentry.child.get_text().decode('utf-8') + room = self._room_jid_entry.get_text().decode('utf-8') + room_jid = room + '@' + server password = self._password_entry.get_text().decode('utf-8') try: nickname = helpers.parse_resource(nickname) @@ -2963,10 +3018,11 @@ class XMLConsoleWindow: self.tagOutIq.set_property('foreground', color) buffer_.create_tag('') # Default tag - self.enabled = False + self.enabled = True + self.xml.get_object('enable_checkbutton').set_active(True) self.input_textview.modify_text( - gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) if len(gajim.connections) > 1: title = _('XML Console for %s') % self.account @@ -2978,9 +3034,8 @@ class XMLConsoleWindow: self.xml.connect_signals(self) - def on_xml_console_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window + def on_xml_console_window_destroy(self, widget): + del gajim.interface.instances[self.account]['xml_console'] def on_clear_button_clicked(self, widget): buffer_ = self.stanzas_log_textview.get_buffer() @@ -3059,42 +3114,41 @@ class XMLConsoleWindow: type_ = kind # 'incoming' or 'outgoing' if kind == 'incoming': - buffer.insert_with_tags_by_name(end_iter, '<!-- In -->\n', - type_) + buffer.insert_with_tags_by_name(end_iter, '<!-- In -->\n', type_) elif kind == 'outgoing': - buffer.insert_with_tags_by_name(end_iter, '<!-- Out -->\n', - type_) + buffer.insert_with_tags_by_name(end_iter, '<!-- Out -->\n', type_) end_iter = buffer.get_end_iter() - buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') +\ - '\n\n', type_) + buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') \ + + '\n\n', type_) if at_the_end: gobject.idle_add(self.scroll_to_end) def on_send_button_clicked(self, widget): if gajim.connections[self.account].connected <= 1: - #if offline or connecting + # if offline or connecting ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) + _('Please make sure you are connected with "%s".') % \ + self.account) return begin_iter, end_iter = self.input_tv_buffer.get_bounds() stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode( - 'utf-8') + 'utf-8') if stanza: gajim.connections[self.account].send_stanza(stanza) self.input_tv_buffer.set_text('') # we sent ok, clear the textview def on_presence_button_clicked(self, widget): self.input_tv_buffer.set_text( - '<presence><show></show><status></status><priority></priority>' - '</presence>') + '<presence><show></show><status></status><priority></priority>' + '</presence>') def on_iq_button_clicked(self, widget): self.input_tv_buffer.set_text( - '<iq to="" type=""><query xmlns=""></query></iq>') + '<iq to="" type=""><query xmlns=""></query></iq>') def on_message_button_clicked(self, widget): self.input_tv_buffer.set_text( - '<message to="" type=""><body></body></message>') + '<message to="" type=""><body></body></message>') def on_expander_activate(self, widget): if not widget.get_expanded(): # it's the opposite! @@ -3103,7 +3157,7 @@ class XMLConsoleWindow: #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), - 'remove': _('remove')} + 'remove': _('remove')} class RosterItemExchangeWindow: """ Windows used when someone send you a exchange contact suggestion @@ -3857,6 +3911,50 @@ class ProgressDialog: return True # WM's X button or Escape key should not destroy the window +class ClientCertChooserDialog(FileChooserDialog): + def __init__(self, path_to_clientcert_file='', on_response_ok=None, + on_response_cancel=None): + ''' + optionally accepts path_to_clientcert_file so it has that as selected + ''' + def on_ok(widget, callback): + ''' + check if file exists and call callback + ''' + path_to_clientcert_file = self.get_filename() + path_to_clientcert_file = \ + gtkgui_helpers.decode_filechooser_file_paths( + (path_to_clientcert_file,))[0] + if os.path.exists(path_to_clientcert_file): + callback(widget, path_to_clientcert_file) + + FileChooserDialog.__init__(self, + title_text=_('Choose Client Cert #PCKS12'), + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_OK), + current_folder='', + default_response=gtk.RESPONSE_OK, + on_response_ok=(on_ok, on_response_ok), + on_response_cancel=on_response_cancel) + + filter_ = gtk.FileFilter() + filter_.set_name(_('All files')) + filter_.add_pattern('*') + self.add_filter(filter_) + + filter_ = gtk.FileFilter() + filter_.set_name(_('PKCS12 Files')) + filter_.add_pattern('*.p12') + self.add_filter(filter_) + self.set_filter(filter_) + + if path_to_clientcert_file: + # set_filename accept only absolute path + path_to_clientcert_file = os.path.abspath(path_to_clientcert_file) + self.set_filename(path_to_clientcert_file) + + class SoundChooserDialog(FileChooserDialog): def __init__(self, path_to_snd_file='', on_response_ok=None, on_response_cancel=None): @@ -4861,6 +4959,15 @@ class VoIPCallReceivedDialog(object): self.content_types.add(type_) self.set_secondary_text() + def remove_contents(self, content_types): + for type_ in content_types: + if type_ in self.content_types: + self.content_types.remove(type_) + if not self.content_types: + self.dialog.destroy() + else: + self.set_secondary_text() + def on_voip_call_received_messagedialog_destroy(self, dialog): if (self.fjid, self.sid) in self.instances: del self.instances[(self.fjid, self.sid)] @@ -4879,17 +4986,10 @@ class VoIPCallReceivedDialog(object): #TODO: Ensure that ctrl.contact.resource == resource jid = gajim.get_jid_without_resource(self.fjid) resource = gajim.get_resource_from_jid(self.fjid) - ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account) - if not ctrl: - ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) - if not ctrl: - # open chat control - contact = gajim.contacts.get_contact(self.account, jid, resource) - if not contact: - contact = gajim.contacts.get_contact(self.account, jid) - if not contact: - return - ctrl = gajim.interface.new_chat(contact, self.account, resource) + ctrl = (gajim.interface.msg_win_mgr.get_control(self.fjid, self.account) + or gajim.interface.msg_win_mgr.get_control(jid, self.account) + or gajim.interface.new_chat_from_jid(self.account, jid)) + # Chat control opened, update content's status audio = session.get_content('audio') video = session.get_content('video') diff --git a/src/disco.py b/src/disco.py index 46f0e82ac..37d91d93b 100644 --- a/src/disco.py +++ b/src/disco.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2005-2006 Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de> @@ -492,8 +492,8 @@ class ServiceDiscoveryWindow(object): Class that represents the Services Discovery window """ - def __init__(self, account, jid = '', node = '', - address_entry = False, parent = None): + def __init__(self, account, jid='', node='', address_entry=False, + parent=None, initial_identities=None): self.account = account self.parent = parent if not jid: @@ -519,6 +519,9 @@ _('Without a connection, you can not browse available services')) self.cache = ServicesCache(account) gajim.connections[account].services_cache = self.cache + if initial_identities: + self.cache.agent_info(account, (jid, node, initial_identities, [], + None)) self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui') self.window = self.xml.get_object('service_discovery_window') self.services_treeview = self.xml.get_object('services_treeview') @@ -546,10 +549,8 @@ _('Without a connection, you can not browse available services')) self.address_comboboxentry_entry = self.address_comboboxentry.child self.address_comboboxentry_entry.set_activates_default(True) - liststore = gtk.ListStore(str) - self.address_comboboxentry.set_model(liststore) self.latest_addresses = gajim.config.get( - 'latest_disco_addresses').split() + 'latest_disco_addresses').split() if jid in self.latest_addresses: self.latest_addresses.remove(jid) self.latest_addresses.insert(0, jid) @@ -1670,6 +1671,7 @@ class ToplevelAgentBrowser(AgentBrowser): # Search for an icon and category we can display pix = self.cache.get_icon(identities) + cat, type_ = None, None for identity in identities: try: cat, type_ = identity['category'], identity['type'] diff --git a/src/features_window.py b/src/features_window.py index e15c9b21d..e9a823c16 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -5,7 +5,7 @@ ## Julien Pivotto <roidelapluie AT gmail.com> ## Stefan Bethge <stefan AT lanpartei.de> ## Stephan Erb <steve-e AT h3c.de> -## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## ## This file is part of Gajim. @@ -31,6 +31,7 @@ import gtkgui_helpers from common import gajim from common import helpers from common import kwalletbinding +from common.i18n import Q_ class FeaturesWindow: """ @@ -111,7 +112,7 @@ class FeaturesWindow: self.model = gtk.ListStore(str, bool) treeview.set_model(self.model) - col = gtk.TreeViewColumn(_('Available')) + col = gtk.TreeViewColumn(Q_('?features:Available')) treeview.append_column(col) cell = gtk.CellRendererToggle() cell.set_property('radio', True) diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 621386eb7..62c9948af 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/filetransfers_window.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com> @@ -396,7 +396,7 @@ class FileTransfersWindow: on_response_ok=(on_response_ok, account, contact, file_props), on_response_cancel=(on_response_cancel, account, file_props)) dialog.connect('delete-event', lambda widget, event: - on_response_cancel(widget, account, file_props)) + on_response_cancel(account, file_props)) dialog.popup() def get_icon(self, ident): diff --git a/src/gajim-remote.py b/src/gajim-remote.py index 555319a4f..ea4a064b2 100644 --- a/src/gajim-remote.py +++ b/src/gajim-remote.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Junglecow <junglecow AT gmail.com> ## Travis Shirk <travis AT pobox.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> @@ -534,6 +534,8 @@ class GajimRemote: self.arguments += ['']*(len(args)-i) def handle_uri(self): + if len(sys.argv) < 3: + send_error(_('No uri given')) if not sys.argv[2].startswith('xmpp:'): send_error(_('Wrong uri')) sys.argv[2] = sys.argv[2][5:] diff --git a/src/gajim.py b/src/gajim.py index e3c9bb5a2..05fbe74fe 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/gajim.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com> ## Norman Rasmussen <norman AT rasmussen.co.za> @@ -70,32 +70,34 @@ import getopt from common import i18n def parseOpts(): - profile = '' - config_path = None + profile_ = '' + config_path_ = None try: shortargs = 'hqvl:p:c:' longargs = 'help quiet verbose loglevel= profile= config_path=' opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] - except getopt.error, msg: - print msg + except getopt.error, msg1: + print msg1 print 'for help use --help' sys.exit(2) for o, a in opts: if o in ('-h', '--help'): - print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]' + print 'gajim [--help] [--quiet] [--verbose] ' + \ + '[--loglevel subsystem=level[,subsystem=level[...]]] ' + \ + '[--profile name] [--config-path]' sys.exit() elif o in ('-q', '--quiet'): logging_helpers.set_quiet() elif o in ('-v', '--verbose'): logging_helpers.set_verbose() elif o in ('-p', '--profile'): # gajim --profile name - profile = a + profile_ = a elif o in ('-l', '--loglevel'): logging_helpers.set_loglevels(a) elif o in ('-c', '--config-path'): - config_path = a - return profile, config_path + config_path_ = a + return profile_, config_path_ profile, config_path = parseOpts() del parseOpts @@ -114,8 +116,8 @@ if os.name == 'nt': _file = None _error = None def write(self, text): - fname=os.path.join(common.configpaths.gajimpaths.cache_root, - os.path.split(sys.executable)[1]+'.log') + fname = os.path.join(common.configpaths.gajimpaths.cache_root, + os.path.split(sys.executable)[1]+'.log') if self._file is None and self._error is None: try: self._file = open(fname, 'a') @@ -133,15 +135,18 @@ if os.name == 'nt': # PyGTK2.10+ only throws a warning warnings.filterwarnings('error', module='gtk') try: + import gobject import gtk -except Warning, msg: - if str(msg) == 'could not open display': +except Warning, msg2: + if str(msg2) == 'could not open display': print >> sys.stderr, _('Gajim needs X server to run. Quiting...') else: - print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg) + print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg2) sys.exit() warnings.resetwarnings() +gobject.set_prgname('gajim') + if os.name == 'nt': warnings.filterwarnings(action='ignore') @@ -152,12 +157,13 @@ try: from common import gajim except exceptions.DatabaseMalformed: pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH + sectext = _('The database file (%s) cannot be read. Try to repair it (see ' + 'http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history ' + 'will be lost).') % common.logger.LOG_DB_PATH else: from common import dbus_support if dbus_support.supported: from music_track_listener import MusicTrackListener - import dbus from ctypes import CDLL from ctypes.util import find_library @@ -167,8 +173,8 @@ else: if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): libc = CDLL(find_library('c')) - # The constant defined in <linux/prctl.h> which is used to set the name of - # the process. + # The constant defined in <linux/prctl.h> which is used to set the name + # of the process. PR_SET_NAME = 15 if sysname == 'Linux': @@ -183,11 +189,7 @@ else: pritext = _('Gajim needs GTK 2.16 or above') sectext = _('Gajim needs GTK 2.16 or above to run. Quiting...') - try: - from common import check_paths - except exceptions.PysqliteNotAvailable, e: - pritext = _('Gajim needs PySQLite2 to run') - sectext = str(e) + from common import check_paths if os.name == 'nt': try: @@ -195,7 +197,9 @@ else: import win32api # do NOT remove. we req this module except Exception: pritext = _('Gajim needs pywin32 to run') - sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018' + sectext = _('Please make sure that Pywin32 is installed on your ' + 'system. You can get it at %s') % \ + 'http://sourceforge.net/project/showfiles.php?group_id=78018' if pritext: dlg = gtk.MessageDialog(None, @@ -211,13 +215,6 @@ del pritext import gtkexcepthook -import gobject -if not hasattr(gobject, 'timeout_add_seconds'): - def timeout_add_seconds_fake(time_sec, *args): - return gobject.timeout_add(time_sec * 1000, *args) - gobject.timeout_add_seconds = timeout_add_seconds_fake - - import signal import gtkgui_helpers @@ -248,7 +245,8 @@ def pid_alive(): if os.name == 'nt': try: - from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, ) + from ctypes import (windll, c_ulong, c_int, Structure, c_char) + from ctypes import (POINTER, pointer, sizeof) except Exception: return True @@ -265,26 +263,26 @@ def pid_alive(): ('dwFlags', c_ulong, ), ('szExeFile', c_char*512, ), ] - def __init__(self): - Structure.__init__(self, 512+9*4) - - k = windll.kernel32 - k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, - k.CreateToolhelp32Snapshot.restype = c_int - k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), - k.Process32First.restype = c_int - k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), - k.Process32Next.restype = c_int - - def get_p(p): - h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS - assert h > 0, 'CreateToolhelp32Snapshot failed' - b = pointer(PROCESSENTRY32()) - f = k.Process32First(h, b) - while f: - if b.contents.th32ProcessID == p: - return b.contents.szExeFile - f = k.Process32Next(h, b) + + kernel = windll.kernel32 + kernel.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, + kernel.CreateToolhelp32Snapshot.restype = c_int + kernel.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), + kernel.Process32First.restype = c_int + kernel.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), + kernel.Process32Next.restype = c_int + + def get_p(pid_): + TH32CS_SNAPPROCESS = 2 + CreateToolhelp32Snapshot = kernel.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) + assert CreateToolhelp32Snapshot > 0, 'CreateToolhelp32Snapshot failed' + pe32 = PROCESSENTRY32() + pe32.dwSize = sizeof( PROCESSENTRY32 ) + f3 = kernel.Process32First(CreateToolhelp32Snapshot, pointer(pe32)) + while f3: + if pe32.th32ProcessID == pid_: + return pe32.szExeFile + f3 = kernel.Process32Next(CreateToolhelp32Snapshot, pointer(pe32)) if get_p(pid) in ('python.exe', 'gajim.exe'): return True @@ -294,14 +292,14 @@ def pid_alive(): return True # no /proc, assume Gajim is running try: - f = open('/proc/%d/cmdline'% pid) - except IOError, e: - if e.errno == errno.ENOENT: + f1 = open('/proc/%d/cmdline'% pid) + except IOError, e1: + if e1.errno == errno.ENOENT: return False # file/pid does not exist raise - n = f.read().lower() - f.close() + n = f1.read().lower() + f1.close() if n.find('gajim') < 0: return False return True # Running Gajim found at pid @@ -325,7 +323,6 @@ if pid_alive(): # run anyway, delete pid and useless global vars if os.path.exists(pid_filename): os.remove(pid_filename) - del path_to_file del pix del pritext del sectext @@ -337,16 +334,15 @@ if not os.path.exists(pid_dir): check_paths.create_path(pid_dir) # Create pid file try: - f = open(pid_filename, 'w') - f.write(str(os.getpid())) - f.close() -except IOError, e: - dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e)) + f2 = open(pid_filename, 'w') + f2.write(str(os.getpid())) + f2.close() +except IOError, e2: + dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e2)) dlg.run() dlg.destroy() sys.exit() del pid_dir -del f def on_exit(): # delete pid file on normal exit @@ -378,7 +374,7 @@ if __name__ == '__main__': except ImportError: pass else: - def die_cb(cli): + def die_cb(dummy): gajim.interface.roster.quit_gtkgui_interface() gnome.program_init('gajim', gajim.version) cli = gnome.ui.master_client() @@ -389,13 +385,7 @@ if __name__ == '__main__': if path_to_gajim_script: argv = [path_to_gajim_script] - # FIXME: remove this typeerror catch when gnome python is old and - # not bad patched by distro men [2.12.0 + should not need all that - # NORMALLY] - try: - cli.set_restart_command(argv) - except AttributeError: - cli.set_restart_command(len(argv), argv) + cli.set_restart_command(len(argv), argv) check_paths.check_and_possibly_create_paths() diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py index 5d83de43b..15d2952a9 100644 --- a/src/gajim_themes_window.py +++ b/src/gajim_themes_window.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/gajim_themes_window.py ## -## Copyright (C) 2003-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 36f84b69f..9296667f3 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/groupchat_control.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Alex Mauer <hawke AT hawkesnest.net> @@ -40,9 +40,11 @@ import dialogs import config import vcard import cell_renderer_image +import dataforms_widget from common import gajim from common import helpers +from common import dataforms from chat_control import ChatControl from chat_control import ChatControlBase @@ -86,9 +88,11 @@ def tree_cell_data_func(column, renderer, model, iter_, tv=None): renderer.set_property('xalign', 1) # align pixbuf to the right else: renderer.set_property('xalign', 0.5) - if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'): + if parent_iter and (model[iter_][C_AVATAR] or avatar_position == \ + 'left'): renderer.set_property('visible', True) - renderer.set_property('width', gajim.config.get('roster_avatar_width')) + renderer.set_property('width', gajim.config.get( + 'roster_avatar_width')) else: renderer.set_property('visible', False) if parent_iter: @@ -105,7 +109,7 @@ def tree_cell_data_func(column, renderer, model, iter_, tv=None): else: renderer.set_property('foreground', None) renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) else: # it is root (eg. group) bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') if bgcolor: @@ -120,7 +124,7 @@ def tree_cell_data_func(column, renderer, model, iter_, tv=None): else: set_renderer_color(tv, renderer, False) renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) class PrivateChatControl(ChatControl): @@ -132,7 +136,8 @@ class PrivateChatControl(ChatControl): def __init__(self, parent_win, gc_contact, contact, account, session): room_jid = gc_contact.room_jid - room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) + room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + account) if room_jid in gajim.interface.minimized_controls[account]: room_ctrl = gajim.interface.minimized_controls[account][room_jid] if room_ctrl: @@ -161,14 +166,14 @@ class PrivateChatControl(ChatControl): gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick) if not gc_contact: dialogs.ErrorDialog( - _('Sending private message failed'), - #in second %s code replaces with nickname - _('You are no longer in group chat "%(room)s" or "%(nick)s" has ' - 'left.') % {'room': room, 'nick': nick}) + _('Sending private message failed'), + #in second %s code replaces with nickname + _('You are no longer in group chat "%(room)s" or ' + '"%(nick)s" has left.') % {'room': room, 'nick': nick}) return ChatControl.send_message(self, message, xhtml=xhtml, - process_commands=process_commands) + process_commands=process_commands) def update_ui(self): if self.contact.show == 'offline': @@ -185,7 +190,8 @@ class PrivateChatControl(ChatControl): if not self.session: fjid = self.gc_contact.get_full_jid() - new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) + new_sess = gajim.connections[self.account].make_new_session(fjid, + type_=self.type_id) self.set_session(new_sess) self.session.negotiate_e2e(False) @@ -199,9 +205,9 @@ class GroupchatControl(ChatControlBase): def __init__(self, parent_win, contact, acct, is_continued=False): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, - 'groupchat_control', contact, acct) + 'groupchat_control', contact, acct) - self.is_continued=is_continued + self.is_continued = is_continued self.is_anonymous = True # Controls the state of autorejoin. @@ -212,9 +218,12 @@ class GroupchatControl(ChatControlBase): # state in got_connected()). self.autorejoin = None + # Keep error dialog instance to be sure to have only once at a time + self.error_dialog = None + self.actions_button = self.xml.get_object('muc_window_actions_button') id_ = self.actions_button.connect('clicked', - self.on_actions_button_clicked) + self.on_actions_button_clicked) self.handlers[id_] = self.actions_button widget = self.xml.get_object('change_nick_button') @@ -222,7 +231,8 @@ class GroupchatControl(ChatControlBase): self.handlers[id_] = widget widget = self.xml.get_object('change_subject_button') - id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate) + id_ = widget.connect('clicked', + self._on_change_subject_menuitem_activate) self.handlers[id_] = widget widget = self.xml.get_object('bookmark_button') @@ -232,7 +242,7 @@ class GroupchatControl(ChatControlBase): break else: id_ = widget.connect('clicked', - self._on_bookmark_room_menuitem_activate) + self._on_bookmark_room_menuitem_activate) self.handlers[id_] = widget widget.show() @@ -240,27 +250,28 @@ class GroupchatControl(ChatControlBase): id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) self.handlers[id_] = widget - id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed) + id_ = widget.connect('row_collapsed', + self.on_list_treeview_row_collapsed) self.handlers[id_] = widget id_ = widget.connect('row_activated', - self.on_list_treeview_row_activated) + self.on_list_treeview_row_activated) self.handlers[id_] = widget id_ = widget.connect('button_press_event', - self.on_list_treeview_button_press_event) + self.on_list_treeview_button_press_event) self.handlers[id_] = widget id_ = widget.connect('key_press_event', - self.on_list_treeview_key_press_event) + self.on_list_treeview_key_press_event) self.handlers[id_] = widget id_ = widget.connect('motion_notify_event', - self.on_list_treeview_motion_notify_event) + self.on_list_treeview_motion_notify_event) self.handlers[id_] = widget id_ = widget.connect('leave_notify_event', - self.on_list_treeview_leave_notify_event) + self.on_list_treeview_leave_notify_event) self.handlers[id_] = widget self.room_jid = self.contact.jid @@ -277,9 +288,9 @@ class GroupchatControl(ChatControlBase): compact_view = gajim.config.get('compact_view') self.chat_buttons_set_visible(compact_view) self.widget_set_visible(self.xml.get_object('banner_eventbox'), - gajim.config.get('hide_groupchat_banner')) + gajim.config.get('hide_groupchat_banner')) self.widget_set_visible(self.xml.get_object('list_scrolledwindow'), - gajim.config.get('hide_groupchat_occupants_list')) + gajim.config.get('hide_groupchat_occupants_list')) self._last_selected_contact = None # None or holds jid, account tuple @@ -301,7 +312,7 @@ class GroupchatControl(ChatControlBase): self.gc_count_nicknames_colors = 0 self.gc_custom_colors = {} self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\ - split(':')) + split(':')) self.name_label = self.xml.get_object('banner_name_label') self.event_box = self.xml.get_object('banner_eventbox') @@ -314,17 +325,17 @@ class GroupchatControl(ChatControlBase): self.list_treeview = self.xml.get_object('list_treeview') selection = self.list_treeview.get_selection() id_ = selection.connect('changed', - self.on_list_treeview_selection_changed) + self.on_list_treeview_selection_changed) self.handlers[id_] = selection id_ = self.list_treeview.connect('style-set', - self.on_list_treeview_style_set) + self.on_list_treeview_style_set) self.handlers[id_] = self.list_treeview self.resize_from_another_muc = False # we want to know when the the widget resizes, because that is # an indication that the hpaned has moved... # FIXME: Find a better indicator that the hpaned has moved. id_ = self.list_treeview.connect('size-allocate', - self.on_treeview_size_allocate) + self.on_treeview_size_allocate) self.handlers[id_] = self.list_treeview #status_image, shown_nick, type, nickname, avatar store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf) @@ -343,24 +354,25 @@ class GroupchatControl(ChatControlBase): column.pack_start(renderer_pixbuf, expand=False) column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR) column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func, - self.list_treeview) + self.list_treeview) if gajim.config.get('avatar_position_in_roster') == 'left': add_avatar_renderer() - renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img + # status img + renderer_image = cell_renderer_image.CellRendererImage(0, 0) renderer_image.set_property('width', 26) column.pack_start(renderer_image, expand=False) column.add_attribute(renderer_image, 'image', C_IMG) column.set_cell_data_func(renderer_image, tree_cell_data_func, - self.list_treeview) + self.list_treeview) renderer_text = gtk.CellRendererText() # nickname column.pack_start(renderer_text, expand=True) column.add_attribute(renderer_text, 'markup', C_TEXT) renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END) column.set_cell_data_func(renderer_text, tree_cell_data_func, - self.list_treeview) + self.list_treeview) if gajim.config.get('avatar_position_in_roster') == 'right': add_avatar_renderer() @@ -375,6 +387,8 @@ class GroupchatControl(ChatControlBase): column.set_visible(False) self.list_treeview.set_expander_column(column) + self.setup_seclabel(self.xml.get_object('label_selector')) + gajim.gc_connected[self.account][self.room_jid] = False # disable win, we are not connected yet ChatControlBase.got_disconnected(self) @@ -412,7 +426,7 @@ class GroupchatControl(ChatControlBase): if type1 == 'contact' and type2 == 'contact' and \ gajim.config.get('sort_by_show_in_muc'): cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'error': 7} + 'invisible': 5, 'offline': 6, 'error': 7} show1 = cshow[gc_contact1.show] show2 = cshow[gc_contact2.show] if show1 < show2: @@ -442,7 +456,8 @@ class GroupchatControl(ChatControlBase): self.room_jid)): item = gtk.MenuItem(nick, use_underline=False) submenu.append(item) - id_ = item.connect('activate', self.append_nick_in_msg_textview, nick) + id_ = item.connect('activate', self.append_nick_in_msg_textview, + nick) self.handlers[id_] = item menu.show_all() @@ -467,8 +482,10 @@ class GroupchatControl(ChatControlBase): for account in gajim.gc_connected: for room_jid in [i for i in gajim.gc_connected[account] if \ gajim.gc_connected[account][i] and i != self.room_jid]: - ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) - if not ctrl: + ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + account) + if not ctrl and room_jid in \ + gajim.interface.minimized_controls[account]: ctrl = gajim.interface.minimized_controls[account][room_jid] if ctrl: ctrl.resize_occupant_treeview(hpaned_position) @@ -531,8 +548,8 @@ class GroupchatControl(ChatControlBase): self.attention_flag = False # get active color from gtk color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] - elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ - not self.attention_flag: + elif chatstate == 'newmsg' and (not has_focus or not current_tab) \ + and not self.attention_flag: color_name = gajim.config.get_per('themes', theme, 'state_muc_msg_color') if color_name: @@ -580,7 +597,7 @@ class GroupchatControl(ChatControlBase): self.change_roster_style() def _update_banner_state_image(self): - banner_status_img = self.xml.get_object('banner_status_image') + banner_status_img = self.xml.get_object('gc_banner_status_image') images = gajim.interface.jabber_state_images if self.room_jid in gajim.gc_connected[self.account] and \ gajim.gc_connected[self.account][self.room_jid]: @@ -635,7 +652,8 @@ class GroupchatControl(ChatControlBase): subject = helpers.reduce_chars_newlines(self.subject, max_lines=2) subject = gobject.markup_escape_text(subject) subject_text = self.urlfinder.sub(self.make_href, subject) - subject_text = '<span %s>%s</span>' % (font_attrs_small, subject_text) + subject_text = '<span %s>%s</span>' % (font_attrs_small, + subject_text) # tooltip must always hold ALL the subject self.event_box.set_tooltip_text(self.subject) @@ -688,13 +706,13 @@ class GroupchatControl(ChatControlBase): ag = gtk.accel_groups_from_object(self.parent_win.window)[0] change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE) + gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE) change_subject_menuitem.add_accelerator('activate', ag, - gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE) + gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE) bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) if self.contact.jid in gajim.config.get_per('accounts', self.account, 'minimized_gc').split(' '): @@ -703,7 +721,7 @@ class GroupchatControl(ChatControlBase): bookmark_room_menuitem.set_sensitive(False) if gajim.gc_connected[self.account][self.room_jid]: c = gajim.contacts.get_gc_contact(self.account, self.room_jid, - self.nick) + self.nick) if c.affiliation not in ('owner', 'admin'): configure_room_menuitem.set_sensitive(False) else: @@ -723,78 +741,110 @@ class GroupchatControl(ChatControlBase): # connect the menuitems to their respective functions id_ = bookmark_room_menuitem.connect('activate', - self._on_bookmark_room_menuitem_activate) + self._on_bookmark_room_menuitem_activate) self.handlers[id_] = bookmark_room_menuitem id_ = change_nick_menuitem.connect('activate', - self._on_change_nick_menuitem_activate) + self._on_change_nick_menuitem_activate) self.handlers[id_] = change_nick_menuitem id_ = configure_room_menuitem.connect('activate', - self._on_configure_room_menuitem_activate) + self._on_configure_room_menuitem_activate) self.handlers[id_] = configure_room_menuitem id_ = destroy_room_menuitem.connect('activate', - self._on_destroy_room_menuitem_activate) + self._on_destroy_room_menuitem_activate) self.handlers[id_] = destroy_room_menuitem id_ = change_subject_menuitem.connect('activate', - self._on_change_subject_menuitem_activate) + self._on_change_subject_menuitem_activate) self.handlers[id_] = change_subject_menuitem id_ = history_menuitem.connect('activate', - self._on_history_menuitem_activate) + self._on_history_menuitem_activate) self.handlers[id_] = history_menuitem id_ = minimize_menuitem.connect('toggled', - self.on_minimize_menuitem_toggled) + self.on_minimize_menuitem_toggled) self.handlers[id_] = minimize_menuitem menu.connect('selection-done', self.destroy_menu, - change_nick_menuitem, change_subject_menuitem, - bookmark_room_menuitem, history_menuitem) + change_nick_menuitem, change_subject_menuitem, + bookmark_room_menuitem, history_menuitem) return menu def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem, - bookmark_room_menuitem, history_menuitem): + bookmark_room_menuitem, history_menuitem): # destroy accelerators ag = gtk.accel_groups_from_object(self.parent_win.window)[0] change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK) + gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK) change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t, - gtk.gdk.MOD1_MASK) + gtk.gdk.MOD1_MASK) bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b, - gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK) history_menuitem.remove_accelerator(ag, gtk.keysyms.h, - gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK) # destroy menu menu.destroy() def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None, - status_code=[]): + status_code=[], displaymarking=None, captcha=None): + if captcha: + dataform = dataforms.ExtendForm(node=captcha) + self.form_widget = dataforms_widget.DataFormWidget(dataform) + self.form_widget.show_all() + vbox = self.xml.get_object('gc_textviews_vbox') + vbox.pack_start(self.form_widget, expand=False, fill=False) + + def on_send_dataform_clicked(widget): + if not self.form_widget: + return + form_node = self.form_widget.data_form.get_purged() + form_node.type = 'submit' + gajim.connections[self.account].send_captcha(self.room_jid, + form_node) + self.form_widget.hide() + self.form_widget.destroy() + self.btn_box.destroy() + del self.form_widget + del self.btn_box + + valid_button = gtk.Button(stock=gtk.STOCK_OK) + valid_button.connect('clicked', on_send_dataform_clicked) + self.btn_box = gtk.HButtonBox() + self.btn_box.set_layout(gtk.BUTTONBOX_END) + self.btn_box.pack_start(valid_button) + self.btn_box.show_all() + vbox.pack_start(self.btn_box, expand=False, fill=False) + if self.parent_win: + self.parent_win.redraw_tab(self, 'attention') + else: + self.attention_flag = True + helpers.play_sound('muc_message_received') if '100' in status_code: # Room is not anonymous self.is_anonymous = False if not nick: # message from server - self.print_conversation(msg, tim=tim, xhtml=xhtml) + self.print_conversation(msg, tim=tim, xhtml=xhtml, displaymarking=displaymarking) else: # message from someone if has_timestamp: # don't print xhtml if it's an old message. # Like that xhtml messages are grayed too. - self.print_old_conversation(msg, nick, tim, None) + self.print_old_conversation(msg, nick, tim, None, displaymarking=displaymarking) else: - self.print_conversation(msg, nick, tim, xhtml) + self.print_conversation(msg, nick, tim, xhtml, displaymarking=displaymarking) def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None, - encrypted=False): + encrypted=False, displaymarking=None): # Do we have a queue? fjid = self.room_jid + '/' + nick no_queue = len(gajim.events.get_events(self.account, fjid)) == 0 event = gajim.events.create_event('pm', (msg, '', 'incoming', tim, - encrypted, '', msg_id, xhtml, session)) + encrypted, '', msg_id, xhtml, session, displaymarking)) gajim.events.add_event(self.account, fjid, event) autopopup = gajim.config.get('autopopup') @@ -805,9 +855,9 @@ class GroupchatControl(ChatControlBase): gajim.connections[self.account].connected > 2): if no_queue: # We didn't have a queue: we change icons model = self.list_treeview.get_model() - state_images =\ - gajim.interface.roster.get_appropriate_state_images( - self.room_jid, icon_name='event') + state_images = \ + gajim.interface.roster.get_appropriate_state_images( + self.room_jid, icon_name='event') image = state_images['event'] model[iter_][C_IMG] = image if self.parent_win: @@ -819,8 +869,8 @@ class GroupchatControl(ChatControlBase): self.list_treeview.expand_row(path[0:1], False) self.list_treeview.scroll_to_cell(path) self.list_treeview.set_cursor(path) - contact = gajim.contacts.get_contact_with_highest_priority(self.account, \ - self.room_jid) + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, self.room_jid) if contact: gajim.interface.roster.draw_contact(self.room_jid, self.account) @@ -837,7 +887,8 @@ class GroupchatControl(ChatControlBase): role_iter = model.iter_next(role_iter) return None - def print_old_conversation(self, text, contact='', tim=None, xhtml = None): + def print_old_conversation(self, text, contact='', tim=None, xhtml = None, + displaymarking=None): if isinstance(text, str): text = unicode(text, 'utf-8') if contact: @@ -852,11 +903,12 @@ class GroupchatControl(ChatControlBase): else: small_attr = [] ChatControlBase.print_conversation_line(self, text, kind, contact, tim, - small_attr, small_attr + ['restored_message'], - small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml) + small_attr, small_attr + ['restored_message'], + small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml, + displaymarking=displaymarking) def print_conversation(self, text, contact='', tim=None, xhtml=None, - graphics=True): + graphics=True, displaymarking=None): """ Print a line in the conversation @@ -887,15 +939,15 @@ class GroupchatControl(ChatControlBase): (highlight, sound) = self.highlighting_for_message(text, tim) if contact in self.gc_custom_colors: other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[contact])) + str(self.gc_custom_colors[contact])) else: self.gc_count_nicknames_colors += 1 if self.gc_count_nicknames_colors == self.number_of_colors: self.gc_count_nicknames_colors = 0 self.gc_custom_colors[contact] = \ - self.gc_count_nicknames_colors + self.gc_count_nicknames_colors other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_count_nicknames_colors)) + str(self.gc_count_nicknames_colors)) if highlight: # muc-specific chatstate if self.parent_win: @@ -917,28 +969,28 @@ class GroupchatControl(ChatControlBase): helpers.play_sound('muc_message_highlight') if text.startswith('/me ') or text.startswith('/me\n'): other_tags_for_text.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[contact])) + str(self.gc_custom_colors[contact])) self.check_and_possibly_add_focus_out_line() ChatControlBase.print_conversation_line(self, text, kind, contact, tim, - other_tags_for_name, [], other_tags_for_text, xhtml=xhtml, - graphics=graphics) + other_tags_for_name, [], other_tags_for_text, xhtml=xhtml, + graphics=graphics, displaymarking=displaymarking) def get_nb_unread(self): type_events = ['printed_marked_gc_msg'] if gajim.config.get('notify_on_all_muc_messages'): type_events.append('printed_gc_msg') nb = len(gajim.events.get_events(self.account, self.room_jid, - type_events)) + type_events)) nb += self.get_nb_unread_pm() return nb def get_nb_unread_pm(self): nb = 0 for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \ - nick, ['pm'])) + nb += len(gajim.events.get_events(self.account, self.room_jid + \ + '/' + nick, ['pm'])) return nb def highlighting_for_message(self, text, tim): @@ -972,7 +1024,8 @@ class GroupchatControl(ChatControlBase): does not already have it as last event. If it goes to add this line - remove previous line first """ - win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account) + win = gajim.interface.msg_win_mgr.get_window(self.room_jid, + self.account) if win and self.room_jid == win.get_active_jid() and\ win.window.get_property('has-toplevel-focus') and\ self.parent_win.get_active_control() == self: @@ -1031,8 +1084,8 @@ class GroupchatControl(ChatControlBase): for nick in nick_list: # Update pm chat window fjid = self.room_jid + '/' + nick - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) + gc_contact = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick) ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account) if ctrl: @@ -1058,29 +1111,30 @@ class GroupchatControl(ChatControlBase): if self.autorejoin is None and gajim.account_is_connected(self.account): ar_to = gajim.config.get('muc_autorejoin_timeout') if ar_to: - self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin) + self.autorejoin = gobject.timeout_add_seconds(ar_to, + self.rejoin) def rejoin(self): if not self.autorejoin: return False password = gajim.gc_passwords.get(self.room_jid, '') gajim.connections[self.account].join_gc(self.nick, self.room_jid, - password) + password) return True def draw_roster(self): self.list_treeview.get_model().clear() for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) + gc_contact = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick) self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role, - gc_contact.affiliation, gc_contact.status, gc_contact.jid) + gc_contact.affiliation, gc_contact.status, gc_contact.jid) self.draw_all_roles() # Recalculate column width for ellipsizin self.list_treeview.columns_autosize() def on_send_pm(self, widget=None, model=None, iter_=None, nick=None, - msg=None): + msg=None): """ Open a chat window and if msg is not None - send private message to a contact in a room @@ -1106,7 +1160,8 @@ class GroupchatControl(ChatControlBase): gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) state_images = gajim.interface.jabber_state_images['16'] - if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)): + if len(gajim.events.get_events(self.account, self.room_jid + '/' + \ + nick)): image = state_images['event'] else: image = state_images[gc_contact.show] @@ -1125,11 +1180,13 @@ class GroupchatControl(ChatControlBase): if status != '': status = helpers.reduce_chars_newlines(status, max_lines=1) # escape markup entities and make them small italic and fg color - color = gtkgui_helpers._get_fade_color(self.list_treeview, + color = gtkgui_helpers.get_fade_color(self.list_treeview, selected, focus) - colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue) + colorstring = "#%04x%04x%04x" % (color.red, color.green, + color.blue) name += ('\n<span size="small" style="italic" foreground="%s">' - '%s</span>') % (colorstring, gobject.markup_escape_text(status)) + '%s</span>') % (colorstring, gobject.markup_escape_text( + status)) if image.get_storage_type() == gtk.IMAGE_PIXBUF and \ gc_contact.affiliation != 'none' and gajim.config.get( @@ -1143,8 +1200,8 @@ class GroupchatControl(ChatControlBase): elif gc_contact.affiliation == 'member': pixbuf2.fill(0x00ff00ff) # Green pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'), - pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, - gtk.gdk.INTERP_HYPER, 127) + pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, + gtk.gdk.INTERP_HYPER, 127) image = gtk.image_new_from_pixbuf(pixbuf1) model[iter_][C_IMG] = image model[iter_][C_TEXT] = name @@ -1172,7 +1229,7 @@ class GroupchatControl(ChatControlBase): role_name = helpers.get_uf_role(role, plural=True) if gajim.config.get('show_contacts_number'): nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts( - self.account, self.room_jid, role) + self.account, self.room_jid, role) role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total)) model[role_iter][C_TEXT] = role_name @@ -1181,7 +1238,7 @@ class GroupchatControl(ChatControlBase): self.draw_role(role) def chg_contact_status(self, nick, show, status, role, affiliation, jid, - reason, actor, statusCode, new_nick, avatar_sha, tim=None): + reason, actor, statusCode, new_nick, avatar_sha, tim=None): """ When an occupant changes his or her status """ @@ -1205,33 +1262,35 @@ class GroupchatControl(ChatControlBase): nick_jid += ' (%s)' % simple_jid # statusCode - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-\ + # init if statusCode: if '100' in statusCode: - # Can be a message (see handle_event_gc_config_change in gajim.py) + # Can be a message (see handle_event_gc_config_change in + # gajim.py) self.print_conversation(\ - _('Any occupant is allowed to see your full JID')) + _('Any occupant is allowed to see your full JID')) if '170' in statusCode: - # Can be a message (see handle_event_gc_config_change in gajim.py) + # Can be a message (see handle_event_gc_config_change in + # gajim.py) self.print_conversation(_('Room logging is enabled')) if '201' in statusCode: self.print_conversation(_('A new room has been created')) if '210' in statusCode: self.print_conversation(\ - _('The server has assigned or modified your roomnick')) + _('The server has assigned or modified your roomnick')) if show in ('offline', 'error'): if statusCode: if '307' in statusCode: if actor is None: # do not print 'kicked by None' s = _('%(nick)s has been kicked: %(reason)s') % { - 'nick': nick, - 'reason': reason } + 'nick': nick, + 'reason': reason } else: - s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } + s = _('%(nick)s has been kicked by %(who)s: ' + '%(reason)s') % {'nick': nick, 'who': actor, + 'reason': reason } self.print_conversation(s, 'info', tim=tim, graphics=False) if nick == self.nick and not gajim.config.get( 'muc_autorejoin_on_kick'): @@ -1239,13 +1298,11 @@ class GroupchatControl(ChatControlBase): elif '301' in statusCode: if actor is None: # do not print 'banned by None' s = _('%(nick)s has been banned: %(reason)s') % { - 'nick': nick, - 'reason': reason } + 'nick': nick, 'reason': reason } else: - s = _('%(nick)s has been banned by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } + s = _('%(nick)s has been banned by %(who)s: ' + '%(reason)s') % { 'nick': nick, 'who': actor, + 'reason': reason } self.print_conversation(s, 'info', tim=tim, graphics=False) if nick == self.nick: self.autorejoin = False @@ -1257,69 +1314,75 @@ class GroupchatControl(ChatControlBase): s = _('You are now known as %s') % new_nick # Stop all E2E sessions nick_list = gajim.contacts.get_nick_list(self.account, - self.room_jid) + self.room_jid) for nick_ in nick_list: fjid_ = self.room_jid + '/' + nick_ - ctrl = gajim.interface.msg_win_mgr.get_control(fjid_, - self.account) + ctrl = gajim.interface.msg_win_mgr.get_control( + fjid_, self.account) if ctrl and ctrl.session and \ ctrl.session.enable_encryption: thread_id = ctrl.session.thread_id ctrl.session.terminate_e2e() - gajim.connections[self.account].delete_session(fjid_, - thread_id) + gajim.connections[self.account].delete_session( + fjid_, thread_id) ctrl.no_autonegotiation = False else: s = _('%(nick)s is now known as %(new_nick)s') % { - 'nick': nick, 'new_nick': new_nick} + 'nick': nick, 'new_nick': new_nick} # We add new nick to muc roster here, so we don't see - # that "new_nick has joined the room" when he just changed nick. + # that "new_nick has joined the room" when he just changed + # nick. # add_contact_to_roster will be called a second time # after that, but that doesn't hurt - self.add_contact_to_roster(new_nick, show, role, affiliation, - status, jid) + self.add_contact_to_roster(new_nick, show, role, + affiliation, status, jid) if nick in self.attention_list: self.attention_list.remove(nick) # keep nickname color if nick in self.gc_custom_colors: self.gc_custom_colors[new_nick] = \ - self.gc_custom_colors[nick] + self.gc_custom_colors[nick] # rename vcard / avatar puny_jid = helpers.sanitize_filename(self.room_jid) puny_nick = helpers.sanitize_filename(nick) puny_new_nick = helpers.sanitize_filename(new_nick) - old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + old_path = os.path.join(gajim.VCARD_PATH, puny_jid, + puny_nick) new_path = os.path.join(gajim.VCARD_PATH, puny_jid, - puny_new_nick) + puny_new_nick) files = {old_path: new_path} path = os.path.join(gajim.AVATAR_PATH, puny_jid) # possible extensions for ext in ('.png', '.jpeg', '_notif_size_bw.png', '_notif_size_colored.png'): files[os.path.join(path, puny_nick + ext)] = \ - os.path.join(path, puny_new_nick + ext) + os.path.join(path, puny_new_nick + ext) for old_file in files: - if os.path.exists(old_file) and old_file != files[old_file]: - if os.path.exists(files[old_file]) and helpers.windowsify( - old_file) != helpers.windowsify(files[old_file]): - # Windows require this, but os.remove('test') will also - # remove 'TEST' + if os.path.exists(old_file) and old_file != \ + files[old_file]: + if os.path.exists(files[old_file]) and \ + helpers.windowsify(old_file) != helpers.windowsify( + files[old_file]): + # Windows require this, but os.remove('test') + # will also remove 'TEST' os.remove(files[old_file]) os.rename(old_file, files[old_file]) self.print_conversation(s, 'info', tim=tim, graphics=False) elif '321' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, 'reason': _('affiliation changed') } + s = _('%(nick)s has been removed from the room ' + '(%(reason)s)') % { 'nick': nick, + 'reason': _('affiliation changed') } self.print_conversation(s, 'info', tim=tim, graphics=False) elif '322' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, - 'reason': _('room configuration changed to members-only') } + s = _('%(nick)s has been removed from the room ' + '(%(reason)s)') % { 'nick': nick, + 'reason': _('room configuration changed to ' + 'members-only') } self.print_conversation(s, 'info', tim=tim, graphics=False) elif '332' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, - 'reason': _('system shutdown') } + s = _('%(nick)s has been removed from the room ' + '(%(reason)s)') % {'nick': nick, + 'reason': _('system shutdown') } self.print_conversation(s, 'info', tim=tim, graphics=False) # Room has been destroyed. elif 'destroyed' in statusCode: @@ -1331,16 +1394,19 @@ class GroupchatControl(ChatControlBase): self.remove_contact(nick) self.draw_all_roles() else: - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) c.show = show c.status = status if nick == self.nick and (not statusCode or \ '303' not in statusCode): # We became offline self.got_disconnected() contact = gajim.contacts.\ - get_contact_with_highest_priority(self.account, self.room_jid) + get_contact_with_highest_priority(self.account, + self.room_jid) if contact: - gajim.interface.roster.draw_contact(self.room_jid, self.account) + gajim.interface.roster.draw_contact(self.room_jid, + self.account) if self.parent_win: self.parent_win.redraw_tab(self) else: @@ -1351,21 +1417,24 @@ class GroupchatControl(ChatControlBase): self.nick = nick s = _('You are now known as %s') % nick self.print_conversation(s, 'info', tim=tim, graphics=False) - iter_ = self.add_contact_to_roster(nick, show, role, affiliation, - status, jid) + iter_ = self.add_contact_to_roster(nick, show, role, + affiliation, status, jid) newly_created = True self.draw_all_roles() - if statusCode and '201' in statusCode: # We just created the room - gajim.connections[self.account].request_gc_config(self.room_jid) + if statusCode and '201' in statusCode: + # We just created the room + gajim.connections[self.account].request_gc_config( + self.room_jid) else: - gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) + gc_c = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick) if not gc_c: - log.error('%s has an iter, but no gc_contact instance') + log.error('%s has an iter, but no gc_contact instance' % \ + nick) return # Re-get vcard if avatar has changed - # We do that here because we may request it to the real JID if we - # knows it. connections.py doesn't know it. + # We do that here because we may request it to the real JID if + # we knows it. connections.py doesn't know it. con = gajim.connections[self.account] if gc_c and gc_c.jid: real_jid = gc_c.jid @@ -1399,12 +1468,12 @@ class GroupchatControl(ChatControlBase): if affiliation != actual_affiliation: if actor: st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s by %(actor)s') % {'nick': nick_jid, - 'affiliation': affiliation, 'actor': actor} + '%(affiliation)s by %(actor)s') % {'nick': nick_jid, + 'affiliation': affiliation, 'actor': actor} else: st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s') % {'nick': nick_jid, - 'affiliation': affiliation} + '%(affiliation)s') % {'nick': nick_jid, + 'affiliation': affiliation} if reason: st += ' (%s)' % reason self.print_conversation(st, tim=tim, graphics=False) @@ -1412,24 +1481,24 @@ class GroupchatControl(ChatControlBase): actual_role = self.get_role(nick) if role != actual_role: self.remove_contact(nick) - self.add_contact_to_roster(nick, show, role, - affiliation, status, jid) + self.add_contact_to_roster(nick, show, role, affiliation, + status, jid) self.draw_role(actual_role) self.draw_role(role) if actor: - st = _('** Role of %(nick)s has been set to %(role)s by ' - '%(actor)s') % {'nick': nick_jid, 'role': role, - 'actor': actor} + st = _('** Role of %(nick)s has been set to %(role)s ' + 'by %(actor)s') % {'nick': nick_jid, 'role': role, + 'actor': actor} else: - st = _('** Role of %(nick)s has been set to %(role)s') % { - 'nick': nick_jid, 'role': role} + st = _('** Role of %(nick)s has been set to ' + '%(role)s') % {'nick': nick_jid, 'role': role} if reason: st += ' (%s)' % reason self.print_conversation(st, tim=tim, graphics=False) right_changed = True else: if gc_c.show == show and gc_c.status == status and \ - gc_c.affiliation == affiliation: # no change + gc_c.affiliation == affiliation: # no change return gc_c.show = show gc_c.affiliation = affiliation @@ -1458,14 +1527,14 @@ class GroupchatControl(ChatControlBase): st = _('%s has joined the group chat') % nick_jid elif print_status == 'all': st = _('%(nick)s is now %(status)s') % {'nick': nick_jid, - 'status': helpers.get_uf_show(show)} + 'status': helpers.get_uf_show(show)} if st: if status: st += ' (' + status + ')' self.print_conversation(st, tim=tim, graphics=False) def add_contact_to_roster(self, nick, show, role, affiliation, status, - jid=''): + jid=''): model = self.list_treeview.get_model() role_name = helpers.get_uf_role(role, plural=True) @@ -1483,14 +1552,16 @@ class GroupchatControl(ChatControlBase): role_iter = self.get_role_iter(role) if not role_iter: role_iter = model.append(None, - (gajim.interface.jabber_state_images['16']['closed'], role, - 'role', role_name, None)) + (gajim.interface.jabber_state_images['16']['closed'], role, + 'role', role_name, None)) self.draw_all_roles() iter_ = model.append(role_iter, (None, nick, 'contact', name, None)) - if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account, - name=nick, show=show, status=status, role=role, - affiliation=affiliation, jid=j, resource=resource) + if not nick in gajim.contacts.get_nick_list(self.account, + self.room_jid): + gc_contact = gajim.contacts.create_gc_contact( + room_jid=self.room_jid, account=self.account, + name=nick, show=show, status=status, role=role, + affiliation=affiliation, jid=j, resource=resource) gajim.contacts.add_gc_contact(self.account, gc_contact) self.draw_contact(nick) self.draw_avatar(nick) @@ -1501,13 +1572,11 @@ class GroupchatControl(ChatControlBase): fake_jid = self.room_jid + '/' + nick pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid) if pixbuf == 'ask': - if j: - fjid = j - if resource: - fjid += '/' + resource - gajim.connections[self.account].request_vcard(fjid, fake_jid) + if j and not self.is_anonymous: + gajim.connections[self.account].request_vcard(j, fake_jid) else: - gajim.connections[self.account].request_vcard(fake_jid, fake_jid) + gajim.connections[self.account].request_vcard(fake_jid, + fake_jid) if nick == self.nick: # we became online self.got_connected() self.list_treeview.expand_row((model.get_path(role_iter)), False) @@ -1557,12 +1626,13 @@ class GroupchatControl(ChatControlBase): if not message: return + label = self.get_seclabel() if message != '' or message != '\n': self.save_sent_message(message) # Send the message gajim.connections[self.account].send_gc_message(self.room_jid, - message, xhtml=xhtml) + message, xhtml=xhtml, label=label) self.msg_textview.get_buffer().set_text('') self.msg_textview.grab_focus() @@ -1594,12 +1664,12 @@ class GroupchatControl(ChatControlBase): ctrl.parent_win = None gajim.interface.roster.add_groupchat(self.contact.jid, self.account, - status = self.subject) + status = self.subject) del win._controls[self.account][self.contact.jid] def shutdown(self, status='offline'): - # PluginSystem: calling shutdown of super class (ChatControlBase) + # PluginSystem: calling shutdown of super class (ChatControlBase) # to let it remove it's GUI extension points super(GroupchatControl, self).shutdown() @@ -1618,10 +1688,11 @@ class GroupchatControl(ChatControlBase): for nick in nick_list: # Update pm chat window fjid = self.room_jid + '/' + nick - ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account) + ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, + self.account) if ctrl: - contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) + contact = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick) contact.show = 'offline' contact.status = '' ctrl.update_ui() @@ -1632,7 +1703,7 @@ class GroupchatControl(ChatControlBase): if sess.enable_encryption: sess.terminate_e2e() gajim.connections[self.account].delete_session(fjid, - sess.thread_id) + sess.thread_id) # They can already be removed by the destroy function if self.room_jid in gajim.contacts.get_gc_list(self.account): gajim.contacts.remove_room(self.account, self.room_jid) @@ -1654,9 +1725,9 @@ class GroupchatControl(ChatControlBase): includes = gajim.config.get('confirm_close_muc_rooms').split(' ') excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') # whether to ask for comfirmation before closing muc - if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ - and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ - in excludes: + if (gajim.config.get('confirm_close_muc') or self.room_jid in includes)\ + and gajim.gc_connected[self.account][self.room_jid] and self.room_jid \ + not in excludes: return False return True @@ -1673,9 +1744,9 @@ class GroupchatControl(ChatControlBase): includes = gajim.config.get('confirm_close_muc_rooms').split(' ') excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') # whether to ask for comfirmation before closing muc - if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ - and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ - in excludes: + if (gajim.config.get('confirm_close_muc') or self.room_jid in includes)\ + and gajim.gc_connected[self.account][self.room_jid] and self.room_jid \ + not in excludes: def on_ok(clicked): if clicked: @@ -1690,13 +1761,13 @@ class GroupchatControl(ChatControlBase): on_no(self) pritext = _('Are you sure you want to leave group chat "%s"?')\ - % self.name + % self.name sectext = _('If you close this window, you will be disconnected ' - 'from this group chat.') + 'from this group chat.') dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=on_ok, - on_response_cancel=on_cancel) + _('_Do not ask me again'), on_response_ok=on_ok, + on_response_cancel=on_cancel) return on_yes(self) @@ -1726,11 +1797,12 @@ class GroupchatControl(ChatControlBase): def on_ok(subject): # Note, we don't update self.subject since we don't know whether it # will work yet - gajim.connections[self.account].send_gc_subject(self.room_jid, subject) + gajim.connections[self.account].send_gc_subject(self.room_jid, + subject) dialogs.InputTextDialog(_('Changing Subject'), - _('Please specify the new subject:'), input_str=self.subject, - ok_handler=on_ok) + _('Please specify the new subject:'), input_str=self.subject, + ok_handler=on_ok) def _on_change_nick_menuitem_activate(self, widget): if 'change_nick_dialog' in gajim.interface.instances: @@ -1739,18 +1811,20 @@ class GroupchatControl(ChatControlBase): title = _('Changing Nickname') prompt = _('Please specify the new nickname you want to use:') gajim.interface.instances['change_nick_dialog'] = \ - dialogs.ChangeNickDialog(self.account, self.room_jid, title, - prompt) + dialogs.ChangeNickDialog(self.account, self.room_jid, title, + prompt, change_nick=True) def _on_configure_room_menuitem_activate(self, widget): - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick) + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, + self.nick) if c.affiliation == 'owner': gajim.connections[self.account].request_gc_config(self.room_jid) elif c.affiliation == 'admin': if self.room_jid not in gajim.interface.instances[self.account][ 'gc_config']: - gajim.interface.instances[self.account]['gc_config'][self.room_jid]\ - = config.GroupchatConfigWindow(self.account, self.room_jid) + gajim.interface.instances[self.account]['gc_config'][ + self.room_jid] = config.GroupchatConfigWindow(self.account, + self.room_jid) def _on_destroy_room_menuitem_activate(self, widget): def on_ok(reason, jid): @@ -1762,25 +1836,25 @@ class GroupchatControl(ChatControlBase): dialogs.ErrorDialog(_('Invalid group chat Jabber ID'), _('The group chat Jabber ID has not allowed characters.')) return - gajim.connections[self.account].destroy_gc_room(self.room_jid, reason, - jid) + gajim.connections[self.account].destroy_gc_room(self.room_jid, + reason, jid) # Ask for a reason - dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid, - _('You are going to definitively destroy this room.\n' - 'You may specify a reason below:'), - _('You may also enter an alternate venue:'), ok_handler=on_ok) + dialogs.DoubleInputDialog(_('Destroying %s') % self.room_jid, + _('You are going to definitively destroy this room.\n' + 'You may specify a reason below:'), + _('You may also enter an alternate venue:'), ok_handler=on_ok) def _on_bookmark_room_menuitem_activate(self, widget): """ Bookmark the room, without autojoin and not minimized """ password = gajim.gc_passwords.get(self.room_jid, '') - gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \ - '0', '0', password, self.nick) + gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid,\ + '0', '0', password, self.nick) def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): + target_type, timestamp): # Invite contact to groupchat treeview = gajim.interface.roster.tree model = treeview.get_model() @@ -1797,7 +1871,7 @@ class GroupchatControl(ChatControlBase): gajim.connections[self.account].send_invite(self.room_jid, contact_jid) def handle_message_textview_mykey_press(self, widget, event_keyval, - event_keymod): + event_keymod): # NOTE: handles mykeypress which is custom signal connected to this # CB in new_room(). for this singal see message_textview.py @@ -1814,16 +1888,17 @@ class GroupchatControl(ChatControlBase): cursor_position = message_buffer.get_insert() end_iter = message_buffer.get_iter_at_mark(cursor_position) text = message_buffer.get_text(start_iter, end_iter, False).decode( - 'utf-8') + 'utf-8') splitted_text = text.split() # HACK: Not the best soltution. if (text.startswith(self.COMMAND_PREFIX) and not - text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1): - return super(GroupchatControl, - self).handle_message_textview_mykey_press(widget, event_keyval, - event_keymod) + text.startswith(self.COMMAND_PREFIX * 2) and \ + len(splitted_text) == 1): + return super(GroupchatControl, self).\ + handle_message_textview_mykey_press(widget, event_keyval, + event_keymod) # nick completion # check if tab is pressed with empty message @@ -1844,14 +1919,14 @@ class GroupchatControl(ChatControlBase): if len(self.nick_hits) and self.last_key_tabs and \ text[:-after_nick_len].endswith(self.nick_hits[0]): # we should cycle - # Previous nick in list may had a space inside, so we check text and - # not splitted_text and store it into 'begin' var + # Previous nick in list may had a space inside, so we check text + # and not splitted_text and store it into 'begin' var self.nick_hits.append(self.nick_hits[0]) begin = self.nick_hits.pop(0) else: self.nick_hits = [] # clear the hit list list_nick = gajim.contacts.get_nick_list(self.account, - self.room_jid) + self.room_jid) list_nick.sort(key=unicode.lower) # case-insensitive sort if begin == '': # empty message, show lasts nicks that highlighted us first @@ -1884,14 +1959,14 @@ class GroupchatControl(ChatControlBase): # have to accomodate for the added space from last # completion start_iter.backward_chars(len(begin) + \ - len(gc_refer_to_nick_char)) + len(gc_refer_to_nick_char)) else: start_iter.backward_chars(len(begin)) message_buffer.delete(start_iter, end_iter) # get a shell-like completion - # if there's more than one nick for this completion, complete only - # the part that all these nicks have in common + # if there's more than one nick for this completion, complete + # only the part that all these nicks have in common if gajim.config.get('shell_like_completion') and \ len(self.nick_hits) > 1: end = False @@ -1905,13 +1980,14 @@ class GroupchatControl(ChatControlBase): completion = completion[:-1] break # if the current nick matches a COMPLETE existing nick, - # and if the user tab TWICE, complete that nick (with the "add") + # and if the user tab TWICE, complete that nick (with the + # "add") if self.last_key_tabs: for nick in self.nick_hits: if nick == completion: - # The user seems to want this nick, so - # complete it as if it were the only nick - # available + # The user seems to want this nick, so + # complete it as if it were the only nick + # available add = gc_refer_to_nick_char + ' ' else: completion = self.nick_hits[0] @@ -1950,11 +2026,11 @@ class GroupchatControl(ChatControlBase): """ def on_ok(reason): gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'none', reason) + 'none', reason) # ask for reason dialogs.InputDialog(_('Kicking %s') % nick, - _('You may specify a reason below:'), ok_handler=on_ok) + _('You may specify a reason below:'), ok_handler=on_ok) def mk_menu(self, event, iter_): """ @@ -1971,7 +2047,7 @@ class GroupchatControl(ChatControlBase): # looking for user's affiliation and role user_nick = self.nick user_affiliation = gajim.contacts.get_gc_contact(self.account, - self.room_jid, user_nick).affiliation + self.room_jid, user_nick).affiliation user_role = self.get_role(user_nick) # making menu from gtk builder @@ -1996,7 +2072,7 @@ class GroupchatControl(ChatControlBase): target_affiliation in ('admin', 'owner'): item.set_sensitive(False) id_ = item.connect('activate', self.on_voice_checkmenuitem_activate, - nick) + nick) self.handlers[id_] = item item = xml.get_object('moderator_checkmenuitem') @@ -2005,7 +2081,7 @@ class GroupchatControl(ChatControlBase): target_affiliation in ('admin', 'owner'): item.set_sensitive(False) id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate, - nick) + nick) self.handlers[id_] = item item = xml.get_object('ban_menuitem') @@ -2019,23 +2095,27 @@ class GroupchatControl(ChatControlBase): item = xml.get_object('member_checkmenuitem') item.set_active(target_affiliation != 'none') if not user_affiliation in ('admin', 'owner') or \ - (user_affiliation != 'owner' and target_affiliation in ('admin', 'owner')): + (user_affiliation != 'owner' and target_affiliation in ('admin', + 'owner')): item.set_sensitive(False) - id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid) + id_ = item.connect('activate', self.on_member_checkmenuitem_activate, + jid) self.handlers[id_] = item item = xml.get_object('admin_checkmenuitem') item.set_active(target_affiliation in ('admin', 'owner')) if not user_affiliation == 'owner': item.set_sensitive(False) - id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid) + id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, + jid) self.handlers[id_] = item item = xml.get_object('owner_checkmenuitem') item.set_active(target_affiliation == 'owner') if not user_affiliation == 'owner': item.set_sensitive(False) - id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid) + id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, + jid) self.handlers[id_] = item item = xml.get_object('information_menuitem') @@ -2212,8 +2292,8 @@ class GroupchatControl(ChatControlBase): self.tooltip.id = row nick = model[iter_][C_NICK].decode('utf-8') self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, gajim.contacts.get_gc_contact(account, - self.room_jid, nick)) + self.show_tooltip, gajim.contacts.get_gc_contact( + account, self.room_jid, nick)) def on_list_treeview_leave_notify_event(self, widget, event): props = widget.get_path_at_pos(int(event.x), int(event.y)) @@ -2233,7 +2313,7 @@ class GroupchatControl(ChatControlBase): rect = self.list_treeview.get_cell_area(props[0], props[1]) position = self.list_treeview.window.get_origin() self.tooltip.show_tooltip(contact, rect.height, - position[1] + rect.y) + position[1] + rect.y) else: self.tooltip.hide_tooltip() @@ -2242,97 +2322,98 @@ class GroupchatControl(ChatControlBase): Grant voice privilege to a user """ gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'participant') + 'participant') def revoke_voice(self, widget, nick): """ Revoke voice privilege to a user """ gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'visitor') + 'visitor') def grant_moderator(self, widget, nick): """ Grant moderator privilege to a user """ gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'moderator') + 'moderator') def revoke_moderator(self, widget, nick): """ Revoke moderator privilege to a user """ gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'participant') + 'participant') def ban(self, widget, jid): """ Ban a user """ def on_ok(reason): - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'outcast', reason) + gajim.connections[self.account].gc_set_affiliation(self.room_jid, + jid, 'outcast', reason) # to ban we know the real jid. so jid is not fakejid nick = gajim.get_nick_from_jid(jid) # ask for reason dialogs.InputDialog(_('Banning %s') % nick, - _('You may specify a reason below:'), ok_handler=on_ok) + _('You may specify a reason below:'), ok_handler=on_ok) def grant_membership(self, widget, jid): """ Grant membership privilege to a user """ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'member') + 'member') def revoke_membership(self, widget, jid): """ Revoke membership privilege to a user """ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'none') + 'none') def grant_admin(self, widget, jid): """ Grant administrative privilege to a user """ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'admin') + 'admin') def revoke_admin(self, widget, jid): """ Revoke administrative privilege to a user """ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'member') + 'member') def grant_owner(self, widget, jid): """ Grant owner privilege to a user """ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'owner') + 'owner') def revoke_owner(self, widget, jid): """ Revoke owner privilege to a user """ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'admin') + 'admin') def on_info(self, widget, nick): """ Call vcard_information_window class to display user's information """ - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) contact = gc_contact.as_contact() if contact.jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][contact.jid].window.\ - present() + gajim.interface.instances[self.account]['infos'][contact.jid].\ + window.present() else: gajim.interface.instances[self.account]['infos'][contact.jid] = \ - vcard.VcardWindow(contact, self.account, gc_contact) + vcard.VcardWindow(contact, self.account, gc_contact) def on_history(self, widget, nick): jid = gajim.construct_fjid(self.room_jid, nick) @@ -2347,7 +2428,7 @@ class GroupchatControl(ChatControlBase): if fjid in connection.blocked_contacts: return new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']} + 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']} connection.blocked_list.append(new_rule) connection.blocked_contacts.append(fjid) self.draw_contact(nick) @@ -2381,7 +2462,7 @@ class GroupchatControl(ChatControlBase): connection.del_privacy_list('block') if 'blocked_contacts' in gajim.interface.instances[self.account]: gajim.interface.instances[self.account]['blocked_contacts'].\ - privacy_list_received([]) + privacy_list_received([]) def on_voice_checkmenuitem_activate(self, widget, nick): if widget.get_active(): diff --git a/src/groups.py b/src/groups.py index 298e1f4b6..f38cce238 100644 --- a/src/groups.py +++ b/src/groups.py @@ -1,8 +1,8 @@ # -*- coding:utf-8 -*- ## src/groups.py ## -## Copyright (C) 2006 Yann Leboulanger <asterix AT lagaule.org> -## Tomasz Melcer <liori AT exroot.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org> ## ## This file is part of Gajim. ## diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py index f4d6d73c1..1b85e93bb 100644 --- a/src/gtkexcepthook.py +++ b/src/gtkexcepthook.py @@ -2,7 +2,7 @@ ## src/gtkexcepthook.py ## ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de> ## ## This file is part of Gajim. diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index 9d5188207..217b8ba51 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/gtkgui_helpers.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com> @@ -189,7 +189,8 @@ def get_default_font(): xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') if xdg_config_home == '': xdg_config_home = os.path.expanduser('~/.config') # default - xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') + xfce_config_file = os.path.join(xdg_config_home, + 'xfce4/mcs_settings/gtk.xml') kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') @@ -201,7 +202,8 @@ def get_default_font(): return line[start:line.find('"', start)].decode('utf-8') except Exception: #we talk about file - print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file + print >> sys.stderr, _('Error: cannot open %s for reading') % \ + xfce_config_file elif os.path.exists(kde_config_file): try: @@ -216,7 +218,8 @@ def get_default_font(): return font_string.decode('utf-8') except Exception: #we talk about file - print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file + print >> sys.stderr, _('Error: cannot open %s for reading') % \ + kde_config_file return None @@ -261,7 +264,7 @@ def get_running_processes(): files = os.listdir('/proc') # files that doesn't have only digits in names... - files = filter(str.isdigit, files) + files = [f for f in files if f.isdigit()] # files that aren't directories... files = [f for f in files if os.path.isdir('/proc/' + f)] @@ -275,7 +278,8 @@ def get_running_processes(): files = [f for f in files if os.path.islink('/proc/' + f + '/exe')] # list of processes - processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] + processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f \ + in files] return processes return [] @@ -314,7 +318,8 @@ class HashDigest: def cleanID(self, id_): id_ = id_.strip().lower() - for strip in (' :.-_'): id_ = id_.replace(strip, '') + for strip in (' :.-_'): + id_ = id_.replace(strip, '') return id_ def __eq__(self, other): @@ -436,7 +441,8 @@ def get_abspath_for_script(scriptname, want_type = False): os.chmod(path_to_script, 0700) except OSError: # do not traceback (could be a permission problem) #we talk about a file here - s = _('Could not write to %s. Session Management support will not work') % path_to_script + s = _('Could not write to %s. Session Management support will ' + 'not work') % path_to_script print >> sys.stderr, s else: # normal user (not svn user) @@ -541,13 +547,13 @@ def file_is_locked(path_to_file): try: # try make a handle for READING the file hfile = win32file.CreateFile( - path_to_file, # path to file - win32con.GENERIC_READ, # open for reading - 0, # do not share with other proc + path_to_file, # path to file + win32con.GENERIC_READ, # open for reading + 0, # do not share with other proc secur_att, - win32con.OPEN_EXISTING, # existing file only + win32con.OPEN_EXISTING, # existing file only win32con.FILE_ATTRIBUTE_NORMAL, # normal file - 0 # no attr. template + 0 # no attr. template ) except pywintypes.error: return True @@ -555,7 +561,7 @@ def file_is_locked(path_to_file): hfile.Close() return False -def _get_fade_color(treeview, selected, focused): +def get_fade_color(treeview, selected, focused): """ Get a gdk color that is between foreground and background in 0.3 0.7 respectively colors of the cell for the given treeview @@ -621,7 +627,8 @@ def get_avatar_pixbuf_from_cache(fjid, use_local=True): # don't show avatar for the transport itself return None - if any(jid in gajim.contacts.get_gc_list(acc) for acc in gajim.connections): + if any(jid in gajim.contacts.get_gc_list(acc) for acc in \ + gajim.contacts.get_accounts()): is_groupchat_contact = True else: is_groupchat_contact = False @@ -756,8 +763,10 @@ def possibly_set_gajim_as_xmpp_handler(): # setting for GNOME/Gconf client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True) - client.set_string('/desktop/gnome/url-handlers/xmpp/command', command) - client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False) + client.set_string('/desktop/gnome/url-handlers/xmpp/command', + command) + client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', + False) # setting for KDE if path_to_kde_file is not None: # user has run kde at least once @@ -780,7 +789,8 @@ Description=xmpp ''' % command) f.close() except IOError: - log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True) + log.debug("I/O Error writing settings to %s", + repr(path_to_kde_file), exc_info=True) else: # no gajim remote, stop ask user everytime gajim.config.set('check_if_gajim_is_default', False) @@ -807,12 +817,12 @@ Description=xmpp # xmpp: is currently handled by another program, so ask the user pritext = _('Gajim is not the default Jabber client') sectext = _('Would you like to make Gajim the default Jabber client?') - checktext = _('Always check to see if Gajim is the default Jabber client ' - 'on startup') + checktext = _('Always check to see if Gajim is the default Jabber ' + 'client on startup') def on_cancel(checked): gajim.config.set('check_if_gajim_is_default', checked) dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext, - set_gajim_as_xmpp_handler, on_cancel) + set_gajim_as_xmpp_handler, on_cancel) if gajim.config.get('check_if_gajim_is_default'): dlg.checkbutton.set_active(True) @@ -849,12 +859,12 @@ def get_possible_button_event(event): def destroy_widget(widget): widget.destroy() -def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name=''): +def on_avatar_save_as_menuitem_activate(widget, jid, default_name=''): def on_continue(response, file_path): if response < 0: return pixbuf = get_avatar_pixbuf_from_cache(jid) - path, extension = os.path.splitext(file_path) + extension = os.path.splitext(file_path)[1] if not extension: # Silently save as Jpeg image image_format = 'jpeg' @@ -875,9 +885,10 @@ def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name=''): def on_ok(file_path, pixbuf): pixbuf.save(file_path, 'jpeg') dialogs.ConfirmationDialog(_('Extension not supported'), - _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?' - ) % {'type': image_format, 'new_filename': new_file_path}, - on_response_ok = (on_ok, new_file_path, pixbuf)) + _('Image cannot be saved in %(type)s format. Save as ' + '%(new_filename)s?') % {'type': image_format, + 'new_filename': new_file_path}, + on_response_ok = (on_ok, new_file_path, pixbuf)) else: dialog.destroy() @@ -888,22 +899,21 @@ def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name=''): # check if we have write permissions if not os.access(file_path, os.W_OK): file_name = os.path.basename(file_path) - dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % - file_name), - _('A file with this name already exists and you do not have ' - 'permission to overwrite it.')) + dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"') % \ + file_name, _('A file with this name already exists and you ' + 'do not have permission to overwrite it.')) return dialog2 = dialogs.FTOverwriteConfirmationDialog( - _('This file already exists'), _('What do you want to do?'), - propose_resume=False, on_response=(on_continue, file_path)) + _('This file already exists'), _('What do you want to do?'), + propose_resume=False, on_response=(on_continue, file_path)) dialog2.set_transient_for(dialog) dialog2.set_destroy_with_parent(True) else: dirname = os.path.dirname(file_path) if not os.access(dirname, os.W_OK): dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ - dirname, _('You do not have permission to create files in this' - ' directory.')) + dirname, _('You do not have permission to create files in ' + 'this directory.')) return on_continue(0, file_path) @@ -912,15 +922,15 @@ def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name=''): dialog.destroy() dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'), - action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK), - default_response=gtk.RESPONSE_OK, - current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok, - on_response_cancel=on_cancel) + action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, + gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK), + default_response=gtk.RESPONSE_OK, + current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok, + on_response_cancel=on_cancel) dialog.set_current_name(default_name + '.jpeg') dialog.connect('delete-event', lambda widget, event: - on_cancel(widget)) + on_cancel(widget)) def on_bm_header_changed_state(widget, event): widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state @@ -1124,7 +1134,7 @@ def __label_size_allocate(widget, allocation): # set wrap width to the pango.Layout of the labels ### layout.set_width (allocation.width * pango.SCALE) - lw, lh = layout.get_size () + lh = layout.get_size()[1] if lh_old != lh: widget.set_size_request (-1, lh / pango.SCALE) diff --git a/src/gui_interface.py b/src/gui_interface.py index f62e67682..646036b51 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/gajim.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com> ## Norman Rasmussen <norman AT rasmussen.co.za> @@ -78,7 +78,6 @@ from common import socks5 from common import helpers from common import dataforms from common import passwords -from common import pep from common import logging_helpers import roster_window @@ -120,6 +119,15 @@ class Interface: #('ERROR', account, (title_text, section_text)) dialogs.ErrorDialog(data[0], data[1]) + def handle_event_db_error(self, unused, data): + #('DB_ERROR', account, (title_text, section_text)) + if self.db_error_dialog: + return + self.db_error_dialog = dialogs.ErrorDialog(data[0], data[1]) + def destroyed(win): + self.db_error_dialog = None + self.db_error_dialog.connect('destroy', destroyed) + def handle_event_information(self, unused, data): #('INFORMATION', account, (title_text, section_text)) dialogs.InformationDialog(data[0], data[1]) @@ -129,20 +137,19 @@ class Interface: room_jid = data[0] title = _('Unable to join group chat') prompt = _('Your desired nickname in group chat %s is in use or ' - 'registered by another occupant.\nPlease specify another nickname ' - 'below:') % room_jid + 'registered by another occupant.\nPlease specify another nickname ' + 'below:') % room_jid check_text = _('Always use this nickname when there is a conflict') if 'change_nick_dialog' in self.instances: self.instances['change_nick_dialog'].add_room(account, room_jid, - prompt) + prompt) else: self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( - account, room_jid, title, prompt) + account, room_jid, title, prompt) def handle_event_http_auth(self, account, data): #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) def response(account, iq_obj, answer): - self.dialog.destroy() gajim.connections[account].build_http_auth_answer(iq_obj, answer) def on_yes(is_checked, account, iq_obj): @@ -153,10 +160,10 @@ class Interface: sec_msg = _('Do you accept this request on account %s?') % account if data[4]: sec_msg = data[4] + '\n' + sec_msg - self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' - '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], - 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), - on_response_no=(response, account, data[3], 'no')) + dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' + '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], + 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), + on_response_no=(response, account, data[3], 'no')) def handle_event_error_answer(self, account, array): #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) @@ -213,10 +220,12 @@ class Interface: model = self.roster.status_combobox.get_model() if show in ('offline', 'error'): for name in self.instances[account]['online_dialog'].keys(): - # .keys() is needed to not have a dictionary length changed during - # iteration error + # .keys() is needed to not have a dictionary length changed + # during iteration error self.instances[account]['online_dialog'][name].destroy() - del self.instances[account]['online_dialog'][name] + if name in self.instances[account]['online_dialog']: + # destroy handler may have already removed it + del self.instances[account]['online_dialog'][name] for request in self.gpg_passphrase.values(): if request: request.interrupt() @@ -232,7 +241,8 @@ class Interface: # we stop blocking notifications of any kind # this prevents from getting the roster items as 'just signed in' # contacts. 30 seconds should be enough time - gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) + gobject.timeout_add_seconds(30, + self.unblock_signed_in_notifications, account) # sensitivity for this menuitem model[self.roster.status_message_menuitem_iter][3] = True @@ -284,7 +294,7 @@ class Interface: # FIXME: Drop and rewrite... statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] + 'invisible'] # Ignore invalid show if array[1] not in statuss: return @@ -294,6 +304,7 @@ class Interface: jid = array[0].split('/')[0] keyID = array[5] contact_nickname = array[7] + lcontact = [] # Get the proper keyID keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) @@ -308,8 +319,7 @@ class Interface: else: ji = jid - highest = gajim.contacts. \ - get_contact_with_highest_priority(account, jid) + highest = gajim.contacts.get_contact_with_highest_priority(account, jid) was_highest = (highest and highest.resource == resource) conn = gajim.connections[account] @@ -335,11 +345,12 @@ class Interface: contact1.contact_name = contact_nickname self.roster.draw_contact(jid, account) - if old_show == new_show and contact1.status == status_message and \ - contact1.priority == priority: # no change + if old_show == new_show and contact1.status == status_message \ + and contact1.priority == priority: # no change return else: - contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) + contact1 = gajim.contacts.get_first_contact_from_jid(account, + ji) if not contact1: # Presence of another resource of our # jid @@ -350,8 +361,8 @@ class Interface: if new_show < 2: return contact1 = gajim.contacts.create_self_contact(jid=ji, - account=account, show=array[1], status=status_message, - priority=priority, keyID=keyID, resource=resource) + account=account, show=array[1], status=status_message, + priority=priority, keyID=keyID, resource=resource) old_show = 0 gajim.contacts.add_contact(account, contact1) lcontact.append(contact1) @@ -374,20 +385,20 @@ class Interface: gajim.newly_added[account].append(contact1.jid) if contact1.jid in gajim.to_be_removed[account]: gajim.to_be_removed[account].remove(contact1.jid) - gobject.timeout_add_seconds(5, self.roster.remove_newly_added, - contact1.jid, account) + gobject.timeout_add_seconds(5, + self.roster.remove_newly_added, contact1.jid, account) elif old_show > 1 and new_show == 0 and conn.connected > 1: if not contact1.jid in gajim.to_be_removed[account]: gajim.to_be_removed[account].append(contact1.jid) if contact1.jid in gajim.newly_added[account]: gajim.newly_added[account].remove(contact1.jid) self.roster.draw_contact(contact1.jid, account) - gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, - contact1.jid, account) + gobject.timeout_add_seconds(5, + self.roster.remove_to_be_removed, contact1.jid, account) # unset custom status - if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ - and conn.connected > 1): + if (old_show == 0 and new_show > 1) or \ + (old_show > 1 and new_show == 0 and conn.connected > 1): if account in self.status_sent_to_users and \ jid in self.status_sent_to_users[account]: del self.status_sent_to_users[account][jid] @@ -420,7 +431,7 @@ class Interface: account_ji = account + '/' + ji gajim.block_signed_in_notifications[account_ji] = True gobject.timeout_add_seconds(30, - self.unblock_signed_in_notifications, account_ji) + self.unblock_signed_in_notifications, account_ji) locations = (self.instances, self.instances[account]) for location in locations: if 'add_contact' in location: @@ -436,7 +447,7 @@ class Interface: # (when contact signs out or has errors) if array[1] in ('offline', 'error'): contact1.our_chatstate = contact1.chatstate = \ - contact1.composing_xep = None + contact1.composing_xep = None # TODO: This causes problems when another # resource signs off! @@ -450,7 +461,7 @@ class Interface: # there won't be any sessions here if the contact terminated # their sessions before going offline (which we do) for sess in conn.get_sessions(ji): - if (ji+'/'+resource) != str(sess.jid): + if (ji + '/' + resource) != str(sess.jid): continue if sess.control: sess.control.no_autonegotiation = False @@ -459,37 +470,86 @@ class Interface: conn.delete_session(jid, sess.thread_id) self.roster.chg_contact_status(contact1, array[1], status_message, - account) + account) # Notifications if old_show < 2 and new_show > 1: - notify.notify('contact_connected', jid, account, status_message) + show_notif = True + for c in lcontact: + if c.resource == resource: + # we look for other connected resources + continue + if c.show not in ('offline', 'error'): + show_notif = False + break + if show_notif: + # no other resource is connected, let's look in metacontacts + family = gajim.contacts.get_metacontacts_family(account, ji) + for info in family: + acct_ = info['account'] + jid_ = info['jid'] + c_ = gajim.contacts.get_contact_with_highest_priority( + acct_, jid_) + if not c_: + continue + if c_.show not in ('offline', 'error'): + show_notif = False + break + if show_notif: + notify.notify('contact_connected', jid, account, + status_message) if self.remote_ctrl: self.remote_ctrl.raise_signal('ContactPresence', (account, - array)) + array)) elif old_show > 1 and new_show < 2: - notify.notify('contact_disconnected', jid, account, status_message) + show_notif = True + for c in lcontact: + if c.resource == resource: + # we look for other connected resources + continue + if c.show not in ('offline', 'error'): + show_notif = False + break + if show_notif: + # no other resource is connected, let's look in metacontacts + family = gajim.contacts.get_metacontacts_family(account, ji) + for info in family: + acct_ = info['account'] + jid_ = info['jid'] + c_ = gajim.contacts.get_contact_with_highest_priority( + acct_, jid_) + if not c_: + continue + if c_.show not in ('offline', 'error'): + show_notif = False + break + if show_notif: + notify.notify('contact_disconnected', jid, account, + status_message) if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) + self.remote_ctrl.raise_signal('ContactAbsence', (account, + array)) # FIXME: stop non active file transfers # Status change (not connected/disconnected or # error (<1)) elif new_show > 1: notify.notify('status_change', jid, account, [new_show, - status_message]) + status_message]) if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactStatus', (account, array)) + self.remote_ctrl.raise_signal('ContactStatus', (account, + array)) else: # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't # follow the XEP, still the case in 2008. # It's maybe a GC_NOTIFY (specialy for MSN gc) self.handle_event_gc_notify(account, (jid, array[1], status_message, - array[3], None, None, None, None, None, [], None, None)) + array[3], None, None, None, None, None, [], None, None)) highest = gajim.contacts.get_contact_with_highest_priority(account, jid) is_highest = (highest and highest.resource == resource) - # disconnect the session from the ctrl if the highest resource has changed + # disconnect the session from the ctrl if the highest resource has + # changed if (was_highest and not is_highest) or (not was_highest and is_highest): ctrl = self.msg_win_mgr.get_control(jid, account) @@ -499,7 +559,8 @@ class Interface: ctrl.contact = highest def handle_event_msgerror(self, account, array): - #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) + #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, + # session])) full_jid_with_resource = array[0] jids = full_jid_with_resource.split('/', 1) jid = jids[0] @@ -526,7 +587,8 @@ class Interface: if session: ctrl = session.control else: - ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) + ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, + account) if not ctrl: tv = gc_control.list_treeview @@ -536,17 +598,18 @@ class Interface: show = model[iter_][3] else: show = 'offline' - gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, - name=nick, show=show) + gc_c = gajim.contacts.create_gc_contact(room_jid=jid, + account=account, name=nick, show=show) ctrl = self.new_private_chat(gc_c, account, session) ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') + 'code': array[1], 'msg': array[2]}, 'status') return gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: + 'code': array[1], 'msg': array[2]}, 'status') + if gc_control.parent_win and \ + gc_control.parent_win.get_active_jid() == jid: gc_control.set_subject(gc_control.subject) return @@ -563,7 +626,8 @@ class Interface: #('MSGSENT', account, (jid, msg, keyID)) msg = array[1] # do not play sound when standalone chatstate message (eg no msg) - if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): + if msg and gajim.config.get_per('soundevents', 'message_sent', + 'enabled'): helpers.play_sound('message_sent') def handle_event_msgnotsent(self, account, array): @@ -571,7 +635,8 @@ class Interface: msg = _('error while sending %(message)s ( %(error)s )') % { 'message': array[2], 'error': array[1]} if not array[4]: - # No session. This can happen when sending a message from gajim-remote + # No session. This can happen when sending a message from + # gajim-remote log.warn(msg) return array[4].roster_message(array[0], msg, array[3], account, @@ -592,10 +657,11 @@ class Interface: self.add_event(account, jid, 'subscription_request', (text, nick)) if helpers.allow_showing_notification(account): - path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48) + path = gtkgui_helpers.get_icon_path('gajim-subscription_request', + 48) event_type = _('Subscription request') notify.popup(event_type, jid, account, 'subscription_request', path, - event_type, jid) + event_type, jid) def handle_event_subscribed(self, account, array): #('SUBSCRIBED', account, (jid, resource)) @@ -604,24 +670,25 @@ class Interface: c = gajim.contacts.get_first_contact_from_jid(account, jid) c.resource = array[1] self.roster.remove_contact_from_groups(c.jid, account, - [_('Not in Roster'), _('Observers')], update=False) + [_('Not in Roster'), _('Observers')], update=False) else: keyID = '' attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() + 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] name = jid.split('@', 1)[0] name = name.split('%', 1)[0] contact1 = gajim.contacts.create_contact(jid=jid, account=account, - name=name, groups=[], show='online', status='online', - ask='to', resource=array[1], keyID=keyID) + name=name, groups=[], show='online', status='online', ask='to', + resource=array[1], keyID=keyID) gajim.contacts.add_contact(account, contact1) self.roster.add_contact(jid, account) dialogs.InformationDialog(_('Authorization accepted'), - _('The contact "%s" has authorized you to see his or her status.') - % jid) - if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): + _('The contact "%s" has authorized you to see his or her status.') + % jid) + if not gajim.config.get_per('accounts', account, + 'dont_ack_subscription'): gajim.connections[account].ack_subscribed(jid) if self.remote_ctrl: self.remote_ctrl.raise_signal('Subscribed', (account, array)) @@ -811,7 +878,8 @@ class Interface: def handle_event_gc_notify(self, account, array): #'GC_NOTIFY' (account, (room_jid, show, status, nick, - # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) + # role, affiliation, jid, reason, actor, statusCode, newNick, + # avatar_sha)) nick = array[3] if not nick: return @@ -829,14 +897,15 @@ class Interface: room_jid in self.minimized_controls[account]: control = self.minimized_controls[account][room_jid] - if not control or (control and control.type_id != message_control.TYPE_GC): + if not control or (control and \ + control.type_id != message_control.TYPE_GC): return control.chg_contact_status(nick, show, status, array[4], array[5], - array[6], array[7], array[8], array[9], array[10], array[11]) + array[6], array[7], array[8], array[9], array[10], array[11]) - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, room_jid) + contact = gajim.contacts.get_contact_with_highest_priority(account, + room_jid) if contact: self.roster.draw_contact(room_jid, account) @@ -846,9 +915,11 @@ class Interface: statusCode = array[9] if '303' in statusCode: new_nick = array[10] - ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ - % {'nick': nick, 'new_nick': new_nick}, 'status') - gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) + ctrl.print_conversation(_('%(nick)s is now known as ' + '%(new_nick)s') % {'nick': nick, 'new_nick': new_nick}, + 'status') + gc_c = gajim.contacts.get_gc_contact(account, room_jid, + new_nick) c = gc_c.as_contact() ctrl.gc_contact = gc_c ctrl.contact = c @@ -875,7 +946,8 @@ class Interface: 'nick': nick, 'status': uf_show}, 'status') if status: ctrl.print_conversation(' (', 'status', simple=True) - ctrl.print_conversation('%s' % (status), 'status', simple=True) + ctrl.print_conversation('%s' % (status), 'status', + simple=True) ctrl.print_conversation(')', 'status', simple=True) ctrl.parent_win.redraw_tab(ctrl) ctrl.update_ui() @@ -884,7 +956,7 @@ class Interface: def handle_event_gc_msg(self, account, array): # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, - # [status_codes])) + # [status_codes], displaymarking, captcha)) jids = array[0].split('/', 1) room_jid = jids[0] @@ -908,7 +980,8 @@ class Interface: # message from someone nick = jids[1] - gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) + gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5], + displaymarking=array[6], captcha=array[7]) if self.remote_ctrl: highlight = gc_control.needs_visual_notification(msg) @@ -988,7 +1061,8 @@ class Interface: def handle_event_gc_config_change(self, account, array): #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes... + # -init jid = array[0] statusCode = array[1] @@ -1009,8 +1083,8 @@ class Interface: if '103' in statusCode: changes.append(_('room now does not show unavailable members')) if '104' in statusCode: - changes.append( - _('A non-privacy-related room configuration change has occurred')) + changes.append(_('A non-privacy-related room configuration change ' + 'has occurred')) if '170' in statusCode: # Can be a presence (see chg_contact_status in groupchat_control.py) changes.append(_('Room logging is now enabled')) @@ -1055,8 +1129,9 @@ class Interface: win.remove_tab(ctrl, 3) dlg = dialogs.InputDialog(_('Password Required'), - _('A Password is required to join the room %s. Please type it.') % \ - room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) + _('A Password is required to join the room %s. Please type it.') % \ + room_jid, is_modal=False, ok_handler=on_ok, + cancel_handler=on_cancel) dlg.input_entry.set_visibility(False) def handle_event_gc_invitation(self, account, array): @@ -1077,6 +1152,20 @@ class Interface: notify.popup(event_type, jid, account, 'gc-invitation', path, event_type, room_jid) + def handle_event_gc_error(self, account, data): + #('ERROR', account, (gc_control, title_text, section_text)) + gc_control, pritext, sectext = data + if gc_control: + if gc_control.error_dialog: + gc_control.error_dialog.destroy() + def on_close(dummy): + gc_control.error_dialog.destroy() + gc_control.error_dialog = None + gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext, + on_response_ok=on_close, on_response_cancel=on_close) + else: + dialogs.ErrorDialog(pritext, sectext) + def forget_gpg_passphrase(self, keyid): if keyid in self.gpg_passphrase: del self.gpg_passphrase[keyid] @@ -1087,9 +1176,10 @@ class Interface: use_gpg_agent = gajim.config.get('use_gpg_agent') sectext = '' if use_gpg_agent: - sectext = _('You configured Gajim to use GPG agent, but there is no ' - 'GPG agent running or it returned a wrong passphrase.\n') - sectext += _('You are currently connected without your OpenPGP key.') + sectext = _('You configured Gajim to use GPG agent, but there is no' + ' GPG agent running or it returned a wrong passphrase.\n') + sectext += _('You are currently connected without your OpenPGP ' + 'key.') dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) else: path = gtkgui_helpers.get_icon_path('gajim-warning', 48) @@ -1121,9 +1211,9 @@ class Interface: callback(False) dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' - 'encrypt this chat is not trusted. Do you really want to encrypt this ' - 'message?'), checktext=_('Do _not ask me again'), - on_response_yes=on_yes, on_response_no=on_no) + 'encrypt this chat is not trusted. Do you really want to encrypt ' + 'this message?'), checktext=_('_Do not ask me again'), + on_response_yes=on_yes, on_response_no=on_no) def handle_event_password_required(self, account, array): #('PASSWORD_REQUIRED', account, None) @@ -1133,8 +1223,8 @@ class Interface: if passwords.USER_HAS_GNOMEKEYRING and \ not passwords.USER_USES_GNOMEKEYRING: text += '\n' + _('Gnome Keyring is installed but not \ - correctly started (environment variable probably not \ - correctly set)') + correctly started (environment variable probably not \ + correctly set)') def on_ok(passphrase, save): if save: @@ -1149,8 +1239,8 @@ class Interface: del self.pass_dialog[account] self.pass_dialog[account] = dialogs.PassphraseDialog( - _('Password Required'), text, _('Save password'), ok_handler=on_ok, - cancel_handler=on_cancel) + _('Password Required'), text, _('Save password'), ok_handler=on_ok, + cancel_handler=on_cancel) def handle_event_roster_info(self, account, array): #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) @@ -1175,30 +1265,26 @@ class Interface: gajim.contacts.add_contact(account, contact) self.roster.add_contact(jid, account) else: - # it is an existing contact that might has changed - re_place = False # If contact has changed (sub, ask or group) update roster # Mind about observer status changes: - # According to xep 0162, a contact is not an observer anymore when - # we asked for auth, so also remove him if ask changed + # According to xep 0162, a contact is not an observer anymore when + # we asked for auth, so also remove him if ask changed old_groups = contacts[0].groups if contacts[0].sub != sub or contacts[0].ask != ask\ or old_groups != groups: - re_place = True - # c.get_shown_groups() has changed. Reflect that in roster_winodow + # c.get_shown_groups() has changed. Reflect that in + # roster_winodow self.roster.remove_contact(jid, account, force=True) for contact in contacts: contact.name = name or '' contact.sub = sub contact.ask = ask contact.groups = groups or [] - if re_place: - self.roster.add_contact(jid, account) - # Refilter and update old groups - for group in old_groups: - self.roster.draw_group(group, account) - else: - self.roster.draw_contact(jid, account) + self.roster.add_contact(jid, account) + # Refilter and update old groups + for group in old_groups: + self.roster.draw_group(group, account) + self.roster.draw_contact(jid, account) if self.remote_ctrl: self.remote_ctrl.raise_signal('RosterInfo', (account, array)) @@ -1232,7 +1318,7 @@ class Interface: path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, jid, account, 'file-send-error', path, - event_type, file_props['name']) + event_type, file_props['name']) def handle_event_gmail_notify(self, account, array): jid = array[0] @@ -1249,22 +1335,23 @@ class Interface: if gajim.config.get('notify_on_new_gmail_email_extra'): cnt = 0 for gmessage in gmail_messages_list: - #FIXME: emulate Gtalk client popups. find out what they parse and - # how they decide what to show each message has a 'From', - # 'Subject' and 'Snippet' field - if cnt >=5: + # FIXME: emulate Gtalk client popups. find out what they + # parse and how they decide what to show each message has a + # 'From', 'Subject' and 'Snippet' field + if cnt >= 5: break senders = ',\n '.join(reversed(gmessage['From'])) - text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ - {'from_address': senders, 'subject': gmessage['Subject'], - 'snippet': gmessage['Snippet']} + text += _('\n\nFrom: %(from_address)s\nSubject: ' + '%(subject)s\n%(snippet)s') % \ + {'from_address': senders, + 'subject': gmessage['Subject'], + 'snippet': gmessage['Snippet']} cnt += 1 if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): helpers.play_sound('gmail_received') notify.popup(_('New E-mail'), jid, account, 'gmail', - path_to_image=path, title=title, - text=text) + path_to_image=path, title=title, text=text) if self.remote_ctrl: self.remote_ctrl.raise_signal('NewGmail', (account, array)) @@ -1362,7 +1449,8 @@ class Interface: ft.show_stopped(jid, file_props, error_msg=_('Remote contact stopped transfer')) elif file_props['error'] == -6: - ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) + ft.show_stopped(jid, file_props, + error_msg=_('Error opening file')) return msg_type = '' @@ -1377,9 +1465,10 @@ class Interface: if event_type == '': # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) - # this should never happen but it does. see process_result() in socks5.py - # who calls this func (sth is really wrong unless this func is also registered - # as progress_cb + # this should never happen but it does. see process_result() in + # socks5.py + # who calls this func (sth is really wrong unless this func is also + # registered as progress_cb return if msg_type: @@ -1393,12 +1482,12 @@ class Interface: sender).get_shown_name() filename = os.path.basename(file_props['file-name']) if event_type == _('File Transfer Completed'): - txt = _('You successfully received %(filename)s from %(name)s.')\ - % {'filename': filename, 'name': name} + txt = _('You successfully received %(filename)s from ' + '%(name)s.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' else: # ft stopped - txt = _('File transfer of %(filename)s from %(name)s stopped.')\ - % {'filename': filename, 'name': name} + txt = _('File transfer of %(filename)s from %(name)s ' + 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' else: receiver = file_props['receiver'] @@ -1414,8 +1503,8 @@ class Interface: % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' else: # ft stopped - txt = _('File transfer of %(filename)s to %(name)s stopped.')\ - % {'filename': filename, 'name': name} + txt = _('File transfer of %(filename)s to %(name)s ' + 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' path = gtkgui_helpers.get_icon_path(img_name, 48) else: @@ -1434,25 +1523,27 @@ class Interface: if account not in self.instances: return if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') + self.instances[account]['xml_console'].print_stanza(stanza, + 'incoming') def handle_event_stanza_sent(self, account, stanza): if account not in self.instances: return if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') + self.instances[account]['xml_console'].print_stanza(stanza, + 'outgoing') def handle_event_vcard_published(self, account, array): if 'profile' in self.instances[account]: win = self.instances[account]['profile'] win.vcard_published() - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ - self.minimized_controls[account].values(): + for gc_control in self.msg_win_mgr.get_controls( + message_control.TYPE_GC) + self.minimized_controls[account].values(): if gc_control.account == account: show = gajim.SHOW_LIST[gajim.connections[account].connected] status = gajim.connections[account].status gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, show, status) + gc_control.room_jid, show, status) def handle_event_vcard_not_published(self, account, array): if 'profile' in self.instances[account]: @@ -1462,7 +1553,7 @@ class Interface: def ask_offline_status(self, account): for contact in gajim.contacts.iter_contacts(account): gajim.connections[account].request_last_status_time(contact.jid, - contact.resource) + contact.resource) def handle_event_signed_in(self, account, empty): """ @@ -1484,7 +1575,8 @@ class Interface: gajim.sleeper_state[account] = 'online' elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ (state == common.sleepy.STATE_XA and connected == 5)): - # If we are autoaway/xa and come back after a disconnection, do nothing + # If we are autoaway/xa and come back after a disconnection, do + # nothing # Else disable autoaway gajim.sleeper_state[account] = 'off' invisible_show = gajim.SHOW_LIST.index('invisible') @@ -1492,13 +1584,13 @@ class Interface: if gajim.connections[account].connected == invisible_show: return # join already open groupchats - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ - + self.minimized_controls[account].values(): + for gc_control in self.msg_win_mgr.get_controls( + message_control.TYPE_GC) + self.minimized_controls[account].values(): if account != gc_control.account: continue room_jid = gc_control.room_jid if room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: + gajim.gc_connected[account][room_jid]: continue nick = gc_control.nick password = gajim.gc_passwords.get(room_jid, '') @@ -1546,7 +1638,8 @@ class Interface: if account not in self.instances: return if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_lists_received(data) + self.instances[account]['privacy_lists'].privacy_lists_received( + data) def handle_event_privacy_list_received(self, account, data): # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) @@ -1566,19 +1659,15 @@ class Interface: if not 'type' in rule: gajim.connections[account].blocked_all = True elif rule['type'] == 'jid' and rule['action'] == 'deny': - gajim.connections[account].blocked_contacts.append(rule['value']) + gajim.connections[account].blocked_contacts.append( + rule['value']) elif rule['type'] == 'group' and rule['action'] == 'deny': - gajim.connections[account].blocked_groups.append(rule['value']) + gajim.connections[account].blocked_groups.append( + rule['value']) gajim.connections[account].blocked_list.append(rule) - #elif rule['type'] == "group" and action == "deny": - # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] - # self.store.append([text_item]) - # self.global_rules.append(rule) - #else: - # self.global_rules_to_append.append(rule) if 'blocked_contacts' in self.instances[account]: self.instances[account]['blocked_contacts'].\ - privacy_list_received(rules) + privacy_list_received(rules) def handle_event_privacy_lists_active_default(self, account, data): if not data: @@ -1598,15 +1687,17 @@ class Interface: def handle_event_zc_name_conflict(self, account, data): def on_ok(new_name): gajim.config.set_per('accounts', account, 'name', new_name) + show = gajim.connections[account].old_show status = gajim.connections[account].status gajim.connections[account].username = new_name - gajim.connections[account].change_status(status, '') + gajim.connections[account].change_status(show, status) def on_cancel(): gajim.connections[account].change_status('offline', '') dlg = dialogs.InputDialog(_('Username Conflict'), - _('Please type a new username for your local account'), input_str=data, - is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) + _('Please type a new username for your local account'), + input_str=data, is_modal=True, ok_handler=on_ok, + cancel_handler=on_cancel) def handle_event_ping_sent(self, account, contact): if contact.jid == contact.get_full_jid(): @@ -1664,13 +1755,15 @@ class Interface: gajim.connections[account].status) def on_ok(new_resource): gajim.config.set_per('accounts', account, 'resource', new_resource) - self.roster.send_status(account, gajim.connections[account].old_show, - gajim.connections[account].status) + self.roster.send_status(account, + gajim.connections[account].old_show, + gajim.connections[account].status) proposed_resource = gajim.connections[account].server_resource proposed_resource += gajim.config.get('gc_proposed_nick_char') dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), - _('You are already connected to this account with the same resource. ' - 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok) + _('You are already connected to this account with the same ' + 'resource. Please type a new one'), resource=proposed_resource, + ok_handler=on_ok) def handle_event_jingle_incoming(self, account, data): # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, @@ -1693,9 +1786,8 @@ class Interface: jid = gajim.get_jid_without_resource(peerjid) resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) + ctrl = (self.msg_win_mgr.get_control(peerjid, account) + or self.msg_win_mgr.get_control(jid, account)) if ctrl: if 'audio' in content_types: ctrl.set_audio_state('connection_received', sid) @@ -1716,8 +1808,8 @@ class Interface: if helpers.allow_showing_notification(account): # TODO: we should use another pixmap ;-) - txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid( - account, peerjid) + txt = _('%s wants to start a voice chat.') % \ + gajim.get_name_from_jid(account, peerjid) path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48) event_type = _('Voice Chat Request') notify.popup(event_type, peerjid, account, 'jingle-incoming', @@ -1729,9 +1821,8 @@ class Interface: if media in ('audio', 'video'): jid = gajim.get_jid_without_resource(peerjid) resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) + ctrl = (self.msg_win_mgr.get_control(peerjid, account) + or self.msg_win_mgr.get_control(jid, account)) if ctrl: if media == 'audio': ctrl.set_audio_state('connected', sid) @@ -1743,26 +1834,29 @@ class Interface: peerjid, sid, media, reason = data jid = gajim.get_jid_without_resource(peerjid) resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) + ctrl = (self.msg_win_mgr.get_control(peerjid, account) + or self.msg_win_mgr.get_control(jid, account)) if ctrl: - if media in ('audio', None): + if media is None: + ctrl.stop_jingle(sid=sid, reason=reason) + elif media == 'audio': ctrl.set_audio_state('stop', sid=sid, reason=reason) - if media in ('video', None): + elif media == 'video': ctrl.set_video_state('stop', sid=sid, reason=reason) dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) if dialog: - dialog.dialog.destroy() + if media is None: + dialog.dialog.destroy() + else: + dialog.remove_contents((media, )) def handle_event_jingle_error(self, account, data): # ('JINGLE_ERROR', account, (peerjid, sid, reason)) peerjid, sid, reason = data jid = gajim.get_jid_without_resource(peerjid) resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) + ctrl = (self.msg_win_mgr.get_control(peerjid, account) + or self.msg_win_mgr.get_control(jid, account)) if ctrl: ctrl.set_audio_state('error', reason=reason) @@ -1803,14 +1897,15 @@ class Interface: f.close() if data[2] in certs: dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + _('This certificate is already in file %s, so it\'s ' + 'not added again.') % gajim.MY_CACERTS) else: f = open(gajim.MY_CACERTS, 'a') f.write(server + '\n') f.write(data[2] + '\n\n') f.close() - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[3]) + gajim.config.set_per('accounts', account, + 'ssl_fingerprint_sha1', data[3]) if is_checked[1]: ignore_ssl_errors = gajim.config.get_per('accounts', account, 'ignore_ssl_errors').split() @@ -1825,24 +1920,27 @@ class Interface: self.handle_event_status(account, 'offline') pritext = _('Error verifying SSL certificate') - sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]} + sectext = _('There was an error verifying the SSL certificate of your ' + 'jabber server: %(error)s\nDo you still want to connect to this ' + 'server?') % {'error': data[0]} if data[1] in (18, 27): - checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] + checktext1 = _('Add this certificate to the list of trusted ' + 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] else: checktext1 = '' checktext2 = _('Ignore this error for this certificate.') if 'ssl_error' in self.instances[account]['online_dialog']: self.instances[account]['online_dialog']['ssl_error'].destroy() self.instances[account]['online_dialog']['ssl_error'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, - checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) + dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) def handle_event_fingerprint_error(self, account, data): # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) def on_yes(is_checked): del self.instances[account]['online_dialog']['fingerprint_error'] gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[0]) + data[0]) # Reset the ignored ssl errors gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') gajim.connections[account].ssl_certificate_accepted() @@ -1852,20 +1950,20 @@ class Interface: self.handle_event_status(account, 'offline') pritext = _('SSL certificate error') sectext = _('It seems the SSL certificate of account %(account)s has ' - 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' - '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' - ' the fingerprint of the certificate?') % {'account': account, - 'old': gajim.config.get_per('accounts', account, - 'ssl_fingerprint_sha1'), 'new': data[0]} + 'changed or your connection is being hacked.\nOld fingerprint: ' + '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect ' + 'and update the fingerprint of the certificate?') % \ + {'account': account, 'old': gajim.config.get_per('accounts', + account, 'ssl_fingerprint_sha1'), 'new': data[0]} if 'fingerprint_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['fingerprint_error'].destroy() + self.instances[account]['online_dialog']['fingerprint_error'].\ + destroy() self.instances[account]['online_dialog']['fingerprint_error'] = \ - dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, - on_response_no=on_no) + dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, + on_response_no=on_no) def handle_event_plain_connection(self, account, data): # ('PLAIN_CONNECTION', account, (connection)) - server = gajim.config.get_per('accounts', account, 'hostname') def on_ok(is_checked): if not is_checked[0]: on_cancel() @@ -1882,20 +1980,21 @@ class Interface: gajim.connections[account].disconnect(on_purpose=True) self.handle_event_status(account, 'offline') pritext = _('Insecure connection') - sectext = _('You are about to send your password on an unencrypted ' - 'connection. Are you sure you want to do that?') + sectext = _('You are about to connect to the server with an insecure ' + 'connection. This means all your conversations will be ' + 'exchanged unencrypted. Are you sure you want to do that?') checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') + checktext2 = _('_Do not ask me again') if 'plain_connection' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['plain_connection'].destroy() + self.instances[account]['online_dialog']['plain_connection'].\ + destroy() self.instances[account]['online_dialog']['plain_connection'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) + dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, + is_modal=False) def handle_event_insecure_ssl_connection(self, account, data): # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) - server = gajim.config.get_per('accounts', account, 'hostname') def on_ok(is_checked): if not is_checked[0]: on_cancel() @@ -1903,9 +2002,10 @@ class Interface: del self.instances[account]['online_dialog']['insecure_ssl'] if is_checked[1]: gajim.config.set_per('accounts', account, - 'warn_when_insecure_ssl_connection', False) + 'warn_when_insecure_ssl_connection', False) if gajim.connections[account].connected == 0: - # We have been disconnecting (too long time since window is opened) + # We have been disconnecting (too long time since window is + # opened) # re-connect with auto-accept gajim.connections[account].connection_auto_accepted = True show, msg = gajim.connections[account].continue_connect_info[:2] @@ -1918,28 +2018,52 @@ class Interface: self.handle_event_status(account, 'offline') pritext = _('Insecure connection') sectext = _('You are about to send your password on an insecure ' - 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') + 'connection. You should install PyOpenSSL to prevent that. Are you ' + 'sure you want to do that?') checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') + checktext2 = _('_Do not ask me again') if 'insecure_ssl' in self.instances[account]['online_dialog']: self.instances[account]['online_dialog']['insecure_ssl'].destroy() self.instances[account]['online_dialog']['insecure_ssl'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) + dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, + is_modal=False) - def handle_event_pubsub_node_removed(self, account, data): - # ('PUBSUB_NODE_REMOVED', account, (jid, node)) - if 'pep_services' in self.instances[account]: - if data[0] == gajim.get_jid_from_account(account): - self.instances[account]['pep_services'].node_removed(data[1]) - - def handle_event_pubsub_node_not_removed(self, account, data): - # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) - if data[0] == gajim.get_jid_from_account(account): - dialogs.WarningDialog(_('PEP node was not removed'), - _('PEP node %(node)s was not removed: %(message)s') % { - 'node': data[1], 'message': data[2]}) + def handle_event_insecure_password(self, account, data): + # ('INSECURE_PASSWORD', account, ()) + def on_ok(is_checked): + if not is_checked[0]: + on_cancel() + return + del self.instances[account]['online_dialog']['insecure_password'] + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_insecure_password', False) + if gajim.connections[account].connected == 0: + # We have been disconnecting (too long time since window is + # opened) + # re-connect with auto-accept + gajim.connections[account].connection_auto_accepted = True + show, msg = gajim.connections[account].continue_connect_info[:2] + self.roster.send_status(account, show, msg) + return + gajim.connections[account].accept_insecure_password() + def on_cancel(): + del self.instances[account]['online_dialog']['insecure_password'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password unencrypted on an ' + 'insecure connection. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('_Do not ask me again') + if 'insecure_password' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['insecure_password'].\ + destroy() + self.instances[account]['online_dialog']['insecure_password'] = \ + dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, + is_modal=False) def handle_event_pep_received(self, account, data): # ('PEP_RECEIVED', account, (jid, pep_type)) @@ -1971,89 +2095,93 @@ class Interface: def create_core_handlers_list(self): self.handlers = { - 'ROSTER': [self.handle_event_roster], - 'WARNING': [self.handle_event_warning], - 'ERROR': [self.handle_event_error], - 'INFORMATION': [self.handle_event_information], - 'ERROR_ANSWER': [self.handle_event_error_answer], - 'STATUS': [self.handle_event_status], - 'NEW_JID': [self.handle_event_new_jid], - 'NOTIFY': [self.handle_event_notify], - 'MSGERROR': [self.handle_event_msgerror], - 'MSGSENT': [self.handle_event_msgsent], - 'MSGNOTSENT': [self.handle_event_msgnotsent], - 'SUBSCRIBED': [self.handle_event_subscribed], - 'UNSUBSCRIBED': [self.handle_event_unsubscribed], - 'SUBSCRIBE': [self.handle_event_subscribe], - 'AGENT_REMOVED': [self.handle_event_agent_removed], - 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], - 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], - 'QUIT': [self.handle_event_quit], - 'ACC_OK': [self.handle_event_acc_ok], - 'MYVCARD': [self.handle_event_myvcard], - 'VCARD': [self.handle_event_vcard], - 'LAST_STATUS_TIME': [self.handle_event_last_status_time], - 'OS_INFO': [self.handle_event_os_info], - 'ENTITY_TIME': [self.handle_event_entity_time], - 'GC_NOTIFY': [self.handle_event_gc_notify], - 'GC_MSG': [self.handle_event_gc_msg], - 'GC_SUBJECT': [self.handle_event_gc_subject], - 'GC_CONFIG': [self.handle_event_gc_config], - 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], - 'GC_INVITATION': [self.handle_event_gc_invitation], - 'GC_AFFILIATION': [self.handle_event_gc_affiliation], - 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], - 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], - 'ROSTER_INFO': [self.handle_event_roster_info], - 'BOOKMARKS': [self.handle_event_bookmarks], - 'CON_TYPE': [self.handle_event_con_type], - 'CONNECTION_LOST': [self.handle_event_connection_lost], - 'FILE_REQUEST': [self.handle_event_file_request], - 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], - 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], - 'FILE_SEND_ERROR': [self.handle_event_file_send_error], - 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], - 'STANZA_SENT': [self.handle_event_stanza_sent], - 'HTTP_AUTH': [self.handle_event_http_auth], - 'VCARD_PUBLISHED': [self.handle_event_vcard_published], - 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], - 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], - 'SIGNED_IN': [self.handle_event_signed_in], - 'METACONTACTS': [self.handle_event_metacontacts], - 'ATOM_ENTRY': [self.handle_atom_entry], - 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], - 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received], - 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], - 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ - [self.handle_event_privacy_lists_active_default], - 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], - 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], - 'PING_SENT': [self.handle_event_ping_sent], - 'PING_REPLY': [self.handle_event_ping_reply], - 'PING_ERROR': [self.handle_event_ping_error], - 'SEARCH_FORM': [self.handle_event_search_form], - 'SEARCH_RESULT': [self.handle_event_search_result], - 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], - 'ROSTERX': [self.handle_event_roster_item_exchange], - 'PEP_CONFIG': [self.handle_event_pep_config], - 'UNIQUE_ROOM_ID_UNSUPPORTED': \ - [self.handle_event_unique_room_id_unsupported], - 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported], - 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], - 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], - 'PASSWORD_REQUIRED': [self.handle_event_password_required], - 'SSL_ERROR': [self.handle_event_ssl_error], - 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], - 'PLAIN_CONNECTION': [self.handle_event_plain_connection], - 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection], - 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed], - 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed], - 'JINGLE_INCOMING': [self.handle_event_jingle_incoming], - 'JINGLE_CONNECTED': [self.handle_event_jingle_connected], - 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], - 'JINGLE_ERROR': [self.handle_event_jingle_error], - 'PEP_RECEIVED': [self.handle_event_pep_received], - 'CAPS_RECEIVED': [self.handle_event_caps_received] + 'ROSTER': [self.handle_event_roster], + 'WARNING': [self.handle_event_warning], + 'ERROR': [self.handle_event_error], + 'DB_ERROR': [self.handle_event_db_error], + 'INFORMATION': [self.handle_event_information], + 'ERROR_ANSWER': [self.handle_event_error_answer], + 'STATUS': [self.handle_event_status], + 'NEW_JID': [self.handle_event_new_jid], + 'NOTIFY': [self.handle_event_notify], + 'MSGERROR': [self.handle_event_msgerror], + 'MSGSENT': [self.handle_event_msgsent], + 'MSGNOTSENT': [self.handle_event_msgnotsent], + 'SUBSCRIBED': [self.handle_event_subscribed], + 'UNSUBSCRIBED': [self.handle_event_unsubscribed], + 'SUBSCRIBE': [self.handle_event_subscribe], + 'AGENT_REMOVED': [self.handle_event_agent_removed], + 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], + 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], + 'QUIT': [self.handle_event_quit], + 'ACC_OK': [self.handle_event_acc_ok], + 'MYVCARD': [self.handle_event_myvcard], + 'VCARD': [self.handle_event_vcard], + 'LAST_STATUS_TIME': [self.handle_event_last_status_time], + 'OS_INFO': [self.handle_event_os_info], + 'ENTITY_TIME': [self.handle_event_entity_time], + 'GC_NOTIFY': [self.handle_event_gc_notify], + 'GC_MSG': [self.handle_event_gc_msg], + 'GC_SUBJECT': [self.handle_event_gc_subject], + 'GC_CONFIG': [self.handle_event_gc_config], + 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], + 'GC_INVITATION': [self.handle_event_gc_invitation], + 'GC_AFFILIATION': [self.handle_event_gc_affiliation], + 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], + 'GC_ERROR': [self.handle_event_gc_error], + 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], + 'ROSTER_INFO': [self.handle_event_roster_info], + 'BOOKMARKS': [self.handle_event_bookmarks], + 'CON_TYPE': [self.handle_event_con_type], + 'CONNECTION_LOST': [self.handle_event_connection_lost], + 'FILE_REQUEST': [self.handle_event_file_request], + 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], + 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], + 'FILE_SEND_ERROR': [self.handle_event_file_send_error], + 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], + 'STANZA_SENT': [self.handle_event_stanza_sent], + 'HTTP_AUTH': [self.handle_event_http_auth], + 'VCARD_PUBLISHED': [self.handle_event_vcard_published], + 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], + 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], + 'SIGNED_IN': [self.handle_event_signed_in], + 'METACONTACTS': [self.handle_event_metacontacts], + 'ATOM_ENTRY': [self.handle_atom_entry], + 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], + 'PRIVACY_LISTS_RECEIVED': \ + [self.handle_event_privacy_lists_received], + 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], + 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ + [self.handle_event_privacy_lists_active_default], + 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], + 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], + 'PING_SENT': [self.handle_event_ping_sent], + 'PING_REPLY': [self.handle_event_ping_reply], + 'PING_ERROR': [self.handle_event_ping_error], + 'SEARCH_FORM': [self.handle_event_search_form], + 'SEARCH_RESULT': [self.handle_event_search_result], + 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], + 'ROSTERX': [self.handle_event_roster_item_exchange], + 'PEP_CONFIG': [self.handle_event_pep_config], + 'UNIQUE_ROOM_ID_UNSUPPORTED': \ + [self.handle_event_unique_room_id_unsupported], + 'UNIQUE_ROOM_ID_SUPPORTED': \ + [self.handle_event_unique_room_id_supported], + 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], + 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], + 'PASSWORD_REQUIRED': [self.handle_event_password_required], + 'SSL_ERROR': [self.handle_event_ssl_error], + 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], + 'PLAIN_CONNECTION': [self.handle_event_plain_connection], + 'INSECURE_SSL_CONNECTION': \ + [self.handle_event_insecure_ssl_connection], + 'INSECURE_PASSWORD': [self.handle_event_insecure_password], + 'JINGLE_INCOMING': [self.handle_event_jingle_incoming], + 'JINGLE_CONNECTED': [self.handle_event_jingle_connected], + 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], + 'JINGLE_ERROR': [self.handle_event_jingle_error], + 'PEP_RECEIVED': [self.handle_event_pep_received], + 'CAPS_RECEIVED': [self.handle_event_caps_received] } def register_core_handlers(self): @@ -2065,7 +2193,7 @@ class Interface: for event_name, event_handlers in self.handlers.iteritems(): for event_handler in event_handlers: gajim.ged.register_event_handler(event_name, ged.CORE, - event_handler) + event_handler) ################################################################################ ### Methods dealing with gajim.events @@ -2079,22 +2207,24 @@ class Interface: # Do we have a queue? jid = gajim.get_jid_without_resource(jid) no_queue = len(gajim.events.get_events(account, jid)) == 0 - # type_ can be gc-invitation file-send-error file-error file-request-error - # file-request file-completed file-stopped jingle-incoming + # type_ can be gc-invitation file-send-error file-error + # file-request-error file-request file-completed file-stopped + # jingle-incoming # event_type can be in advancedNotificationWindow.events_list event_types = {'file-request': 'ft_request', - 'file-completed': 'ft_finished'} + 'file-completed': 'ft_finished'} event_type = event_types.get(type_) show_in_roster = notify.get_show_in_roster(event_type, account, jid) show_in_systray = notify.get_show_in_systray(event_type, account, jid) event = gajim.events.create_event(type_, event_args, - show_in_roster=show_in_roster, - show_in_systray=show_in_systray) + show_in_roster=show_in_roster, + show_in_systray=show_in_systray) gajim.events.add_event(account, jid, event) self.roster.show_title() if no_queue: # We didn't have a queue: we change icons - if not gajim.contacts.get_contact_with_highest_priority(account, jid): + if not gajim.contacts.get_contact_with_highest_priority(account, + jid): if type_ == 'gc-invitation': self.roster.add_groupchat(jid, account, status='offline') else: @@ -2108,7 +2238,8 @@ class Interface: family = gajim.contacts.get_metacontacts_family(account, jid) if family: nearby_family, bb_jid, bb_account = \ - gajim.contacts.get_nearby_family_and_big_brother(family, account) + gajim.contacts.get_nearby_family_and_big_brother(family, + account) else: bb_jid, bb_account = jid, account self.roster.select_contact(bb_jid, bb_account) @@ -2148,8 +2279,8 @@ class Interface: ctrl = self.msg_win_mgr.get_control(fjid, account) if not ctrl: - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) + highest_contact = gajim.contacts.\ + get_contact_with_highest_priority(account, jid) # jid can have a window if this resource was lower when he sent # message and is now higher because the other one is offline if resource and highest_contact.resource == resource and \ @@ -2164,14 +2295,15 @@ class Interface: if not contact: contact = highest_contact - ctrl = self.new_chat(contact, account, resource = resource, session = session) + ctrl = self.new_chat(contact, account, resource=resource, + session=session) gajim.last_message_time[account][jid] = 0 # long time ago w = ctrl.parent_win elif type_ in ('printed_pm', 'pm'): - # assume that the most recently updated control we have for this party - # is the one that this event was in + # assume that the most recently updated control we have for this + # party is the one that this event was in event = gajim.events.get_first_event(account, fjid, type_) if not event: event = gajim.events.get_first_event(account, jid, type_) @@ -2187,17 +2319,18 @@ class Interface: room_jid = jid nick = resource gc_contact = gajim.contacts.get_gc_contact(account, room_jid, - nick) + nick) if gc_contact: show = gc_contact.show else: show = 'offline' gc_contact = gajim.contacts.create_gc_contact( - room_jid=room_jid, account=account, name=nick, show=show) + room_jid=room_jid, account=account, name=nick, + show=show) if not session: session = gajim.connections[account].make_new_session( - fjid, None, type_='pm') + fjid, None, type_='pm') self.new_private_chat(gc_contact, account, session=session) ctrl = session.control @@ -2219,7 +2352,7 @@ class Interface: # Open the window self.roster.open_event(account, fjid, event) elif type_ == 'gmail': - url=gajim.connections[account].gmail_url + url = gajim.connections[account].gmail_url if url: helpers.launch_browser_mailer('url', url) elif type_ == 'gc-invitation': @@ -2268,36 +2401,29 @@ class Interface: @property def basic_pattern_re(self): - try: - return self._basic_pattern_re - except AttributeError: - self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) - return self._basic_pattern_re + if not self._basic_pattern_re: + self._basic_pattern_re = re.compile(self.basic_pattern, + re.IGNORECASE) + return self._basic_pattern_re @property def emot_and_basic_re(self): - try: - return self._emot_and_basic_re - except AttributeError: + if not self._emot_and_basic_re: self._emot_and_basic_re = re.compile(self.emot_and_basic, re.IGNORECASE + re.UNICODE) - return self._emot_and_basic_re + return self._emot_and_basic_re @property def sth_at_sth_dot_sth_re(self): - try: - return self._sth_at_sth_dot_sth_re - except AttributeError: + if not self._sth_at_sth_dot_sth_re: self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) - return self._sth_at_sth_dot_sth_re + return self._sth_at_sth_dot_sth_re @property def invalid_XML_chars_re(self): - try: - return self._invalid_XML_chars_re - except AttributeError: + if not self._invalid_XML_chars_re: self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) - return self._invalid_XML_chars_re + return self._invalid_XML_chars_re def make_regexps(self): # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) @@ -2307,7 +2433,7 @@ class Interface: # \w any alphanumeric character # \W any non-alphanumeric character # \b means word boundary. This is a zero-width assertion that - # matches only at the beginning or end of a word. + # matches only at the beginning or end of a word. # ^ matches at the beginning of lines # # * means 0 or more times @@ -2316,38 +2442,44 @@ class Interface: # | means or # [^*] anything but '*' (inside [] you don't have to escape metachars) # [^\s*] anything but whitespaces and '*' - # (?<!\S) is a one char lookbehind assertion and asks for any leading whitespace + # (?<!\S) is a one char lookbehind assertion and asks for any leading + # whitespace # and mathces beginning of lines so we have correct formatting detection # even if the the text is just '*foo*' # (?!\S) is the same thing but it's a lookahead assertion - # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at the end - # so http://be) will match http://be and http://be)be) will match http://be)be - - legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ - r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at + # the end + # so http://be) will match http://be and http://be)be) will match + # http://be)be + + legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\ + r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ + r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\ + r"|%[A-Fa-f0-9]{2})+"\ + r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" # NOTE: it's ok to catch www.gr such stuff exist! - #FIXME: recognize xmpp: and treat it specially + # FIXME: recognize xmpp: and treat it specially links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ - r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"(?=\)))|(\w[\w\+\.\-]*:(\S|%[A-Fa-f0-9]{2})+)" - #2nd one: at_least_one_char@at_least_one_char.at_least_one_char + # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' - #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) - #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* + # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) + # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\ - r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\ - r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)' + r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\ + r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)' - latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$' + latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\ + r'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$' basic_pattern = links + '|' + mail + '|' + legacy_prefixes link_pattern = basic_pattern - self.link_pattern_re = re.compile(link_pattern, re.IGNORECASE) + self.link_pattern_re = re.compile(link_pattern, re.I | re.U) if gajim.config.get('use_latex'): basic_pattern += latex @@ -2358,8 +2490,8 @@ class Interface: emoticons_pattern = '' if gajim.config.get('emoticons_theme'): - # When an emoticon is bordered by an alpha-numeric character it is NOT - # expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc. + # When an emoticon is bordered by an alpha-numeric character it is + # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc # We still allow multiple emoticons side-by-side like :P:P:P # sort keys by length so :qwe emot is checked before :q keys = sorted(self.emoticons, key=len, reverse=True) @@ -2369,21 +2501,24 @@ class Interface: for emoticon in keys: # travel thru emoticons list emoticon = emoticon.decode('utf-8') emoticon_escaped = re.escape(emoticon) # espace regexp metachars - emoticons_pattern += emoticon_escaped + '|'# | means or in regexp + # | means or in regexp + emoticons_pattern += emoticon_escaped + '|' if (emoticon_length != len(emoticon)): - # Build up expressions to match emoticons next to other emoticons - emoticons_pattern_prematch = emoticons_pattern_prematch[:-1] + ')|(?<=' - emoticons_pattern_postmatch = emoticons_pattern_postmatch[:-1] + ')|(?=' + # Build up expressions to match emoticons next to others + emoticons_pattern_prematch = \ + emoticons_pattern_prematch[:-1] + ')|(?<=' + emoticons_pattern_postmatch = \ + emoticons_pattern_postmatch[:-1] + ')|(?=' emoticon_length = len(emoticon) emoticons_pattern_prematch += emoticon_escaped + '|' emoticons_pattern_postmatch += emoticon_escaped + '|' # We match from our list of emoticons, but they must either have # whitespace, or another emoticon next to it to match successfully # [\w.] alphanumeric and dot (for not matching 8) in (2.8)) - emoticons_pattern = '|' + \ - '(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \ - '(?:' + emoticons_pattern[:-1] + ')' + \ - '(?:(?![\w]' + emoticons_pattern_postmatch[:-1] + '))' + emoticons_pattern = '|' + '(?:(?<![\w.]' + \ + emoticons_pattern_prematch[:-1] + '))' + '(?:' + \ + emoticons_pattern[:-1] + ')' + '(?:(?![\w]' + \ + emoticons_pattern_postmatch[:-1] + '))' # because emoticons match later (in the string) they need to be after # basic matches that may occur earlier @@ -2396,14 +2531,15 @@ class Interface: self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]' # Invalid XML chars - self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]' + self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|'\ + u'[\ud800-\udfff]|[\ufffe-\uffff]' def popup_emoticons_under_button(self, button, parent_win): """ Popup the emoticons menu under button, located in parent_win """ gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu, - button, parent_win) + button, parent_win) def prepare_emoticons_menu(self): menu = gtk.Menu() @@ -2431,7 +2567,7 @@ class Interface: item.connect('activate', emoticon_clicked, image[0]) #FIXME: add tooltip with ascii menu.attach(item, counter % size, counter % size + 1, - counter / size, counter / size + 1) + counter / size, counter / size + 1) counter += 1 menu.connect('selection-done', selection_done) menu.show_all() @@ -2446,7 +2582,8 @@ class Interface: sys.path.append(path) import emoticons if need_reload: - # we need to reload else that doesn't work when changing emoticon set + # we need to reload else that doesn't work when changing emoticon + # set reload(emoticons) emots = emoticons.emoticons for emot_filename in emots: @@ -2455,12 +2592,14 @@ class Interface: continue for emot in emots[emot_filename]: emot = emot.decode('utf-8') - # This avoids duplicated emoticons with the same image eg. :) and :-) + # This avoids duplicated emoticons with the same image eg. :) + # and :-) if not emot_file in self.emoticons.values(): if emot_file.endswith('.gif'): pix = gtk.gdk.PixbufAnimation(emot_file) else: - pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file, 16, 16) + pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file, + 16, 16) self.emoticons_images.append((emot, pix)) self.emoticons[emot.upper()] = emot_file del emoticons @@ -2475,9 +2614,11 @@ class Interface: if not os.path.exists(path): # It's maybe a user theme path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme) - if not os.path.exists(path): # theme doesn't exist, disable emoticons + if not os.path.exists(path): + # theme doesn't exist, disable emoticons dialogs.WarningDialog(_('Emoticons disabled'), - _('Your configured emoticons theme has not been found, so emoticons have been disabled.')) + _('Your configured emoticons theme has not been found, so ' + 'emoticons have been disabled.')) gajim.config.set('emoticons_theme', '') return self._init_emoticons(path, need_reload) @@ -2490,8 +2631,8 @@ class Interface: fd = open(os.path.join(path, 'emoticons.py'), 'w') fd.write('emoticons = ') pprint.pprint( dict([ - (file_, [i for i in emots.keys() if emots[i] == file_]) - for file_ in set(emots.values())]), fd) + (file_, [i for i in emots.keys() if emots[i] == file_]) + for file_ in set(emots.values())]), fd) fd.close() del emoticons self._init_emoticons(path, need_reload=True) @@ -2499,7 +2640,9 @@ class Interface: pass if len(self.emoticons) == 0: dialogs.WarningDialog(_('Emoticons disabled'), - _('Your configured emoticons theme cannot been loaded. You maybe need to update the format of emoticons.py file. See http://trac.gajim.org/wiki/Emoticons for more details.')) + _('Your configured emoticons theme cannot been loaded. You ' + 'maybe need to update the format of emoticons.py file. See ' + 'http://trac.gajim.org/wiki/Emoticons for more details.')) if self.emoticons_menu: self.emoticons_menu.destroy() self.emoticons_menu = self.prepare_emoticons_menu() @@ -2521,29 +2664,33 @@ class Interface: gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) win = gc_ctrl.parent_win win.set_active_tab(gc_ctrl) - dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid) + dialogs.ErrorDialog(_('You are already in group chat %s') % \ + room_jid) return invisible_show = gajim.SHOW_LIST.index('invisible') if gajim.connections[account].connected == invisible_show: dialogs.ErrorDialog( - _('You cannot join a group chat while you are invisible')) + _('You cannot join a group chat while you are invisible')) return minimized_control = gajim.interface.minimized_controls[account].get( - room_jid, None) + room_jid, None) - if minimized_control is None and not self.msg_win_mgr.has_window(room_jid, - account): + if minimized_control is None and not self.msg_win_mgr.has_window( + room_jid, account): # Join new groupchat if minimize: - #GCMIN - contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) + # GCMIN + contact = gajim.contacts.create_contact(jid=room_jid, + account=account, name=nick) gc_control = GroupchatControl(None, contact, account) - gajim.interface.minimized_controls[account][room_jid] = gc_control + gajim.interface.minimized_controls[account][room_jid] = \ + gc_control self.roster.add_groupchat(room_jid, account) else: - self.new_room(room_jid, nick, account, is_continued=is_continued) + self.new_room(room_jid, nick, account, + is_continued=is_continued) elif minimized_control is None: # We are already in that groupchat gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) @@ -2562,20 +2709,21 @@ class Interface: def new_room(self, room_jid, nick, account, is_continued=False): # Get target window, create a control, and associate it with the window # GCMIN - contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) + contact = gajim.contacts.create_contact(jid=room_jid, account=account, + name=nick) mw = self.msg_win_mgr.get_window(contact.jid, account) if not mw: mw = self.msg_win_mgr.create_window(contact, account, - GroupchatControl.TYPE_ID) + GroupchatControl.TYPE_ID) gc_control = GroupchatControl(mw, contact, account, - is_continued=is_continued) + is_continued=is_continued) mw.new_tab(gc_control) def new_private_chat(self, gc_contact, account, session=None): conn = gajim.connections[account] if not session and gc_contact.get_full_jid() in conn.sessions: - sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].values() - if isinstance(s, ChatControlSession)] + sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\ + values() if isinstance(s, ChatControlSession)] # look for an existing session with a chat control for s in sessions: @@ -2583,22 +2731,24 @@ class Interface: session = s break if not session and not len(sessions) == 0: - # there are no sessions with chat controls, just take the first one + # there are no sessions with chat controls, just take the first + # one session = sessions[0] if not session: # couldn't find an existing ChatControlSession, just make a new one - session = conn.make_new_session(gc_contact.get_full_jid(), None, 'pm') + session = conn.make_new_session(gc_contact.get_full_jid(), None, + 'pm') contact = gc_contact.as_contact() if not session.control: - message_window = self.msg_win_mgr.get_window(gc_contact.get_full_jid(), - account) + message_window = self.msg_win_mgr.get_window( + gc_contact.get_full_jid(), account) if not message_window: - message_window = self.msg_win_mgr.create_window(contact, account, - message_control.TYPE_PM) + message_window = self.msg_win_mgr.create_window(contact, + account, message_control.TYPE_PM) session.control = PrivateChatControl(message_window, gc_contact, - contact, account, session) + contact, account, session) message_window.new_tab(session.control) if gajim.events.get_events(account, gc_contact.get_full_jid()): @@ -2617,7 +2767,8 @@ class Interface: mw = self.msg_win_mgr.get_window(fjid, account) if not mw: - mw = self.msg_win_mgr.create_window(contact, account, type_, resource) + mw = self.msg_win_mgr.create_window(contact, account, type_, + resource) chat_control = ChatControl(mw, contact, account, session, resource) @@ -2636,25 +2787,27 @@ class Interface: if not contact: added_to_roster = True contact = self.roster.add_to_not_in_the_roster(account, jid, - resource=resource) + resource=resource) ctrl = self.msg_win_mgr.get_control(fjid, account) if not ctrl: ctrl = self.new_chat(contact, account, - resource=resource) + resource=resource) if len(gajim.events.get_events(account, fjid)): ctrl.read_queue() if message: - buffer = ctrl.msg_textview.get_buffer() - buffer.set_text(message) + buffer_ = ctrl.msg_textview.get_buffer() + buffer_.set_text(message) mw = ctrl.parent_win mw.set_active_tab(ctrl) # For JEP-0172 if added_to_roster: ctrl.user_nick = gajim.nicks[account] - gobject.idle_add(lambda: mw.window.grab_focus()) + gobject.idle_add(mw.window.grab_focus) + + return ctrl def on_open_chat_window(self, widget, contact, account, resource=None, session=None): @@ -2676,7 +2829,7 @@ class Interface: if not ctrl: ctrl = self.new_chat(contact, account, resource=resource, - session=session) + session=session) # last message is long time ago gajim.last_message_time[account][ctrl.get_full_jid()] = 0 @@ -2694,7 +2847,7 @@ class Interface: ### Other Methods ################################################################################ - def _change_awn_icon_status(self, status): + def change_awn_icon_status(self, status): if not dbus_support.supported: # do nothing if user doesn't have D-Bus bindings return @@ -2724,7 +2877,7 @@ class Interface: listener = MusicTrackListener.get() if not self.music_track_changed_signal: self.music_track_changed_signal = listener.connect( - 'music-track-changed', self.music_track_changed) + 'music-track-changed', self.music_track_changed) track = listener.get_playing_track() self.music_track_changed(listener, track) @@ -2741,7 +2894,7 @@ class Interface: accounts = [account] is_paused = hasattr(music_track_info, 'paused') and \ - music_track_info.paused == 0 + music_track_info.paused == 0 if not music_track_info or is_paused: artist = title = source = '' else: @@ -2761,7 +2914,7 @@ class Interface: def get_bg_fg_colors(self): def gdkcolor_to_rgb (gdkcolor): return [c / 65535. for c in (gdkcolor.red, gdkcolor.green, - gdkcolor.blue)] + gdkcolor.blue)] def format_rgb (r, g, b): return ' '.join([str(c) for c in ('rgb', r, g, b)]) @@ -2813,7 +2966,8 @@ class Interface: 'status': gajim.status_before_autoaway[account], 'time': gajim.config.get('autoawaytime') } - self.roster.send_status(account, 'away', auto_message, auto=True) + self.roster.send_status(account, 'away', auto_message, + auto=True) gajim.sleeper_state[account] = 'autoaway' elif state == common.sleepy.STATE_XA and \ gajim.sleeper_state[account] in ('online', 'autoaway', @@ -2842,9 +2996,9 @@ class Interface: for a in gajim.connections: if gajim.config.get_per('accounts', a, 'autoconnect'): if gajim.config.get_per('accounts', a, 'restore_last_status'): - self.roster.send_status(a, gajim.config.get_per('accounts', a, - 'last_status'), helpers.from_one_line(gajim.config.get_per( - 'accounts', a, 'last_status_msg'))) + self.roster.send_status(a, gajim.config.get_per('accounts', + a, 'last_status'), helpers.from_one_line( + gajim.config.get_per('accounts', a, 'last_status_msg'))) continue show = gajim.config.get_per('accounts', a, 'autoconnect_as') if not show in gajim.SHOW_LIST: @@ -2896,8 +3050,8 @@ class Interface: print >> sys.stderr, err_str # it is good to notify the user # in case he or she cannot see the output of the console - dialogs.ErrorDialog(_('Could not save your settings and preferences'), - err_str) + dialogs.ErrorDialog(_('Could not save your settings and ' + 'preferences'), err_str) sys.exit() def save_avatar_files(self, jid, photo, puny_nick = None, local = False): @@ -2923,39 +3077,43 @@ class Interface: typ = 'png' extension = '_local.png' # save local avatars as png file else: - pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) + pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, + want_type=True) if pixbuf is None: return extension = '.' + typ if typ not in ('jpeg', 'png'): - gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) + gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\ + 'png formats. saving %s\'avatar as png file (originaly %s)'\ + % (jid, typ)) typ = 'png' extension = '.png' path_to_original_file = path_to_file + extension try: pixbuf.save(path_to_original_file, typ) except Exception, e: - log.error('Error writing avatar file %s: %s' % (path_to_original_file, - str(e))) + log.error('Error writing avatar file %s: %s' % ( + path_to_original_file, str(e))) # Generate and save the resized, color avatar pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') if pixbuf: - path_to_normal_file = path_to_file + '_notif_size_colored' + extension + path_to_normal_file = path_to_file + '_notif_size_colored' + \ + extension try: pixbuf.save(path_to_normal_file, 'png') except Exception, e: log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) + (path_to_original_file, str(e))) # Generate and save the resized, black and white avatar bwbuf = gtkgui_helpers.get_scaled_pixbuf( - gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') + gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') if bwbuf: path_to_bw_file = path_to_file + '_notif_size_bw' + extension try: bwbuf.save(path_to_bw_file, 'png') except Exception, e: log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) + (path_to_original_file, str(e))) def remove_avatar_files(self, jid, puny_nick = None, local = False): """ @@ -2993,7 +3151,8 @@ class Interface: elif jid in self.minimized_controls[account]: # more or less a hack: # On disconnect the minimized gc contact instances - # were set to offline. Reconnect them to show up in the roster. + # were set to offline. Reconnect them to show up in the + # roster. self.roster.add_groupchat(jid, account) def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, @@ -3015,8 +3174,9 @@ class Interface: for bookmark in gajim.connections[account].bookmarks: if bookmark['jid'] == bm['jid']: dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % \ + bm['jid']) return if bookmark['name'] > bm['name']: place_found = True @@ -3029,8 +3189,8 @@ class Interface: gajim.connections[account].store_bookmarks() self.roster.set_actions_menu_needs_rebuild() dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) + _('Bookmark has been added successfully'), + _('You can manage your bookmarks via Actions menu in your roster.')) # does JID exist only within a groupchat? @@ -3100,10 +3260,6 @@ class Interface: if resolver.USE_LIBASYNCNS: gobject.timeout_add(200, gajim.resolver.process) - # setup the indicator - if gajim.HAVE_INDICATOR: - notify.setup_indicator_server() - def remote_init(): if gajim.config.get('remote_control'): try: @@ -3132,6 +3288,7 @@ class Interface: self.status_sent_to_groups = {} self.gpg_passphrase = {} self.pass_dialog = {} + self.db_error_dialog = None self.default_colors = { 'inmsgcolor': gajim.config.get('inmsgcolor'), 'outmsgcolor': gajim.config.get('outmsgcolor'), @@ -3141,6 +3298,22 @@ class Interface: 'urlmsgcolor': gajim.config.get('urlmsgcolor'), } + self.handlers = {} + self.roster = None + self._invalid_XML_chars_re = None + self._basic_pattern_re = None + self._emot_and_basic_re = None + self._sth_at_sth_dot_sth_re = None + self.link_pattern_re = None + self.invalid_XML_chars = None + self.basic_pattern = None + self.emot_and_basic = None + self.sth_at_sth_dot_sth = None + self.emot_only = None + self.emoticons = [] + self.emoticons_animations = {} + self.emoticons_images = {} + cfg_was_read = parser.read() from common import latex @@ -3175,24 +3348,28 @@ class Interface: default = gajim.config.statusmsg_default for msg in default: gajim.config.add_per('statusmsg', msg) - gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) - gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) + gajim.config.set_per('statusmsg', msg, 'message', + default[msg][0]) + gajim.config.set_per('statusmsg', msg, 'activity', + default[msg][1]) gajim.config.set_per('statusmsg', msg, 'subactivity', - default[msg][2]) + default[msg][2]) gajim.config.set_per('statusmsg', msg, 'activity_text', - default[msg][3]) - gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) - gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) + default[msg][3]) + gajim.config.set_per('statusmsg', msg, 'mood', + default[msg][4]) + gajim.config.set_per('statusmsg', msg, 'mood_text', + default[msg][5]) #add default themes if there is not in the config file theme = gajim.config.get('roster_theme') if not theme in gajim.config.get_per('themes'): gajim.config.set('roster_theme', _('default')) if len(gajim.config.get_per('themes')) == 0: d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', - 'contactfont', 'contactfontattrs', 'bannertextcolor', - 'bannerbgcolor'] + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', + 'groupfont', 'groupfontattrs', 'contacttextcolor', + 'contactbgcolor', 'contactfont', 'contactfontattrs', + 'bannertextcolor', 'bannerbgcolor'] default = gajim.config.themes_default for theme_name in default: @@ -3200,7 +3377,7 @@ class Interface: theme = default[theme_name] for o in d: gajim.config.set_per('themes', theme_name, o, - theme[d.index(o)]) + theme[d.index(o)]) if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: gtkgui_helpers.autodetect_browser_mailer() @@ -3209,14 +3386,13 @@ class Interface: # resolve and keep current record of resolved hosts gajim.resolver = resolver.get_resolver(gajim.idlequeue) gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, - self.handle_event_file_rcv_completed, - self.handle_event_file_progress, - self.handle_event_file_error) + self.handle_event_file_rcv_completed, + self.handle_event_file_progress, + self.handle_event_file_error) gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) gajim.default_session_type = ChatControlSession # Creating Global Events Dispatcher - from common import ged gajim.ged = ged.GlobalEventsDispatcher() # Creating Network Events Controller from common import nec @@ -3227,11 +3403,12 @@ class Interface: if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ and gajim.HAVE_ZEROCONF: gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ - connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) + connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) for account in gajim.config.get_per('accounts'): - if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ + if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\ gajim.config.get_per('accounts', account, 'active'): - gajim.connections[account] = common.connection.Connection(account) + gajim.connections[account] = common.connection.Connection( + account) # gtk hooks gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') @@ -3242,9 +3419,9 @@ class Interface: for a in gajim.connections: self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, - 'search': {}, 'online_dialog': {}} - # online_dialog contains all dialogs that have a meaning only when we - # are not disconnected + 'search': {}, 'online_dialog': {}} + # online_dialog contains all dialogs that have a meaning only when + # we are not disconnected self.minimized_controls[a] = {} gajim.contacts.add_account(a) gajim.groups[a] = {} @@ -3265,14 +3442,16 @@ class Interface: helpers.update_optional_features() # prepopulate data which we are sure of; note: we do not log these info for account in gajim.connections: - gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])] + gajimcaps = caps_cache.capscache[('sha-1', + gajim.caps_hash[account])] gajimcaps.identities = [gajim.gajim_identity] gajimcaps.features = gajim.gajim_common_features + \ - gajim.gajim_optional_features[account] + gajim.gajim_optional_features[account] self.remote_ctrl = None - if gajim.config.get('networkmanager_support') and dbus_support.supported: + if gajim.config.get('networkmanager_support') and \ + dbus_support.supported: import network_manager_listener # Handle gnome screensaver @@ -3281,9 +3460,10 @@ class Interface: if not active: for account in gajim.connections: if gajim.sleeper_state[account] == 'autoaway-forced': - # We came back online ofter gnome-screensaver autoaway + # We came back online ofter gnome-screensaver + # autoaway self.roster.send_status(account, 'online', - gajim.status_before_autoaway[account]) + gajim.status_before_autoaway[account]) gajim.status_before_autoaway[account] = '' gajim.sleeper_state[account] = 'online' return @@ -3298,33 +3478,35 @@ class Interface: # we save out online status gajim.status_before_autoaway[account] = \ gajim.connections[account].status - # we go away (no auto status) [we pass True to auto param] + # we go away (no auto status) [we pass True to auto + # param] auto_message = gajim.config.get('autoaway_message') if not auto_message: auto_message = gajim.connections[account].status else: - auto_message = auto_message.replace('$S', '%(status)s') - auto_message = auto_message.replace('$T', '%(time)s') + auto_message = auto_message.replace('$S', + '%(status)s') + auto_message = auto_message.replace('$T', + '%(time)s') auto_message = auto_message % { - 'status': gajim.status_before_autoaway[account], - 'time': gajim.config.get('autoxatime') - } + 'status': gajim.status_before_autoaway[account], + 'time': gajim.config.get('autoxatime')} self.roster.send_status(account, 'away', auto_message, - auto=True) + auto=True) gajim.sleeper_state[account] = 'autoaway-forced' try: bus = dbus.SessionBus() bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, - 'ActiveChanged', 'org.gnome.ScreenSaver') + 'ActiveChanged', 'org.gnome.ScreenSaver') except Exception: pass self.show_vcard_when_connect = [] self.sleeper = common.sleepy.Sleepy( - gajim.config.get('autoawaytime') * 60, # make minutes to seconds - gajim.config.get('autoxatime') * 60) + gajim.config.get('autoawaytime') * 60, # make minutes to seconds + gajim.config.get('autoxatime') * 60) gtkgui_helpers.make_jabber_state_images() @@ -3376,6 +3558,7 @@ class PassphraseRequest: self.callbacks = [] self.dialog_created = False self.dialog = None + self.passphrase = None self.completed = False def interrupt(self): @@ -3398,8 +3581,8 @@ class PassphraseRequest: self.passphrase = passphrase self.completed = True if passphrase is not None: - gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, - self.keyid) + gobject.timeout_add_seconds(30, + gajim.interface.forget_gpg_passphrase, self.keyid) for (account, cb) in self.callbacks: self.run_callback(account, cb) del self.callbacks @@ -3407,7 +3590,7 @@ class PassphraseRequest: def create_dialog(self, account): title = _('Passphrase Required') second = _('Enter GPG key passphrase for key %(keyid)s (account ' - '%(account)s).') % {'keyid': self.keyid, 'account': account} + '%(account)s).') % {'keyid': self.keyid, 'account': account} def _cancel(): # user cancelled, continue without GPG @@ -3421,8 +3604,8 @@ class PassphraseRequest: return elif result == 'expired': dialogs.ErrorDialog(_('GPG key expired'), - _('Your GPG key has expired, you will be connected to %s without' - ' OpenPGP.') % account) + _('Your GPG key has expired, you will be connected to %s ' + 'without OpenPGP.') % account) # Don't try to connect with GPG gajim.connections[account].continue_connect_info[2] = False self.complete(None) @@ -3431,14 +3614,14 @@ class PassphraseRequest: if count < 3: # ask again dialogs.PassphraseDialog(_('Wrong Passphrase'), - _('Please retype your GPG passphrase or press Cancel.'), - ok_handler=(_ok, count + 1), cancel_handler=_cancel) + _('Please retype your GPG passphrase or press Cancel.'), + ok_handler=(_ok, count + 1), cancel_handler=_cancel) else: # user failed 3 times, continue without GPG self.complete(None) - self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), - cancel_handler=_cancel) + self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, + 1), cancel_handler=_cancel) self.dialog_created = True diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index 6652badef..fbc93d1b1 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/gui_menu_builder.py ## -## Copyright (C) 2009 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2009-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## @@ -95,7 +95,7 @@ def build_invite_submenu(invite_menuitem, list_): invite_to_new_room_menuitem.set_image(icon) if len(contact_list) > 1: # several resources invite_to_new_room_menuitem.set_submenu(build_resources_submenu( - contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) + contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) elif len(list_) == 1 and contact.supports(NS_MUC): invite_menuitem.set_sensitive(True) # use resource if it's self contact @@ -104,9 +104,9 @@ def build_invite_submenu(invite_menuitem, list_): else: resource = None invite_to_new_room_menuitem.connect('activate', - roster.on_invite_to_new_room, list_, resource) + roster.on_invite_to_new_room, list_, resource) else: - invite_menuitem.set_sensitive(False) + invite_menuitem.set_sensitive(True) # transform None in 'jabber' c_t = contacts_transport or 'jabber' muc_jid = {} @@ -120,7 +120,8 @@ def build_invite_submenu(invite_menuitem, list_): rooms = [] # a list of (room_jid, account) tuple minimized_controls = [] for account in connected_accounts: - minimized_controls += gajim.interface.minimized_controls[account].values() + minimized_controls += \ + gajim.interface.minimized_controls[account].values() for gc_control in gajim.interface.msg_win_mgr.get_controls( message_control.TYPE_GC) + minimized_controls: acct = gc_control.account @@ -149,8 +150,8 @@ def build_invite_submenu(invite_menuitem, list_): invite_to_submenu.append(menuitem) def get_contact_menu(contact, account, use_multiple_contacts=True, - show_start_chat=True, show_encryption=False, show_buttonbar_items=True, - control=None): +show_start_chat=True, show_encryption=False, show_buttonbar_items=True, +control=None): """ Build contact popup menu for roster and chat window. If control is not set, we hide invite_contacts_menuitem diff --git a/src/history_manager.py b/src/history_manager.py index 5c1456bde..866207050 100644 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -4,7 +4,7 @@ ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## diff --git a/src/history_window.py b/src/history_window.py index 2b8922211..961b45f3b 100644 --- a/src/history_window.py +++ b/src/history_window.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/history_window.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> @@ -337,6 +337,11 @@ class HistoryWindow: if not self.jid: return year, month, day = widget.get_date() # integers + if year < 1900: + widget.select_month(0, 1900) + widget.select_day(1) + return + # in gtk January is 1, in python January is 0, # I want the second # first day of month is 1 not 0 @@ -345,7 +350,7 @@ class HistoryWindow: days_in_this_month = calendar.monthrange(year, month)[1] try: log_days = gajim.logger.get_days_with_logs(self.jid, year, month, - days_in_this_month, self.account) + days_in_this_month, self.account) except exceptions.PysqliteOperationalError, e: dialogs.ErrorDialog(_('Disk Error'), str(e)) return diff --git a/src/htmltextview.py b/src/htmltextview.py index e37556105..488305843 100644 --- a/src/htmltextview.py +++ b/src/htmltextview.py @@ -4,7 +4,7 @@ ## Copyright (C) 2005 Gustavo J. A. M. Carneiro ## Copyright (C) 2006 Santiago Gala ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## Julien Pivotto <roidelapluie AT gmail.com> diff --git a/src/ipython_view.py b/src/ipython_view.py index 49e98fad4..6b128da01 100644 --- a/src/ipython_view.py +++ b/src/ipython_view.py @@ -2,7 +2,7 @@ # -*- coding:utf-8 -*- ## src/ipython_view.py ## -## Copyright (C) 2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2008-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## diff --git a/src/message_control.py b/src/message_control.py index 86205c50f..700ac9485 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -5,7 +5,7 @@ ## Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> ## Travis Shirk <travis AT pobox.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com> ## Stephan Erb <steve-e AT h3c.de> ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> @@ -208,7 +208,7 @@ class MessageControl(object): def send_message(self, message, keyID='', type_='chat', chatstate=None, msg_id=None, composing_xep=None, resource=None, user_nick=None, - xhtml=None, callback=None, callback_args=[]): + xhtml=None, label=None, callback=None, callback_args=[]): # Send the given message to the active tab. # Doesn't return None if error jid = self.contact.jid @@ -241,5 +241,5 @@ class MessageControl(object): conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, resource=self.resource, user_nick=user_nick, session=self.session, - original_message=original_message, xhtml=xhtml, callback=callback, + original_message=original_message, xhtml=xhtml, label=label, callback=callback, callback_args=callback_args) diff --git a/src/message_textview.py b/src/message_textview.py index 1b568af6b..2197f1b03 100644 --- a/src/message_textview.py +++ b/src/message_textview.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/message_textview.py ## -## Copyright (C) 2003-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2008-2009 Julien Pivotto <roidelapluie AT gmail.com> diff --git a/src/message_window.py b/src/message_window.py index 22c14c327..4dff5394c 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/message_window.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2008 Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Geobert Quach <geobert AT gmail.com> @@ -144,6 +144,8 @@ class MessageWindow(object): self.notebook.set_show_border(gajim.config.get('tabs_border')) self.show_icon() + gobject.idle_add(self.notebook.grab_focus) + def change_account_name(self, old_name, new_name): if old_name in self._controls: self._controls[new_name] = self._controls[old_name] @@ -219,7 +221,7 @@ class MessageWindow(object): dialogs.YesNoDialog( _('You are going to close several tabs'), _('Do you really want to close them all?'), - checktext=_('Do _not ask me again'), on_response_yes=on_yes1) + checktext=_('_Do not ask me again'), on_response_yes=on_yes1) return True def on_yes(ctrl): @@ -685,6 +687,10 @@ _('Do you really want to close them all?'), except KeyError: return + if new_jid in self._controls[acct]: + self.remove_tab(self._controls[acct][new_jid], + self.CLOSE_CLOSE_BUTTON, force=True) + self._controls[acct][new_jid] = ctrl del self._controls[acct][old_jid] diff --git a/src/music_track_listener.py b/src/music_track_listener.py index 9de01dd02..4d0df3bf8 100644 --- a/src/music_track_listener.py +++ b/src/music_track_listener.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org> ## Jonathan Schleifer <js-gajim AT webkeks.org> ## Stephan Erb <steve-e AT h3c.de> diff --git a/src/negotiation.py b/src/negotiation.py index d5d974b79..869e2fb8b 100644 --- a/src/negotiation.py +++ b/src/negotiation.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/negotiation.py ## -## Copyright (C) 2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> ## ## This file is part of Gajim. diff --git a/src/network_manager_listener.py b/src/network_manager_listener.py index 5239015fe..303c1488b 100644 --- a/src/network_manager_listener.py +++ b/src/network_manager_listener.py @@ -4,7 +4,7 @@ ## Copyright (C) 2006 Jeffrey C. Ollie <jeff AT ocjtech.us> ## Nikos Kouremenos <kourem AT gmail.com> ## Stefan Bethge <stefan AT lanpartei.de> -## Copyright (C) 2006-2007 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## diff --git a/src/notify.py b/src/notify.py index 92cc63b5f..9c8624b68 100644 --- a/src/notify.py +++ b/src/notify.py @@ -4,7 +4,7 @@ ## Copyright (C) 2005 Sebastian Estienne ## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com> @@ -50,24 +50,6 @@ try: except ImportError: USER_HAS_PYNOTIFY = False -if gajim.HAVE_INDICATOR: - import indicate - -def setup_indicator_server(): - server = indicate.indicate_server_ref_default() - server.set_type('message.im') - server.set_desktop_file('/usr/share/applications/gajim.desktop') - server.connect('server-display', server_display) - server.show() - -def display(indicator, account, jid, msg_type): - gajim.interface.handle_event(account, jid, msg_type) - indicator.hide() - -def server_display(server): - win = gajim.interface.roster.window - win.present() - def get_show_in_roster(event, account, contact, session=None): """ Return True if this event must be shown in roster, else False @@ -115,9 +97,9 @@ def get_advanced_notification(event, account, contact): if gajim.config.get_per('notifications', str(num), 'event') == event: # test recipient recipient_type = gajim.config.get_per('notifications', str(num), - 'recipient_type') + 'recipient_type') recipients = gajim.config.get_per('notifications', str(num), - 'recipients').split() + 'recipients').split() if recipient_type == 'all': recipient_ok = True elif recipient_type == 'contact' and contact.jid in recipients: @@ -136,13 +118,13 @@ def get_advanced_notification(event, account, contact): if status_ok: # test window_opened tab_opened = gajim.config.get_per('notifications', str(num), - 'tab_opened') + 'tab_opened') if tab_opened == 'both': tab_opened_ok = True else: chat_control = helpers.get_chat_control(account, contact) - if (chat_control and tab_opened == 'yes') or (not chat_control and \ - tab_opened == 'no'): + if (chat_control and tab_opened == 'yes') or (not chat_control \ + and tab_opened == 'no'): tab_opened_ok = True if tab_opened_ok: return num @@ -152,9 +134,10 @@ def get_advanced_notification(event, account, contact): def notify(event, jid, account, parameters, advanced_notif_num=None): """ - Check what type of notifications we want, depending on basic and the advanced - configuration of notifications and do these notifications; advanced_notif_num - holds the number of the first (top most) advanced notification + Check what type of notifications we want, depending on basic and the + advanced configuration of notifications and do these notifications; + advanced_notif_num holds the number of the first (top most) advanced + notification """ # First, find what notifications we want do_popup = False @@ -174,7 +157,8 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): gajim.block_signed_in_notifications[account_server]: block_transport = True if helpers.allow_showing_notification(account, 'notify_on_signin') and \ - not gajim.block_signed_in_notifications[account] and not block_transport: + not gajim.block_signed_in_notifications[account] and \ + not block_transport: do_popup = True if gajim.config.get_per('soundevents', 'contact_connected', 'enabled') and not gajim.block_signed_in_notifications[account] and \ @@ -185,7 +169,7 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): if helpers.allow_showing_notification(account, 'notify_on_signout'): do_popup = True if gajim.config.get_per('soundevents', 'contact_disconnected', - 'enabled'): + 'enabled'): do_sound = True elif event == 'new_message': message_type = parameters[0] @@ -206,8 +190,8 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): 'first_message_received', advanced_notif_num): do_sound = True elif not is_first_message and focused and \ - helpers.allow_sound_notification(account, 'next_message_received_focused', - advanced_notif_num): + helpers.allow_sound_notification(account, + 'next_message_received_focused', advanced_notif_num): do_sound = True elif not is_first_message and not focused and \ helpers.allow_sound_notification(account, @@ -227,7 +211,7 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): if event == 'contact_disconnected': show_image = 'offline.png' suffix = '_notif_size_bw' - else: #Status Change or Connected + else: # Status Change or Connected # FIXME: for status change, # we don't always 'online.png', but we # first need 48x48 for all status @@ -236,46 +220,46 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): transport_name = gajim.get_transport_name_from_jid(jid) img_path = None if transport_name: - img_path = os.path.join(helpers.get_transport_path(transport_name), - '48x48', show_image) + img_path = os.path.join(helpers.get_transport_path( + transport_name), '48x48', show_image) if not img_path or not os.path.isfile(img_path): iconset = gajim.config.get('iconset') - img_path = os.path.join(helpers.get_iconset_path(iconset), '48x48', - show_image) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, jid=jid, - suffix=suffix) + img_path = os.path.join(helpers.get_iconset_path(iconset), + '48x48', show_image) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, + jid=jid, suffix=suffix) if event == 'status_change': title = _('%(nick)s Changed Status') % \ - {'nick': gajim.get_name_from_jid(account, jid)} + {'nick': gajim.get_name_from_jid(account, jid)} text = _('%(nick)s is now %(status)s') % \ - {'nick': gajim.get_name_from_jid(account, jid),\ - 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])} + {'nick': gajim.get_name_from_jid(account, jid),\ + 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])} if status_message: text = text + " : " + status_message popup(_('Contact Changed Status'), jid, account, - path_to_image=path, title=title, text=text) + path_to_image=path, title=title, text=text) elif event == 'contact_connected': title = _('%(nickname)s Signed In') % \ - {'nickname': gajim.get_name_from_jid(account, jid)} + {'nickname': gajim.get_name_from_jid(account, jid)} text = '' if status_message: text = status_message popup(_('Contact Signed In'), jid, account, - path_to_image=path, title=title, text=text) + path_to_image=path, title=title, text=text) elif event == 'contact_disconnected': title = _('%(nickname)s Signed Out') % \ - {'nickname': gajim.get_name_from_jid(account, jid)} + {'nickname': gajim.get_name_from_jid(account, jid)} text = '' if status_message: text = status_message popup(_('Contact Signed Out'), jid, account, - path_to_image=path, title=title, text=text) + path_to_image=path, title=title, text=text) elif event == 'new_message': if message_type == 'normal': # single message event_type = _('New Single Message') img_name = 'gajim-single_msg_recv' title = _('New Single Message from %(nickname)s') % \ - {'nickname': nickname} + {'nickname': nickname} text = message elif message_type == 'pm': # private message event_type = _('New Private Message') @@ -283,20 +267,21 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): img_name = 'gajim-priv_msg_recv' title = _('New Private Message from group chat %s') % room_name if message: - text = _('%(nickname)s: %(message)s') % {'nickname': nickname, - 'message': message} + text = _('%(nickname)s: %(message)s') % \ + {'nickname': nickname, 'message': message} else: - text = _('Messaged by %(nickname)s') % {'nickname': nickname} + text = _('Messaged by %(nickname)s') % \ + {'nickname': nickname} else: # chat message event_type = _('New Message') img_name = 'gajim-chat_msg_recv' title = _('New Message from %(nickname)s') % \ - {'nickname': nickname} + {'nickname': nickname} text = message img_path = gtkgui_helpers.get_icon_path(img_name, 48) popup(event_type, jid, account, message_type, - path_to_image=img_path, title=title, text=text) + path_to_image=img_path, title=title, text=text) if do_sound: snd_file = None @@ -305,7 +290,7 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): if advanced_notif_num is not None and gajim.config.get_per( 'notifications', str(advanced_notif_num), 'sound') == 'yes': snd_file = gajim.config.get_per('notifications', - str(advanced_notif_num), 'sound_file') + str(advanced_notif_num), 'sound_file') elif advanced_notif_num is not None and gajim.config.get_per( 'notifications', str(advanced_notif_num), 'sound') == 'no': pass # do not set snd_event @@ -324,14 +309,14 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): if do_cmd: command = gajim.config.get_per('notifications', str(advanced_notif_num), - 'command') + 'command') try: helpers.exec_command(command) except Exception: pass def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None, - text=None): +text=None): """ Notify a user of an event. It first tries to a valid implementation of the Desktop Notification Specification. If that fails, then we fall back to @@ -341,23 +326,11 @@ def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None, if not path_to_image: path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) - if gajim.HAVE_INDICATOR and event_type in (_('New Message'), - _('New Single Message'), _('New Private Message')): - indicator = indicate.Indicator() - indicator.set_property('subtype', 'im') - indicator.set_property('sender', jid) - indicator.set_property('body', text) - indicator.set_property_time('time', time.time()) - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image) - indicator.set_property_icon('icon', pixbuf) - indicator.connect('user-display', display, account, jid, msg_type) - indicator.show() - # Try to show our popup via D-Bus and notification daemon if gajim.config.get('use_notif_daemon') and dbus_support.supported: try: DesktopNotification(event_type, jid, account, msg_type, - path_to_image, title, gobject.markup_escape_text(text)) + path_to_image, title, gobject.markup_escape_text(text)) return # sucessfully did D-Bus Notification procedure! except dbus.DBusException, e: # Connection to D-Bus failed @@ -372,7 +345,7 @@ def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None, # empty text for new_message means do_preview = False # -> default value for text _text = gobject.markup_escape_text( - gajim.get_name_from_jid(account, jid)) + gajim.get_name_from_jid(account, jid)) else: _text = gobject.markup_escape_text(text) @@ -404,7 +377,7 @@ def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None, # Either nothing succeeded or the user wants old-style notifications instance = dialogs.PopupNotificationWindow(event_type, jid, account, - msg_type, path_to_image, title, text) + msg_type, path_to_image, title, text) gajim.interface.roster.popup_notification_windows.append(instance) def on_pynotify_notification_clicked(notification, action): @@ -431,7 +404,8 @@ class NotificationResponseManager: if self.interface is not None: return self.interface = dbus_support.get_notifications_interface() - self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked) + self.interface.connect_to_signal('ActionInvoked', + self.on_action_invoked) self.interface.connect_to_signal('NotificationClosed', self.on_closed) def on_action_invoked(self, id_, reason): @@ -470,12 +444,12 @@ notification_response_manager = NotificationResponseManager() class DesktopNotification: """ - A DesktopNotification that interfaces with D-Bus via the Desktop Notification - specification + A DesktopNotification that interfaces with D-Bus via the Desktop + Notification Specification """ def __init__(self, event_type, jid, account, msg_type='', - path_to_image=None, title=None, text=None): + path_to_image=None, title=None, text=None): self.path_to_image = path_to_image self.event_type = event_type self.title = title @@ -501,7 +475,7 @@ class DesktopNotification: elif event_type == _('Contact Signed Out'): ntype = 'presence.offline' elif event_type in (_('New Message'), _('New Single Message'), - _('New Private Message')): + _('New Private Message')): ntype = 'im.received' elif event_type == _('File Transfer Request'): ntype = 'transfer' @@ -525,7 +499,7 @@ class DesktopNotification: else: # default failsafe values self.path_to_image = gtkgui_helpers.get_icon_path( - 'gajim-chat_msg_recv', 48) + 'gajim-chat_msg_recv', 48) ntype = 'im' # Notification Type self.notif = dbus_support.get_notifications_interface(self) @@ -546,24 +520,27 @@ class DesktopNotification: ntype = self.ntype if self.kde_notifications: notification_text = ('<html><img src="%(image)s" align=left />' \ - '%(title)s<br/>%(text)s</html>') % {'title': self.title, - 'text': self.text, 'image': self.path_to_image} + '%(title)s<br/>%(text)s</html>') % {'title': self.title, + 'text': self.text, 'image': self.path_to_image} gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48) - self.notif.Notify( - dbus.String(_('Gajim')), # app_name (string) - dbus.UInt32(0), # replaces_id (uint) - ntype, # event_id (string) - dbus.String(gajim_icon), # app_icon (string) - dbus.String(''), # summary (string) + try: + self.notif.Notify( + dbus.String(_('Gajim')), # app_name (string) + dbus.UInt32(0), # replaces_id (uint) + ntype, # event_id (string) + dbus.String(gajim_icon), # app_icon (string) + dbus.String(''), # summary (string) dbus.String(notification_text), # body (string) # actions (stringlist) (dbus.String('default'), dbus.String(self.event_type), - dbus.String('ignore'), dbus.String(_('Ignore'))), + dbus.String('ignore'), dbus.String(_('Ignore'))), [], # hints (not used in KDE yet) - dbus.UInt32(timeout*1000), # timeout (int), in ms + dbus.UInt32(timeout*1000), # timeout (int), in ms reply_handler=self.attach_by_id, error_handler=self.notify_another_way) - return + return + except Exception: + pass version = self.version if version[:2] == [0, 2]: actions = {} @@ -571,22 +548,23 @@ class DesktopNotification: actions = {'default': 0} try: self.notif.Notify( - dbus.String(_('Gajim')), - dbus.String(self.path_to_image), - dbus.UInt32(0), - ntype, - dbus.Byte(0), - dbus.String(self.title), - dbus.String(self.text), - [dbus.String(self.path_to_image)], - actions, - [''], - True, - dbus.UInt32(timeout), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) + dbus.String(_('Gajim')), + dbus.String(self.path_to_image), + dbus.UInt32(0), + ntype, + dbus.Byte(0), + dbus.String(self.title), + dbus.String(self.text), + [dbus.String(self.path_to_image)], + actions, + [''], + True, + dbus.UInt32(timeout), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) except AttributeError: - version = [0, 3, 1] # we're actually dealing with the newer version + # we're actually dealing with the newer version + version = [0, 3, 1] if version > [0, 3]: if gajim.interface.systray_enabled and \ gajim.config.get('attach_notifications_to_systray'): @@ -607,10 +585,13 @@ class DesktopNotification: text = ' ' actions = () if 'actions' in self.capabilities: - actions = (dbus.String('default'), dbus.String(self.event_type)) - self.notif.Notify( + actions = (dbus.String('default'), dbus.String( + self.event_type)) + try: + self.notif.Notify( dbus.String(_('Gajim')), - dbus.UInt32(0), # this notification does not replace other + # this notification does not replace other + dbus.UInt32(0), dbus.String(self.path_to_image), dbus.String(self.title), dbus.String(text), @@ -619,8 +600,11 @@ class DesktopNotification: dbus.UInt32(timeout*1000), reply_handler=self.attach_by_id, error_handler=self.notify_another_way) + except Exception, e: + self.notify_another_way(e) else: - self.notif.Notify( + try: + self.notif.Notify( dbus.String(_('Gajim')), dbus.String(self.path_to_image), dbus.UInt32(0), @@ -631,6 +615,8 @@ class DesktopNotification: dbus.UInt32(timeout*1000), reply_handler=self.attach_by_id, error_handler=self.notify_another_way) + except Exception, e: + self.notify_another_way(e) def attach_by_id(self, id_): self.id = id_ @@ -638,8 +624,12 @@ class DesktopNotification: notification_response_manager.add_pending(self.id, self) def notify_another_way(self, e): - gajim.log.debug(str(e)) - gajim.log.debug('Need to implement a new way of falling back') + gajim.log.debug('Error when trying to use notification daemon: %s' % \ + str(e)) + instance = dialogs.PopupNotificationWindow(self.event_type, self.jid, + self.account, self.msg_type, self.path_to_image, self.title, + self.text) + gajim.interface.roster.popup_notification_windows.append(instance) def on_action_invoked(self, id_, reason): if self.notif is None: @@ -668,12 +658,13 @@ class DesktopNotification: def get_version(self): self.notif.GetServerInfo( - reply_handler=self.version_reply_handler, - error_handler=self.version_error_handler_2_x_try) + reply_handler=self.version_reply_handler, + error_handler=self.version_error_handler_2_x_try) def version_error_handler_2_x_try(self, e): - self.notif.GetServerInformation(reply_handler=self.version_reply_handler, - error_handler=self.version_error_handler_3_x_try) + self.notif.GetServerInformation( + reply_handler=self.version_reply_handler, + error_handler=self.version_error_handler_3_x_try) def version_error_handler_3_x_try(self, e): self.version = self.default_version diff --git a/src/profile_window.py b/src/profile_window.py index 577f50fa4..077ba4f24 100644 --- a/src/profile_window.py +++ b/src/profile_window.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/profile_window.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## @@ -189,8 +189,8 @@ class ProfileWindow: nick = gajim.config.get_per('accounts', self.account, 'name') menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.jid, self.account, nick) + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.jid, nick) menu.append(menuitem) # show clear menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR) @@ -203,6 +203,20 @@ class ProfileWindow: elif event.button == 1: # left click self.on_set_avatar_button_clicked(widget) + def on_BDAY_entry_focus_out_event(self, widget, event): + txt = widget.get_text() + if not txt: + return + try: + time.strptime(txt, '%Y-%m-%d') + except ValueError: + if not widget.is_focus(): + pritext = _('Wrong date format') + dialogs.ErrorDialog(pritext, _('Format of the date must be ' + 'YYYY-MM-DD')) + gobject.idle_add(lambda: widget.grab_focus()) + return True + def set_value(self, entry_name, value): try: self.xml.get_object(entry_name).set_text(value) diff --git a/src/remote_control.py b/src/remote_control.py index 71cab3aa8..95411a653 100644 --- a/src/remote_control.py +++ b/src/remote_control.py @@ -4,7 +4,7 @@ ## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com> ## Dimitur Kirov <dkirov AT gmail.com> ## Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net> diff --git a/src/roster_window.py b/src/roster_window.py index d6716fb61..468bb25d0 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ## src/roster_window.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net> ## Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> @@ -74,17 +74,17 @@ from common.pep import MOODS, ACTIVITIES #(icon, name, type, jid, account, editable, second pixbuf) ( - C_IMG, # image to show state (online, new message etc) - C_NAME, # cellrenderer text that holds contact nickame - C_TYPE, # account, group or contact? - C_JID, # the jid of the row - C_ACCOUNT, # cellrenderer text that holds account name - C_MOOD_PIXBUF, - C_ACTIVITY_PIXBUF, - C_TUNE_PIXBUF, - C_LOCATION_PIXBUF, - C_AVATAR_PIXBUF, # avatar_pixbuf - C_PADLOCK_PIXBUF, # use for account row only + C_IMG, # image to show state (online, new message etc) + C_NAME, # cellrenderer text that holds contact nickame + C_TYPE, # account, group or contact? + C_JID, # the jid of the row + C_ACCOUNT, # cellrenderer text that holds account name + C_MOOD_PIXBUF, + C_ACTIVITY_PIXBUF, + C_TUNE_PIXBUF, + C_LOCATION_PIXBUF, + C_AVATAR_PIXBUF, # avatar_pixbuf + C_PADLOCK_PIXBUF, # use for account row only ) = range(11) class RosterWindow: @@ -220,7 +220,8 @@ class RosterWindow: # It's the last one. # Go up if we are big brother parent_iter = model.iter_parent(contact_iter) - if parent_iter and model[parent_iter][C_TYPE] == 'contact': + if parent_iter and model[parent_iter][C_TYPE] == \ + 'contact': contact_iter = model.iter_next(parent_iter) else: # we tested all @@ -305,8 +306,8 @@ class RosterWindow: def add_account_contacts(self, account): """ - Add all contacts and groups of the given account to roster, draw them and - account + Add all contacts and groups of the given account to roster, draw them + and account """ self.starting = True jids = gajim.contacts.get_jid_list(account) @@ -359,9 +360,9 @@ class RosterWindow: contact.groups = big_brother_contact.get_shown_groups()[:] for child_iter in parent_iters: - it = self.model.append(child_iter, (None, contact.get_shown_name(), - 'contact', contact.jid, account, None, None, None, None, None, - None)) + it = self.model.append(child_iter, (None, + contact.get_shown_name(), 'contact', contact.jid, account, + None, None, None, None, None, None)) added_iters.append(it) else: # We are a normal contact. Add us to our groups. @@ -374,9 +375,10 @@ class RosterWindow: # Group is not yet in roster, add it! child_iterA = self._get_account_iter(account, self.model) child_iterG = self.model.append(child_iterA, - [gajim.interface.jabber_state_images['16']['closed'], - gobject.markup_escape_text(group), - 'group', group, account, None, None, None, None, None, None]) + [gajim.interface.jabber_state_images['16']['closed'], + gobject.markup_escape_text(group), + 'group', group, account, None, None, None, None, None, + None]) self.draw_group(group, account) if contact.is_transport(): @@ -402,7 +404,8 @@ class RosterWindow: if group not in gajim.groups[account]: gajim.groups[account][group] = {'expand': is_expanded} - assert len(added_iters), '%s has not been added to roster!' % contact.jid + assert len(added_iters), '%s has not been added to roster!' % \ + contact.jid return added_iters def _remove_entity(self, contact, account, groups=None): @@ -419,7 +422,8 @@ class RosterWindow: account -- the contacts account groups -- list of groups to remove the contact from. """ - iters = self._get_contact_iter(contact.jid, account, contact, self.model) + iters = self._get_contact_iter(contact.jid, account, contact, + self.model) assert iters, '%s shall be removed but is not in roster' % contact.jid parent_iter = self.model.iter_parent(iters[0]) @@ -545,29 +549,28 @@ class RosterWindow: ok = self._remove_entity(_contact, _account) assert ok, '%s was not removed' % _jid assert len(self._get_contact_iter(_jid, _account, _contact, - self.model)) == 0, '%s is removed but still in roster' % _jid + self.model)) == 0, '%s is removed but still in roster' % _jid if not family_in_roster: return False assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \ - (nearby_family, family) + (nearby_family, family) iters = self._get_contact_iter(old_big_jid, old_big_account, - old_big_contact, self.model) + old_big_contact, self.model) assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \ - old_big_jid - assert not self.model.iter_children(iters[0]),\ - 'Old Big Brother %s still has children' % old_big_jid + old_big_jid + assert not self.model.iter_children(iters[0]), \ + 'Old Big Brother %s still has children' % old_big_jid ok = self._remove_entity(old_big_contact, old_big_account) assert ok, "Old Big Brother %s not removed" % old_big_jid assert len(self._get_contact_iter(old_big_jid, old_big_account, - old_big_contact, self.model)) == 0,\ - 'Old Big Brother %s is removed but still in roster' % old_big_jid + old_big_contact, self.model)) == 0, \ + 'Old Big Brother %s is removed but still in roster' % old_big_jid return True - def _recalibrate_metacontact_family(self, family, account): """ Regroup metacontact family if necessary @@ -575,11 +578,11 @@ class RosterWindow: brothers = [] nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) + self._get_nearby_family_and_big_brother(family, account) big_brother_contact = gajim.contacts.get_contact(big_brother_account, - big_brother_jid) - child_iters = self._get_contact_iter(big_brother_jid, big_brother_account, - model=self.model) + big_brother_jid) + child_iters = self._get_contact_iter(big_brother_jid, + big_brother_account, model=self.model) if child_iters: parent_iter = self.model.iter_parent(child_iters[0]) parent_type = self.model[parent_iter][C_TYPE] @@ -603,7 +606,8 @@ class RosterWindow: _account = child['account'] if _account == big_brother_account and _jid == big_brother_jid: continue - child_iters = self._get_contact_iter(_jid, _account, model=self.model) + child_iters = self._get_contact_iter(_jid, _account, + model=self.model) if not child_iters: continue parent_iter = self.model.iter_parent(child_iters[0]) @@ -627,13 +631,12 @@ class RosterWindow: jid = gajim.get_jid_from_account(account) contact = gajim.contacts.get_first_contact_from_jid(account, jid) - assert len(self._get_contact_iter(jid, account, contact, self.model)) == \ - 0, 'Self contact %s already in roster' % jid + assert len(self._get_contact_iter(jid, account, contact, + self.model)) == 0, 'Self contact %s already in roster' % jid child_iterA = self._get_account_iter(account, self.model) self.model.append(child_iterA, (None, gajim.nicks[account], - 'self_contact', jid, account, None, None, None, None, - None, None)) + 'self_contact', jid, account, None, None, None, None, None, None)) self.draw_completely(jid, account) self.draw_account(account) @@ -668,9 +671,9 @@ class RosterWindow: show_self_contact = gajim.config.get('show_self_contact') if show_self_contact == 'never': return - if (contact.resource != gajim.connections[account].server_resource and\ - show_self_contact == 'when_other_resource') or show_self_contact == \ - 'always': + if (contact.resource != gajim.connections[account].server_resource \ + and show_self_contact == 'when_other_resource') or \ + show_self_contact == 'always': return self._add_self_contact(account) return @@ -691,7 +694,7 @@ class RosterWindow: contacts = self._add_metacontact_family(family, account) else: # We are a normal contact - contacts = [(contact, account),] + contacts = [(contact, account), ] self._add_entity(contact, account) # Draw the contact and its groups contact @@ -748,18 +751,19 @@ class RosterWindow: else: self._remove_entity(contact, account) - if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\ - or force): + if backend and (not gajim.interface.msg_win_mgr.get_control(jid, + account) or force): # If a window is still opened: don't remove contact instance # Remove contact before redrawing, otherwise the old # numbers will still be show gajim.contacts.remove_jid(account, jid, remove_meta=True) if iters: rest_of_family = [data for data in family - if account != data['account'] or jid != data['jid']] + if account != data['account'] or jid != data['jid']] if rest_of_family: # reshow the rest of the family - brothers = self._add_metacontact_family(rest_of_family, account) + brothers = self._add_metacontact_family(rest_of_family, + account) for c, acc in brothers: self.draw_completely(c.jid, acc) @@ -800,7 +804,8 @@ class RosterWindow: status = '' if contact is None: - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, + account) if gc_control: # there is a window that we can minimize gajim.interface.minimized_controls[account][jid] = gc_control @@ -810,9 +815,9 @@ class RosterWindow: else: name = jid.split('@')[0] # New groupchat - #GCMIN - contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, - groups=[_('Groupchats')], show=show, status=status, sub='none') + contact = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=[_('Groupchats')], show=show, status=status, + sub='none') gajim.contacts.add_contact(account, contact) self.add_contact(jid, account) else: @@ -849,10 +854,9 @@ class RosterWindow: """ contact = gajim.contacts.get_contact_with_highest_priority(account, jid) if contact is None: - #TRANSP - contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, - groups=[_('Transports')], show='offline', status='offline', - sub='from') + contact = gajim.contacts.create_contact(jid=jid, account=account, + name=jid, groups=[_('Transports')], show='offline', + status='offline', sub='from') gajim.contacts.add_contact(account, contact) self.add_contact(jid, account) return contact @@ -880,7 +884,7 @@ class RosterWindow: if self.regroup: accounts = gajim.connections.keys() else: - accounts = [account,] + accounts = [account, ] for acc in accounts: changed_contacts = [] @@ -895,8 +899,8 @@ class RosterWindow: if new_name not in contact.groups: contact.groups.append(new_name) - changed_contacts.append({'jid':jid, 'name':contact.name, - 'groups':contact.groups}) + changed_contacts.append({'jid': jid, 'name': contact.name, + 'groups':contact.groups}) gajim.connections[acc].update_contacts(changed_contacts) @@ -1013,10 +1017,11 @@ class RosterWindow: num_of_accounts = gajim.get_number_of_connected_accounts() num_of_secured = gajim.get_number_of_securely_connected_accounts() - if gajim.account_is_securely_connected(account) and not self.regroup or \ + if gajim.account_is_securely_connected(account) and not self.regroup or\ self.regroup and num_of_secured and num_of_secured == num_of_accounts: - tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock + # the only way to create a pixbuf from stock + tls_pixbuf = self.window.render_icon( + gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf else: self.model[child_iter][C_PADLOCK_PIXBUF] = None @@ -1041,24 +1046,30 @@ class RosterWindow: self.model[child_iter][C_NAME] = account_name - pep = gajim.connections[account].pep - if gajim.config.get('show_mood_in_roster') and 'mood' in pep: - self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon() + pep_dict = gajim.connections[account].pep + if gajim.config.get('show_mood_in_roster') and 'mood' in pep_dict: + self.model[child_iter][C_MOOD_PIXBUF] = pep_dict['mood'].\ + asPixbufIcon() else: self.model[child_iter][C_MOOD_PIXBUF] = None - if gajim.config.get('show_activity_in_roster') and 'activity' in pep: - self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon() + if gajim.config.get('show_activity_in_roster') and 'activity' in \ + pep_dict: + self.model[child_iter][C_ACTIVITY_PIXBUF] = pep_dict['activity'].\ + asPixbufIcon() else: self.model[child_iter][C_ACTIVITY_PIXBUF] = None - if gajim.config.get('show_tunes_in_roster') and 'tune' in pep: - self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon() + if gajim.config.get('show_tunes_in_roster') and 'tune' in pep_dict: + self.model[child_iter][C_TUNE_PIXBUF] = pep_dict['tune'].\ + asPixbufIcon() else: self.model[child_iter][C_TUNE_PIXBUF] = None - if gajim.config.get('show_location_in_roster') and 'location' in pep: - self.model[child_iter][C_LOCATION_PIXBUF] = pep['location'].asPixbufIcon() + if gajim.config.get('show_location_in_roster') and 'location' in \ + pep_dict: + self.model[child_iter][C_LOCATION_PIXBUF] = pep_dict['location'].\ + asPixbufIcon() else: self.model[child_iter][C_LOCATION_PIXBUF] = None return False @@ -1161,8 +1172,8 @@ class RosterWindow: for jid_ in gajim.contacts.get_jid_list(account_): contact_ = gajim.contacts.get_first_contact_from_jid( account_, jid_) - if contact_.get_shown_name() == contact.get_shown_name() and \ - (jid_, account_) != (jid, account): + if contact_.get_shown_name() == contact.get_shown_name() \ + and (jid_, account_) != (jid, account): add_acct = True break if add_acct: @@ -1182,12 +1193,13 @@ class RosterWindow: # escape markup entities and make them small # italic and fg color color is calcuted to be # always readable - color = gtkgui_helpers._get_fade_color(self.tree, selected, focus) - colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue) + color = gtkgui_helpers.get_fade_color(self.tree, selected, + focus) + colorstring = '#%04x%04x%04x' % (color.red, color.green, + color.blue) name += '\n<span size="small" style="italic" ' \ - 'foreground="%s">%s</span>' % ( - colorstring, - gobject.markup_escape_text(status)) + 'foreground="%s">%s</span>' % (colorstring, + gobject.markup_escape_text(status)) icon_name = helpers.get_icon_name_to_show(contact, account) # look if another resource has awaiting events @@ -1203,11 +1215,11 @@ class RosterWindow: have_visible_children = False if family: bb_jid, bb_account = \ - self._get_nearby_family_and_big_brother(family, account)[1:] + self._get_nearby_family_and_big_brother(family, account)[1:] is_big_brother = (jid, account) == (bb_jid, bb_account) iters = self._get_contact_iter(jid, account) - have_visible_children = iters \ - and self.modelfilter.iter_has_child(iters[0]) + have_visible_children = iters and \ + self.modelfilter.iter_has_child(iters[0]) if have_visible_children: # We are the big brother and have a visible family @@ -1388,7 +1400,7 @@ class RosterWindow: self.on_modelfilter_row_has_child_toggled) self.tree.set_model(self.modelfilter) - for acct in gajim.connections: + for acct in gajim.contacts.get_accounts(): self.add_account(acct) self.add_account_contacts(acct) # Recalculate column width for ellipsizing @@ -1407,7 +1419,8 @@ class RosterWindow: # Not visible in roster return path = self.modelfilter.get_path(iters[0]) - if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): + if self.dragging or not gajim.config.get( + 'scroll_roster_to_last_message'): # do not change selection while DND'ing return # Expand his parent, so this path is visible, don't expand it. @@ -1521,12 +1534,11 @@ class RosterWindow: self.contact_has_pending_roster_events(contact, _acc): return True return gajim.config.get('show_transports_group') and \ - (gajim.account_is_connected(account) or \ - gajim.config.get('showoffline')) + (gajim.account_is_connected(account) or \ + gajim.config.get('showoffline')) if gajim.config.get('showoffline'): return True - if self.regroup: # C_ACCOUNT for groups depends on the order # accounts were connected @@ -1536,8 +1548,7 @@ class RosterWindow: accounts = [account] for _acc in accounts: for contact in gajim.contacts.iter_contacts(_acc): - # Is this contact in this group ? (last part of if check if it's - # self contact) + # Is this contact in this group? if group in contact.get_shown_groups(): if self.contact_is_visible(contact, _acc): return True @@ -1557,21 +1568,21 @@ class RosterWindow: jid = data['jid'] account = data['account'] contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) + account, jid) if contact and self.contact_is_visible(contact, account): return True return False else: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) + contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) return self.contact_is_visible(contact, account) if type_ == 'agent': contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) + jid) return self.contact_has_pending_roster_events(contact, account) or \ - (gajim.config.get('show_transports_group') and \ - (gajim.account_is_connected(account) or \ - gajim.config.get('showoffline'))) + (gajim.config.get('show_transports_group') and \ + (gajim.account_is_connected(account) or \ + gajim.config.get('showoffline'))) return True def _compareIters(self, model, iter1, iter2, data=None): @@ -1627,12 +1638,12 @@ class RosterWindow: if not contact2: return 0 name2 = contact2.get_shown_name() - # We first compare by show if sort_by_show_in_roster is True or if it's a - # child contact + # We first compare by show if sort_by_show_in_roster is True or if it's + # a child contact if type1 == 'contact' and type2 == 'contact' and \ gajim.config.get('sort_by_show_in_roster'): cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} + 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} s = self.get_show(lcontact1) show1 = cshow.get(s, 9) s = self.get_show(lcontact2) @@ -1696,8 +1707,8 @@ class RosterWindow: if gajim.contacts.get_first_contact_from_jid(account, jid) and not \ shown: # We have this jid in our contacts list - # XXX unread messages should probably have their session saved with - # them + # XXX unread messages should probably have their session saved + # with them session = gajim.connections[account].make_new_session(jid) tim = time.localtime(float(result[2])) @@ -1706,10 +1717,10 @@ class RosterWindow: gajim.logger.set_shown_unread_msgs(result[0]) elif (time.time() - result[2]) > 2592000: - # ok, here we see that we have a message in unread messages table - # that is older than a month. It is probably from someone not in our - # roster for accounts we usually launch, so we will delete this id - # from unread message tables. + # ok, here we see that we have a message in unread messages + # table that is older than a month. It is probably from someone + # not in our roster for accounts we usually launch, so we will + # delete this id from unread message tables. gajim.logger.set_read_messages([result[0]]) def fill_contacts_and_groups_dicts(self, array, account): @@ -1727,7 +1738,8 @@ class RosterWindow: if gajim.connections[account].server_resource: self_jid += '/' + gajim.connections[account].server_resource array[self_jid] = {'name': gajim.nicks[account], - 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'} + 'groups': ['self_contact'], 'subscription': 'both', + 'ask': 'none'} # .keys() is needed for jid in array.keys(): # Remove the contact in roster. It might has changed @@ -1748,17 +1760,17 @@ class RosterWindow: keyID = '' attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() + 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] if gajim.jid_is_transport(jid): array[jid]['groups'] = [_('Transports')] #TRANSP - potential - contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, - groups=array[jid]['groups'], show=show, status=status, - sub=array[jid]['subscription'], ask=array[jid]['ask'], - resource=resource, keyID=keyID) + contact1 = gajim.contacts.create_contact(jid=ji, account=account, + name=name, groups=array[jid]['groups'], show=show, + status=status, sub=array[jid]['subscription'], + ask=array[jid]['ask'], resource=resource, keyID=keyID) gajim.contacts.add_contact(account, contact1) if gajim.config.get('ask_avatars_on_startup'): @@ -1769,16 +1781,19 @@ class RosterWindow: jid_with_resource = contact1.jid if contact1.resource: jid_with_resource += '/' + contact1.resource - gajim.connections[account].request_vcard(jid_with_resource) + gajim.connections[account].request_vcard( + jid_with_resource) else: host = gajim.get_server_from_jid(contact1.jid) if host not in gajim.transport_avatar[account]: - gajim.transport_avatar[account][host] = [contact1.jid] + gajim.transport_avatar[account][host] = \ + [contact1.jid] else: - gajim.transport_avatar[account][host].append(contact1.jid) + gajim.transport_avatar[account][host].append( + contact1.jid) - # If we already have chat windows opened, update them with new contact - # instance + # If we already have chat windows opened, update them with new + # contact instance chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) if chat_control: chat_control.contact = contact1 @@ -1805,7 +1820,8 @@ class RosterWindow: if key in self.contacts_to_be_removed.keys(): backend = self.contacts_to_be_removed[key]['backend'] del self.contacts_to_be_removed[key] - # Remove contact will delay removal if there are more events pending + # Remove contact will delay removal if there are more events + # pending self.remove_contact(jid, account, backend=backend) self.show_title() @@ -1818,8 +1834,9 @@ class RosterWindow: event = gajim.events.get_first_event(account, jid, event.type_) if event.type_ == 'normal': dialogs.SingleMessageWindow(account, jid, - action='receive', from_whom=jid, subject=data[1], message=data[0], - resource=data[5], session=data[8], form_node=data[9]) + action='receive', from_whom=jid, subject=data[1], + message=data[0], resource=data[5], session=data[8], + form_node=data[9]) gajim.events.remove_events(account, jid, event) return True elif event.type_ == 'file-request': @@ -1887,7 +1904,8 @@ class RosterWindow: # position of the treeview on the screen position = self.tree.window.get_origin() - self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) + self.tooltip.show_tooltip(contact, rect.height, position[1] + \ + rect.y) else: self.tooltip.hide_tooltip() @@ -1900,32 +1918,33 @@ class RosterWindow: dialogs.InformationDialog(_('Authorization has been sent'), _('Now "%s" will know your status.') %jid) - def req_sub(self, widget, jid, txt, account, groups=[], nickname=None, + def req_sub(self, widget, jid, txt, account, groups=None, nickname=None, auto_auth=False): """ Request subscription to a contact """ + groups_list = groups or [] gajim.connections[account].request_subscription(jid, txt, nickname, - groups, auto_auth, gajim.nicks[account]) + groups_list, auto_auth, gajim.nicks[account]) contact = gajim.contacts.get_contact_with_highest_priority(account, jid) if not contact: keyID = '' attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() + 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname, - groups=groups, show='requested', status='', ask='none', - sub='subscribe', keyID=keyID) + contact = gajim.contacts.create_contact(jid=jid, account=account, + name=nickname, groups=groups_list, show='requested', status='', + ask='none', sub='subscribe', keyID=keyID) gajim.contacts.add_contact(account, contact) else: if not _('Not in Roster') in contact.get_shown_groups(): - dialogs.InformationDialog(_('Subscription request has been sent'), - _('If "%s" accepts this request you will know his or her status.' - ) % jid) + dialogs.InformationDialog(_('Subscription request has been ' + 'sent'), _('If "%s" accepts this request you will know his ' + 'or her status.') % jid) return self.remove_contact(contact.jid, account, force=True) - contact.groups = groups + contact.groups = groups_list if nickname: contact.name = nickname self.add_contact(jid, account) @@ -1950,7 +1969,6 @@ class RosterWindow: self.set_state(account, 'connecting') def send_status(self, account, status, txt, auto=False, to=None): - child_iterA = self._get_account_iter(account, self.model) if status != 'offline': if to is None: if status == gajim.connections[account].get_status() and \ @@ -1965,7 +1983,8 @@ class RosterWindow: keyid = gajim.config.get_per('accounts', account, 'keyid') if keyid and not gajim.connections[account].gpg: dialogs.WarningDialog(_('GPG is not usable'), - _('You will be connected to %s without OpenPGP.') % account) + _('You will be connected to %s without OpenPGP.') % \ + account) self.send_status_continue(account, status, txt, auto, to) @@ -2027,11 +2046,11 @@ class RosterWindow: gajim.interface.minimized_controls[account].values(): if gc_control.account == account: if gajim.gc_connected[account][gc_control.room_jid]: - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, status, txt) + gajim.connections[account].send_gc_status( + gc_control.nick, gc_control.room_jid, status, txt) else: - # for some reason, we are not connected to the room even if - # tab is opened, send initial join_gc() + # for some reason, we are not connected to the room even + # if tab is opened, send initial join_gc() gajim.connections[account].join_gc(gc_control.nick, gc_control.room_jid, None) if was_invisible and status != 'offline': @@ -2129,9 +2148,10 @@ class RosterWindow: gajim.con_types[account] = None for jid in gajim.contacts.get_jid_list(account): lcontact = gajim.contacts.get_contacts(account, jid) - ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - for contact in [c for c in lcontact if ((c.show != 'offline' or \ - c.is_transport()) and not ctrl)]: + ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, + account) + for contact in [c for c in lcontact if ( + (c.show != 'offline' or c.is_transport()) and not ctrl)]: self.chg_contact_status(contact, 'offline', '', account) self.actions_menu_needs_rebuild = True self.update_status_combobox() @@ -2147,7 +2167,7 @@ class RosterWindow: show_pep can be False to hide pep things from status message or True """ empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '', - 'mood': '', 'mood_text': ''} + 'mood': '', 'mood_text': ''} if show in gajim.config.get_per('defaultstatusmsg'): if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): msg = gajim.config.get_per('defaultstatusmsg', show, 'message') @@ -2175,17 +2195,17 @@ class RosterWindow: if status == 'invisible' and self.connected_rooms(account): dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in disconnection ' - 'from those group chats. Are you sure you want to go invisible?'), - on_response_ok = (change, account, status)) + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in ' + 'disconnection from those group chats. Are you sure you want ' + 'to go invisible?'), on_response_ok = (change, account, status)) else: change(account, status) def update_status_combobox(self): # table to change index in connection.connected to index in combobox table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, - 'xa':3, 'dnd':4, 'invisible':5} + 'xa':3, 'dnd':4, 'invisible':5} # we check if there are more options in the combobox that it should # if yes, we remove the first ones @@ -2204,9 +2224,9 @@ class RosterWindow: liststore.prepend(['SEPARATOR', None, '', True]) status_combobox_text = uf_show + ' (' + _("desync'ed") +')' liststore.prepend([status_combobox_text, - gajim.interface.jabber_state_images['16'][show], show, False]) + gajim.interface.jabber_state_images['16'][show], show, False]) self.status_combobox.set_active(0) - gajim.interface._change_awn_icon_status(show) + gajim.interface.change_awn_icon_status(show) self.combobox_callback_active = True if gajim.interface.systray_enabled: gajim.interface.systray.change_status(show) @@ -2253,7 +2273,8 @@ class RosterWindow: Main window X button was clicked """ if gajim.interface.systray_enabled and not gajim.config.get( - 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event': + 'quit_on_roster_x_button') and gajim.config.get('trayicon') != \ + 'on_event': self.tooltip.hide_tooltip() self.window.hide() elif gajim.config.get('quit_on_roster_x_button'): @@ -2324,7 +2345,7 @@ class RosterWindow: self.quit_on_next_offline = 0 accounts_to_disconnect = [] for acct in accounts: - if gajim.connections[acct].connected: + if gajim.connections[acct].connected > 1: self.quit_on_next_offline += 1 accounts_to_disconnect.append(acct) @@ -2338,7 +2359,8 @@ class RosterWindow: def on_continue2(message, pep_dict): # check if there is an active file transfer from common.protocol.bytestream import (is_transfer_active) - files_props = gajim.interface.instances['file_transfers'].files_props + files_props = gajim.interface.instances['file_transfers'].\ + files_props transfer_active = False for x in files_props: for y in files_props[x]: @@ -2348,8 +2370,8 @@ class RosterWindow: if transfer_active: dialogs.ConfirmationDialog(_('You have running file transfers'), - _('If you quit now, the file(s) being transfered will be ' - 'stopped. Do you still want to quit?'), + _('If you quit now, the file(s) being transfered will ' + 'be stopped. Do you still want to quit?'), on_response_ok=(on_continue3, message, pep_dict)) return on_continue3(message, pep_dict) @@ -2371,8 +2393,8 @@ class RosterWindow: for ctrl in win.controls(): fjid = ctrl.get_full_jid() if fjid in gajim.last_message_time[ctrl.account]: - if time.time() - gajim.last_message_time[ctrl.account][fjid] \ - < 2: + if time.time() - gajim.last_message_time[ctrl.account][ + fjid] < 2: recent = True break if recent: @@ -2380,9 +2402,10 @@ class RosterWindow: if unread or recent: dialogs.ConfirmationDialog(_('You have unread messages'), - _('Messages will only be available for reading them later if you' - ' have history enabled and contact is in your roster.'), - on_response_ok=(on_continue2, message, pep_dict)) + _('Messages will only be available for reading them later ' + 'if you have history enabled and contact is in your ' + 'roster.'), on_response_ok=(on_continue2, + message, pep_dict)) return on_continue2(message, pep_dict) @@ -2544,13 +2567,14 @@ class RosterWindow: account_name = account if gajim.account_is_connected(account): account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - contact = gajim.contacts.create_self_contact(jid=jid, account=account, - name=account_name, show=connection.get_status(), - status=connection.status, resource=connection.server_resource, - priority=connection.priority) + contact = gajim.contacts.create_self_contact(jid=jid, + account=account, name=account_name, + show=connection.get_status(), status=connection.status, + resource=connection.server_resource, + priority=connection.priority) if gajim.connections[account].gpg: - contact.keyID = gajim.config.get_per('accounts', connection.name, - 'keyid') + contact.keyID = gajim.config.get_per('accounts', + connection.name, 'keyid') contacts.append(contact) # if we're online ... if connection.connection: @@ -2572,10 +2596,11 @@ class RosterWindow: show = roster.getShow(jid+'/'+resource) if not show: show = 'online' - contact = gajim.contacts.create_self_contact(jid=jid, - account=account, show=show, status=roster.getStatus(jid + '/' + resource), - priority=roster.getPriority(jid + '/' + resource), - resource=resource) + contact = gajim.contacts.create_self_contact( + jid=jid, account=account, show=show, + status=roster.getStatus(jid + '/' + resource), + priority=roster.getPriority( + jid + '/' + resource), resource=resource) contacts.append(contact) if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: self.tooltip.id = row @@ -2629,18 +2654,18 @@ class RosterWindow: return if len(list_) == 1: pritext = _('Transport "%s" will be removed') % list_[0][0].jid - sectext = _('You will no longer be able to send and receive messages ' - 'from contacts using this transport.') + sectext = _('You will no longer be able to send and receive ' + 'messages from contacts using this transport.') else: pritext = _('Transports will be removed') jids = '' for (contact, account) in list_: jids += '\n ' + contact.get_shown_name() + ',' jids = jids[:-1] + '.' - sectext = _('You will no longer be able to send and receive messages ' - 'to contacts from these transports: %s') % jids + sectext = _('You will no longer be able to send and receive ' + 'messages to contacts from these transports: %s') % jids dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (remove, list_)) + on_response_ok = (remove, list_)) def on_block(self, widget, list_, group=None): """ @@ -2655,22 +2680,24 @@ class RosterWindow: if group is None: for (contact, account) in list_: if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: + if not gajim.connections[account].\ + privacy_rules_supported: continue accounts.append(account) self.send_status(account, 'offline', msg, to=contact.jid) - new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : contact.jid, 'child': [u'message', u'iq', - u'presence-out']} + new_rule = {'order': u'1', 'type': u'jid', + 'action': u'deny', 'value' : contact.jid, + 'child': [u'message', u'iq', u'presence-out']} gajim.connections[account].blocked_list.append(new_rule) # needed for draw_contact: gajim.connections[account].blocked_contacts.append( - contact.jid) + contact.jid) self.draw_contact(contact.jid, account) else: for (contact, account) in list_: if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: + if not gajim.connections[account].\ + privacy_rules_supported: continue accounts.append(account) # needed for draw_group: @@ -2679,8 +2706,10 @@ class RosterWindow: self.send_status(account, 'offline', msg, to=contact.jid) self.draw_contact(contact.jid, account) new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', - 'value' : group, 'child': [u'message', u'iq', u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) + 'value' : group, 'child': [u'message', u'iq', + u'presence-out']} + # account is the same for all when we block a group + gajim.connections[list_[0][1]].blocked_list.append(new_rule) for account in accounts: connection = gajim.connections[account] connection.set_privacy_list('block', connection.blocked_list) @@ -2702,11 +2731,11 @@ class RosterWindow: _block_it() return pritext = _('You are about to block a contact. Are you sure you want' - ' to continue?') - sectext = _('This contact will see you offline and you will not receive ' - 'messages he will send you.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=_block_it) + ' to continue?') + sectext = _('This contact will see you offline and you will not ' + 'receive messages he will send you.') + dialogs.ConfirmationDialogCheck(pritext, sectext, + _('_Do not ask me again'), on_response_ok=_block_it) def on_unblock(self, widget, list_, group=None): """ @@ -2720,17 +2749,20 @@ class RosterWindow: accounts.append(account) gajim.connections[account].new_blocked_list = [] gajim.connections[account].to_unblock = [] - gajim.connections[account].to_unblock.append(contact.jid) + gajim.connections[account].to_unblock.append( + contact.jid) else: gajim.connections[account].to_unblock.append(contact.jid) # needed for draw_contact: if contact.jid in gajim.connections[account].blocked_contacts: - gajim.connections[account].blocked_contacts.remove(contact.jid) + gajim.connections[account].blocked_contacts.remove( + contact.jid) self.draw_contact(contact.jid, account) for account in accounts: for rule in gajim.connections[account].blocked_list: if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] not in gajim.connections[account].to_unblock: + or rule['value'] not in \ + gajim.connections[account].to_unblock: gajim.connections[account].new_blocked_list.append(rule) else: for (contact, account) in list_: @@ -2739,17 +2771,19 @@ class RosterWindow: accounts.append(account) # needed for draw_group: if group in gajim.connections[account].blocked_groups: - gajim.connections[account].blocked_groups.remove(group) + gajim.connections[account].blocked_groups.remove( + group) self.draw_group(group, account) gajim.connections[account].new_blocked_list = [] for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'group' \ - or rule['value'] != group: - gajim.connections[account].new_blocked_list.append(rule) + if rule['action'] != 'deny' or \ + rule['type'] != 'group' or rule['value'] != group: + gajim.connections[account].new_blocked_list.\ + append(rule) self.draw_contact(contact.jid, account) for account in accounts: gajim.connections[account].set_privacy_list('block', - gajim.connections[account].new_blocked_list) + gajim.connections[account].new_blocked_list) gajim.connections[account].get_privacy_list('block') if len(gajim.connections[account].new_blocked_list) == 0: gajim.connections[account].blocked_list = [] @@ -2760,7 +2794,7 @@ class RosterWindow: gajim.connections[account].del_privacy_list('block') if 'blocked_contacts' in gajim.interface.instances[account]: gajim.interface.instances[account]['blocked_contacts'].\ - privacy_list_received([]) + privacy_list_received([]) for (contact, account) in list_: if not self.regroup: show = gajim.SHOW_LIST[gajim.connections[account].connected] @@ -2773,10 +2807,10 @@ class RosterWindow: accounts.append(account) if gajim.connections[account].privacy_rules_supported: self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) + gajim.connections[account].status, to=contact.jid) else: self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) + gajim.connections[account].status, to=contact.jid) def on_rename(self, widget, row_type, jid, account): # this function is called either by F2 or by Rename menuitem @@ -2807,13 +2841,15 @@ class RosterWindow: if row_type in ('contact', 'agent'): if old_text == new_text: return - for contact in gajim.contacts.get_contacts(account, jid): + contacts = gajim.contacts.get_contacts(account, jid) + for contact in contacts: contact.name = new_text gajim.connections[account].update_contact(jid, new_text, \ - contact.groups) + contacts[0].groups) self.draw_contact(jid, account) # Update opened chats - for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account): + for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, + account): ctrl.update_ui() win = gajim.interface.msg_win_mgr.get_window(jid, account) win.redraw_tab(ctrl) @@ -2826,23 +2862,25 @@ class RosterWindow: if 'rename' in gajim.interface.instances: del gajim.interface.instances['rename'] - gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, - old_text, False, (on_renamed, account, row_type, jid, old_text), - on_canceled) + gajim.interface.instances['rename'] = dialogs.InputDialog(title, + message, old_text, False, (on_renamed, account, row_type, jid, + old_text), on_canceled) def on_remove_group_item_activated(self, widget, group, account): def on_ok(checked): - for contact in gajim.contacts.get_contacts_from_group(account, group): + for contact in gajim.contacts.get_contacts_from_group(account, + group): if not checked: - self.remove_contact_from_groups(contact.jid, account, [group]) + self.remove_contact_from_groups(contact.jid, account, + [group]) else: gajim.connections[account].unsubscribe(contact.jid) self.remove_contact(contact.jid, account, backend=True) dialogs.ConfirmationDialogCheck(_('Remove Group'), - _('Do you want to remove group %s from the roster?') % group, - _('Also remove all contacts in this group from your roster'), - on_response_ok=on_ok) + _('Do you want to remove group %s from the roster?') % group, + _('Also remove all contacts in this group from your roster'), + on_response_ok=on_ok) def on_assign_pgp_key(self, widget, contact, account): attached_keys = gajim.config.get_per('accounts', account, @@ -2878,11 +2916,11 @@ class RosterWindow: keys_str) for u in gajim.contacts.get_contacts(account, contact.jid): u.keyID = helpers.prepare_and_validate_gpg_keyID(account, - contact.jid, keyID) + contact.jid, keyID) dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), - _('Select a key to apply to the contact'), public_keys, - on_key_selected, selected=keyID) + _('Select a key to apply to the contact'), public_keys, + on_key_selected, selected=keyID) def on_set_custom_avatar_activate(self, widget, contact, account): def on_ok(widget, path_to_file): @@ -2922,7 +2960,7 @@ class RosterWindow: self.update_avatar_in_gui(contact.jid, account) dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok, - on_response_clear=on_clear) + on_response_clear=on_clear) def on_edit_groups(self, widget, list_): dialogs.EditGroupsDialog(list_) @@ -2936,7 +2974,7 @@ class RosterWindow: gajim.interface.instances['logs'].open_history(contact.jid, account) else: gajim.interface.instances['logs'] = history_window.\ - HistoryWindow(contact.jid, account) + HistoryWindow(contact.jid, account) def on_disconnect(self, widget, jid, account): """ @@ -2955,7 +2993,7 @@ class RosterWindow: if jid in gajim.interface.minimized_controls[account]: ctrl = gajim.interface.minimized_controls[account][jid] gajim.interface.join_gc_room(account, jid, ctrl.nick, - gajim.gc_passwords.get(jid, '')) + gajim.gc_passwords.get(jid, '')) def on_send_single_message_menuitem_activate(self, widget, account, contact=None): @@ -2972,7 +3010,7 @@ class RosterWindow: def on_send_file_menuitem_activate(self, widget, contact, account, resource=None): gajim.interface.instances['file_transfers'].show_file_send_request( - account, contact) + account, contact) def on_add_special_notification_menuitem_activate(self, widget, jid): dialogs.AddSpecialNotificationDialog(jid) @@ -2998,12 +3036,13 @@ class RosterWindow: if gajim.connections[account].muc_jid[type_]: # create the room on this muc server if 'join_gc' in gajim.interface.instances[account]: - gajim.interface.instances[account]['join_gc'].window.destroy() + gajim.interface.instances[account]['join_gc'].window.\ + destroy() try: gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account, - gajim.connections[account].muc_jid[type_], - automatic = {'invities': jid_list}) + dialogs.JoinGroupchatWindow(account, + gajim.connections[account].muc_jid[type_], + automatic = {'invities': jid_list}) except GajimGeneralException: continue break @@ -3030,7 +3069,8 @@ class RosterWindow: """ if not jid in gajim.interface.minimized_controls[account]: # Already opened? - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, + account) if gc_control: mw = gajim.interface.msg_win_mgr.get_window(jid, account) mw.set_active_tab(gc_control) @@ -3040,7 +3080,7 @@ class RosterWindow: mw = gajim.interface.msg_win_mgr.get_window(jid, account) if not mw: mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, - ctrl.account, ctrl.type_id) + ctrl.account, ctrl.type_id) ctrl.parent_win = mw mw.new_tab(ctrl) mw.set_active_tab(ctrl) @@ -3117,8 +3157,8 @@ class RosterWindow: return jid = model[path][C_JID].decode('utf-8') account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) + contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) list_.append((contact, account)) if type_ == 'contact': self.on_req_usub(widget, list_) @@ -3151,7 +3191,8 @@ class RosterWindow: if event.button == 3: # Right click try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() + model, list_of_paths = self.tree.get_selection().\ + get_selected_rows() except TypeError: list_of_paths = [] if path not in list_of_paths: @@ -3161,7 +3202,8 @@ class RosterWindow: elif event.button == 2: # Middle click try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() + model, list_of_paths = self.tree.get_selection().\ + get_selected_rows() except TypeError: list_of_paths = [] if list_of_paths != [path]: @@ -3188,7 +3230,7 @@ class RosterWindow: 'sync_with_global_status'): continue current_show = gajim.SHOW_LIST[gajim.connections[acct].\ - connected] + connected] self.send_status(acct, current_show, message) self.send_pep(acct, pep_dict) dialogs.ChangeStatusMessageDialog(on_response, show) @@ -3213,8 +3255,9 @@ class RosterWindow: else: self.tree.expand_row(path, False) return - # We just save on which row we press button, and open chat window on - # button release to be able to do DND without opening chat window + # We just save on which row we press button, and open chat + # window on button release to be able to do DND without opening + # chat window self.clicked_path = path return else: @@ -3243,7 +3286,8 @@ class RosterWindow: remove_auth = False for (contact, account) in list_: if _('Not in Roster') not in contact.get_shown_groups(): - gajim.connections[account].unsubscribe(contact.jid, remove_auth) + gajim.connections[account].unsubscribe(contact.jid, + remove_auth) self.remove_contact(contact.jid, account, backend=True) if not remove_auth and contact.sub == 'both': contact.name = '' @@ -3259,38 +3303,39 @@ class RosterWindow: if len(list_) == 1: contact = list_[0][0] pritext = _('Contact "%s" will be removed from your roster') % \ - contact.get_shown_name() - sectext = _('You are about to remove "%(name)s" (%(jid)s) from your ' - 'roster.\n') % {'name': contact.get_shown_name(), - 'jid': contact.jid} + contact.get_shown_name() + sectext = _('You are about to remove "%(name)s" (%(jid)s) from ' + 'your roster.\n') % {'name': contact.get_shown_name(), + 'jid': contact.jid} if contact.sub == 'to': dialogs.ConfirmationDialog(pritext, sectext + \ - _('By removing this contact you also remove authorization ' - 'resulting in him or her always seeing you as offline.'), - on_response_ok = (on_ok2, list_)) + _('By removing this contact you also remove authorization ' + 'resulting in him or her always seeing you as offline.'), + on_response_ok=(on_ok2, list_)) elif _('Not in Roster') in contact.get_shown_groups(): # Contact is not in roster dialogs.ConfirmationDialog(pritext, sectext + \ - _('Do you want to continue?'), on_response_ok = (on_ok2, list_)) + _('Do you want to continue?'), on_response_ok=(on_ok2, + list_)) else: dialogs.ConfirmationDialogCheck(pritext, sectext + \ - _('By removing this contact you also by default remove ' - 'authorization resulting in him or her always seeing you as ' - 'offline.'), - _('I want this contact to know my status after removal'), - on_response_ok = (on_ok, list_)) + _('By removing this contact you also by default remove ' + 'authorization resulting in him or her always seeing you as' + ' offline.'), + _('I want this contact to know my status after removal'), + on_response_ok=(on_ok, list_)) else: # several contact to remove at the same time pritext = _('Contacts will be removed from your roster') jids = '' for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\ - ',' + jids += '\n ' + contact.get_shown_name() + ' (%s)' % \ + contact.jid + ',' sectext = _('By removing these contacts:%s\nyou also remove ' - 'authorization resulting in them always seeing you as offline.') % \ - jids + 'authorization resulting in them always seeing you as ' + 'offline.') % jids dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (on_ok2, list_)) + on_response_ok=(on_ok2, list_)) def on_send_custom_status(self, widget, contact_list, show, group=None): """ @@ -3314,7 +3359,8 @@ class RosterWindow: for (contact, account) in contact_list: if account not in gajim.interface.status_sent_to_users: gajim.interface.status_sent_to_users[account] = {} - gajim.interface.status_sent_to_users[account][contact.jid] = show + gajim.interface.status_sent_to_users[account][contact.jid] \ + = show # 2. update privacy lists if main status is invisible for account in account_list: @@ -3337,19 +3383,19 @@ class RosterWindow: else: gajim.config.set('confirm_custom_status', 'yes') self.get_status_message(show, on_response, show_pep=False, - always_ask=True) + always_ask=True) confirm_custom_status = gajim.config.get('confirm_custom_status') if confirm_custom_status == 'no': send_it() return - pritext = _('You are about to send a custom status. Are you sure you want' - ' to continue?') + pritext = _('You are about to send a custom status. Are you sure you ' + 'want to continue?') sectext = _('This contact will temporarily see you as %(status)s, ' - 'but only until you change your status. Then he or she will see your ' - 'global status.') % {'status': show} - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=send_it) + 'but only until you change your status. Then he or she will see ' + 'your global status.') % {'status': show} + dialogs.ConfirmationDialogCheck(pritext, sectext, + _('_Do not ask me again'), on_response_ok=send_it) def on_status_combobox_changed(self, widget): """ @@ -3365,29 +3411,32 @@ class RosterWindow: accounts = gajim.connections.keys() if len(accounts) == 0: dialogs.ErrorDialog(_('No account available'), - _('You must create an account before you can chat with other contacts.')) + _('You must create an account before you can chat with other ' + 'contacts.')) self.update_status_combobox() return status = model[active][2].decode('utf-8') - statuses_unified = helpers.statuses_unified() # status "desync'ed" or not + # status "desync'ed" or not + statuses_unified = helpers.statuses_unified() if (active == 7 and statuses_unified) or (active == 9 and \ not statuses_unified): # 'Change status message' selected: # do not change show, just show change status dialog - status = model[self.previous_status_combobox_active][2].decode('utf-8') + status = model[self.previous_status_combobox_active][2].decode( + 'utf-8') def on_response(message, pep_dict): if message is not None: # None if user pressed Cancel for account in accounts: if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): + 'sync_with_global_status'): continue current_show = gajim.SHOW_LIST[ - gajim.connections[account].connected] + gajim.connections[account].connected] self.send_status(account, current_show, message) self.send_pep(account, pep_dict) self.combobox_callback_active = False self.status_combobox.set_active( - self.previous_status_combobox_active) + self.previous_status_combobox_active) self.combobox_callback_active = True dialogs.ChangeStatusMessageDialog(on_response, status) return @@ -3408,14 +3457,14 @@ class RosterWindow: 'sync_with_global_status'): global_sync_accounts.append(acct) global_sync_connected_accounts = \ - gajim.get_number_of_connected_accounts(global_sync_accounts) + gajim.get_number_of_connected_accounts(global_sync_accounts) for account in accounts: if not gajim.config.get_per('accounts', account, 'sync_with_global_status'): continue # we are connected (so we wanna change show and status) - # or no account is connected and we want to connect with new show - # and status + # or no account is connected and we want to connect with new + # show and status if not global_sync_connected_accounts > 0 or \ gajim.connections[account].connected > 0: @@ -3426,9 +3475,10 @@ class RosterWindow: if status == 'invisible': bug_user = False for account in accounts: - if connected_accounts < 1 or gajim.account_is_connected(account): + if connected_accounts < 1 or gajim.account_is_connected( + account): if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): + 'sync_with_global_status'): continue # We're going to change our status to invisible if self.connected_rooms(account): @@ -3442,11 +3492,11 @@ class RosterWindow: self.update_status_combobox() dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in ' - 'disconnection from those group chats. Are you sure you want to ' - 'go invisible?'), on_reponse_ok=on_ok, - on_response_cancel=on_cancel) + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in ' + 'disconnection from those group chats. Are you sure you ' + 'want to go invisible?'), on_reponse_ok=on_ok, + on_response_cancel=on_cancel) return self.get_status_message(status, on_continue) @@ -3455,7 +3505,8 @@ class RosterWindow: if 'preferences' in gajim.interface.instances: gajim.interface.instances['preferences'].window.present() else: - gajim.interface.instances['preferences'] = config.PreferencesWindow() + gajim.interface.instances['preferences'] = config.PreferencesWindow( + ) def on_plugins_menuitem_activate(self, widget): if gajim.interface.instances.has_key('plugins'): @@ -3500,7 +3551,7 @@ class RosterWindow: gajim.interface.instances[account]['pep_services'].window.present() else: gajim.interface.instances[account]['pep_services'] = \ - config.ManagePEPServicesWindow(account) + config.ManagePEPServicesWindow(account) def on_add_new_contact(self, widget, account): dialogs.AddNewContactWindow(account) @@ -3512,14 +3563,14 @@ class RosterWindow: invisible_show = gajim.SHOW_LIST.index('invisible') if gajim.connections[account].connected == invisible_show: dialogs.ErrorDialog(_('You cannot join a group chat while you are ' - 'invisible')) + 'invisible')) return if 'join_gc' in gajim.interface.instances[account]: gajim.interface.instances[account]['join_gc'].window.present() else: try: gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account) + dialogs.JoinGroupchatWindow(account) except GajimGeneralException: pass @@ -3531,7 +3582,7 @@ class RosterWindow: def on_faq_menuitem_activate(self, widget): helpers.launch_browser_mailer('url', - 'http://trac.gajim.org/wiki/GajimFaq') + 'http://trac.gajim.org/wiki/GajimFaq') def on_features_menuitem_activate(self, widget): features_window.FeaturesWindow() @@ -3557,7 +3608,7 @@ class RosterWindow: gajim.interface.instances['logs'].window.present() else: gajim.interface.instances['logs'] = history_window.\ - HistoryWindow() + HistoryWindow() def on_show_transports_menuitem_activate(self, widget): gajim.config.set('show_transports_group', widget.get_active()) @@ -3572,7 +3623,8 @@ class RosterWindow: def on_execute_command(self, widget, contact, account, resource=None): """ Execute command. Full JID needed; if it is other contact, resource is - necessary. Widget is unnecessary, only to be able to make this a callback + necessary. Widget is unnecessary, only to be able to make this a + callback """ jid = contact.jid if resource is not None: @@ -3612,7 +3664,8 @@ class RosterWindow: not gajim.config.get('quit_on_roster_x_button'): self.tooltip.hide_tooltip() self.window.hide() - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i: + elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \ + gtk.keysyms.i: treeselection = self.tree.get_selection() model, list_of_paths = treeselection.get_selected_rows() for path in list_of_paths: @@ -3620,9 +3673,11 @@ class RosterWindow: if type_ in ('contact', 'agent'): jid = model[path][C_JID].decode('utf-8') account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_first_contact_from_jid(account, jid) + contact = gajim.contacts.get_first_contact_from_jid(account, + jid) self.on_info(widget, contact, account) - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h: + elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == \ + gtk.keysyms.h: treeselection = self.tree.get_selection() model, list_of_paths = treeselection.get_selected_rows() if len(list_of_paths) != 1: @@ -3632,7 +3687,8 @@ class RosterWindow: if type_ in ('contact', 'agent'): jid = model[path][C_JID].decode('utf-8') account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_first_contact_from_jid(account, jid) + contact = gajim.contacts.get_first_contact_from_jid(account, + jid) self.on_history(widget, contact, account) def on_roster_window_popup_menu(self, widget): @@ -3704,7 +3760,7 @@ class RosterWindow: resource = contact.resource gajim.interface.on_open_chat_window(None, contact, account, \ - resource=resource, session=session) + resource=resource, session=session) def on_roster_treeview_row_activated(self, widget, path, col=0): """ @@ -3730,8 +3786,8 @@ class RosterWindow: type_ = model[titer][C_TYPE] if type_ == 'group': group = model[titer][C_JID].decode('utf-8') - child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ - '16']['opened'] + child_model[child_iter][C_IMG] = \ + gajim.interface.jabber_state_images['16']['opened'] for account in accounts: if group in gajim.groups[account]: # This account has this group gajim.groups[account][group]['expand'] = True @@ -3739,9 +3795,9 @@ class RosterWindow: self.collapsed_rows.remove(account + group) for contact in gajim.contacts.iter_contacts(account): jid = contact.jid - if group in contact.groups and gajim.contacts.is_big_brother( - account, jid, accounts) and account + group + jid \ - not in self.collapsed_rows: + if group in contact.groups and \ + gajim.contacts.is_big_brother(account, jid, accounts) and \ + account + group + jid not in self.collapsed_rows: titers = self._get_contact_iter(jid, account) for titer in titers: path = model.get_path(titer) @@ -3768,7 +3824,7 @@ class RosterWindow: self.collapsed_rows.remove(account + group + jid) family = gajim.contacts.get_metacontacts_family(account, jid) nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] + self._get_nearby_family_and_big_brother(family, account)[0] # Redraw all brothers to show pending events for data in nearby_family: self.draw_contact(data['jid'], data['account']) @@ -3791,8 +3847,8 @@ class RosterWindow: type_ = model[titer][C_TYPE] if type_ == 'group': - child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ - '16']['closed'] + child_model[child_iter][C_IMG] = gajim.interface.\ + jabber_state_images['16']['closed'] group = model[titer][C_JID].decode('utf-8') for account in accounts: if group in gajim.groups[account]: # This account has this group @@ -3868,7 +3924,8 @@ class RosterWindow: # if len(self._last_selected_contact): # # update unselected rows # for (jid, account) in self._last_selected_contact: -# gobject.idle_add(self.draw_contact, jid, account) +# gobject.idle_add(self.draw_contact, jid, +# account) # self._last_selected_contact = [] # if len(list_of_paths) == 0: # return @@ -3886,7 +3943,7 @@ class RosterWindow: server_jid = gajim.config.get_per('accounts', account, 'hostname') if server_jid in gajim.interface.instances[account]['disco']: gajim.interface.instances[account]['disco'][server_jid].\ - window.present() + window.present() else: try: # Object will add itself to the window dict @@ -3942,7 +3999,8 @@ class RosterWindow: ### Drag and Drop handling ################################################################################ - def drag_data_get_data(self, treeview, context, selection, target_id, etime): + def drag_data_get_data(self, treeview, context, selection, target_id, + etime): model, list_of_paths = self.tree.get_selection().get_selected_rows() if len(list_of_paths) != 1: return @@ -3965,12 +4023,13 @@ class RosterWindow: def on_drop_in_contact(self, widget, account_source, c_source, account_dest, c_dest, was_big_brother, context, etime): - if not gajim.connections[account_source].private_storage_supported or not\ - gajim.connections[account_dest].private_storage_supported: - dialogs.WarningDialog(_('Metacontacts storage not supported by your ' - 'server'), - _('Your server does not support storing metacontacts information. ' - 'So those information will not be saved on next reconnection.')) + if not gajim.connections[account_source].private_storage_supported or \ + not gajim.connections[account_dest].private_storage_supported: + dialogs.WarningDialog(_('Metacontacts storage not supported by ' + 'your server'), + _('Your server does not support storing metacontacts ' + 'information. So those information will not be saved on next ' + 'reconnection.')) def merge_contacts(is_checked=None): contacts = 0 @@ -3983,10 +4042,11 @@ class RosterWindow: # We might have dropped on a metacontact. # Remove it and readd later with updated family info dest_family = gajim.contacts.get_metacontacts_family(account_dest, - c_dest.jid) + c_dest.jid) if dest_family: self._remove_metacontact_family(dest_family, account_dest) - source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid) + source_family = gajim.contacts.get_metacontacts_family( + account_source, c_source.jid) if dest_family == source_family: n = contacts = len(dest_family) for tag in source_family: @@ -4008,7 +4068,8 @@ class RosterWindow: # We have got little brothers. Readd them all self._remove_metacontact_family(old_family, account_source) else: - # We are only a litle brother. Simply remove us from our big brother + # We are only a litle brother. Simply remove us from our big + # brother if self._get_contact_iter(c_source.jid, account_source): # When we have been in the group before. # Do not try to remove us again @@ -4027,20 +4088,21 @@ class RosterWindow: _account = data['account'] _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + _contact = gajim.contacts.get_first_contact_from_jid(_account, + _jid) if not _contact: # One of the metacontacts may be not connected. continue _contact.groups = c_dest.groups[:] gajim.contacts.add_metacontact(account_dest, c_dest.jid, - _account, _contact.jid, contacts) + _account, _contact.jid, contacts) gajim.connections[account_source].update_contact(_contact.jid, - _contact.name, _contact.groups) + _contact.name, _contact.groups) # Re-add all and update GUI new_family = gajim.contacts.get_metacontacts_family(account_source, - c_source.jid) + c_source.jid) brothers = self._add_metacontact_family(new_family, account_source) for c, acc in brothers: @@ -4057,30 +4119,30 @@ class RosterWindow: if confirm_metacontacts == 'no': merge_contacts() return - pritext = _('You are about to create a metacontact. Are you sure you want' - ' to continue?') + pritext = _('You are about to create a metacontact. Are you sure you ' + 'want to continue?') sectext = _('Metacontacts are a way to regroup several contacts in one ' - 'line. Generally it is used when the same person has several Jabber ' - 'accounts or transport accounts.') + 'line. Generally it is used when the same person has several ' + 'Jabber accounts or transport accounts.') dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=merge_contacts) + _('_Do not ask me again'), on_response_ok=merge_contacts) if not confirm_metacontacts: # First time we see this window dlg.checkbutton.set_active(True) - def on_drop_in_group(self, widget, account, c_source, grp_dest, is_big_brother, context, etime, grp_source = None): if is_big_brother: # add whole metacontact to new group - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + self.add_contact_to_groups(c_source.jid, account, [grp_dest, ]) # remove afterwards so the contact is not moved to General in the # meantime if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, [grp_source]) + self.remove_contact_from_groups(c_source.jid, account, + [grp_source]) else: # Normal contact or little brother family = gajim.contacts.get_metacontacts_family(account, - c_source.jid) + c_source.jid) if family: # Little brother # Remove whole family. Remove us from the family. @@ -4096,21 +4158,20 @@ class RosterWindow: self.add_contact(data['jid'], data['account']) break - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + self.add_contact_to_groups(c_source.jid, account, [grp_dest, ]) else: # Normal contact - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - # remove afterwards so the contact is not moved to General in the - # meantime + self.add_contact_to_groups(c_source.jid, account, [grp_dest, ]) + # remove afterwards so the contact is not moved to General in + # the meantime if grp_dest != grp_source: self.remove_contact_from_groups(c_source.jid, account, - [grp_source]) + [grp_source]) if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): context.finish(True, True, etime) - def drag_drop(self, treeview, context, x, y, timestamp): target_list = treeview.drag_dest_get_target_list() target = treeview.drag_dest_find_target(context, target_list) @@ -4134,7 +4195,8 @@ class RosterWindow: and path_dest[1] == 0: # dropped before the first group return if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: - # dropped before a group: we drop it in the previous group every time + # dropped before a group: we drop it in the previous group every + # time path_dest = (path_dest[0], path_dest[1]-1) # destination: the row something got dropped on iter_dest = model.get_iter(path_dest) @@ -4155,8 +4217,8 @@ class RosterWindow: return if type_dest != 'contact': return - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) + c_dest = gajim.contacts.get_contact_with_highest_priority( + account_dest, jid_dest) if not c_dest.supports(NS_FILE): return uri = data.strip() @@ -4177,23 +4239,24 @@ class RosterWindow: dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) return def _on_send_files(account, jid, uris): - c = gajim.contacts.get_contact_with_highest_priority(account, jid) + c = gajim.contacts.get_contact_with_highest_priority(account, + jid) for uri in uris: path = helpers.get_file_path_from_dnd_dropped_uri(uri) if os.path.isfile(path): # is it file? gajim.interface.instances['file_transfers'].send_file( - account, c, path) + account, c, path) # Popup dialog to confirm sending prim_text = 'Send file?' sec_text = i18n.ngettext('Do you want to send this file to %s:', - 'Do you want to send these files to %s:', nb_uri) %\ - c_dest.get_shown_name() + 'Do you want to send these files to %s:', nb_uri) %\ + c_dest.get_shown_name() for uri in uri_splitted: path = helpers.get_file_path_from_dnd_dropped_uri(uri) sec_text += '\n' + os.path.basename(path) dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (_on_send_files, account_dest, jid_dest, - uri_splitted)) + on_response_ok=(_on_send_files, account_dest, jid_dest, + uri_splitted)) dialog.popup() return @@ -4239,7 +4302,7 @@ class RosterWindow: return jid_source = data.decode('utf-8') c_source = gajim.contacts.get_contact_with_highest_priority( - account_source, jid_source) + account_source, jid_source) # Get destination group grp_dest = None @@ -4260,10 +4323,10 @@ class RosterWindow: # contact drop somewhere in or on a foreign account if (type_dest == 'account' or not self.regroup) and \ - account_source != account_dest: + account_source != account_dest: # add to account in specified group dialogs.AddNewContactWindow(account=account_dest, jid=jid_source, - user_nick=c_source.name, group=grp_dest) + user_nick=c_source.name, group=grp_dest) return # we may not add contacts from special_groups @@ -4273,14 +4336,14 @@ class RosterWindow: # Is the contact we drag a meta contact? accounts = (self.regroup and gajim.contacts.get_accounts()) or \ account_source - is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source, - accounts) + is_big_brother = gajim.contacts.is_big_brother(account_source, + jid_source, accounts) drop_in_middle_of_meta = False if type_dest == 'contact': if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4: drop_in_middle_of_meta = True - if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \ + if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or\ self.modelfilter.iter_has_child(iter_dest)): drop_in_middle_of_meta = True # Contact drop on group row or between two contacts that are @@ -4288,14 +4351,14 @@ class RosterWindow: if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta: self.on_drop_in_group(None, account_source, c_source, grp_dest, - is_big_brother, context, etime, grp_source) + is_big_brother, context, etime, grp_source) return # Contact drop on another contact, make meta contacts if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta: - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) + c_dest = gajim.contacts.get_contact_with_highest_priority( + account_dest, jid_dest) if not c_dest: # c_dest is None if jid_dest doesn't belong to account return @@ -4303,13 +4366,13 @@ class RosterWindow: item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(), c_dest.get_shown_name())) item.connect('activate', self.on_drop_rosterx, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) + c_source, account_dest, c_dest, is_big_brother, context, etime) menu.append(item) item = gtk.MenuItem(_('Make %s and %s metacontacts') % ( c_source.get_shown_name(), c_dest.get_shown_name())) item.connect('activate', self.on_drop_in_contact, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) + c_source, account_dest, c_dest, is_big_brother, context, etime) menu.append(item) @@ -4333,7 +4396,8 @@ class RosterWindow: transport = gajim.get_transport_name_from_jid(jid) if transport and size in self.transports_state_images: if transport not in self.transports_state_images[size]: - # we don't have iconset for this transport loaded yet. Let's do it + # we don't have iconset for this transport loaded yet. Let's do + # it self.make_transport_state_images(transport) if transport in self.transports_state_images[size] and \ icon_name in self.transports_state_images[size][transport]: @@ -4346,18 +4410,20 @@ class RosterWindow: """ if gajim.config.get('use_transports_iconsets'): folder = os.path.join(helpers.get_transport_path(transport), - '16x16') + '16x16') pixo, pixc = gtkgui_helpers.load_icons_meta() self.transports_state_images['opened'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixo, transport=True) + gtkgui_helpers.load_iconset(folder, pixo, transport=True) self.transports_state_images['closed'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixc, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), '32x32') + gtkgui_helpers.load_iconset(folder, pixc, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), + '32x32') self.transports_state_images['32'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), '16x16') + gtkgui_helpers.load_iconset(folder, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), + '16x16') self.transports_state_images['16'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) + gtkgui_helpers.load_iconset(folder, transport=True) def update_jabber_state_images(self): # Update the roster @@ -4369,8 +4435,8 @@ class RosterWindow: if model[titer][2] != '': # If it's not change status message iter # eg. if it has show parameter not '' - model[titer][1] = gajim.interface.jabber_state_images['16'][model[ - titer][2]] + model[titer][1] = gajim.interface.jabber_state_images['16'][ + model[titer][2]] titer = model.iter_next(titer) # Update the systray if gajim.interface.systray_enabled: @@ -4390,10 +4456,10 @@ class RosterWindow: return if not self.regroup: show = gajim.SHOW_LIST[status] - else: # accounts merged + else: # accounts merged show = helpers.get_global_show() self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[ - '16'][show] + '16'][show] ################################################################################ ### Style and theme related methods @@ -4417,9 +4483,9 @@ class RosterWindow: # Count events in roster title only if we don't auto open them if not helpers.allow_popup_window(account): nb_unread += gajim.events.get_nb_events(['chat', 'normal', - 'file-request', 'file-error', 'file-completed', - 'file-request-error', 'file-send-error', 'file-stopped', - 'printed_chat'], account) + 'file-request', 'file-error', 'file-completed', + 'file-request-error', 'file-send-error', 'file-stopped', + 'printed_chat'], account) if nb_unread > 1: start = '[' + str(nb_unread) + '] ' elif nb_unread == 1: @@ -4466,7 +4532,7 @@ class RosterWindow: """ for contact in self._iter_contact_rows(): self.draw_contact(contact[C_JID].decode('utf-8'), - contact[C_ACCOUNT].decode('utf-8')) + contact[C_ACCOUNT].decode('utf-8')) def set_renderer_color(self, renderer, style, set_background=True): """ @@ -4490,7 +4556,8 @@ class RosterWindow: elif type_ == 'group': self._set_group_row_background_color(renderer) renderer.set_property('xalign', 0.2) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + elif type_: + # prevent type_ = None, see http://trac.gajim.org/ticket/2534 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: # This can append when at the moment we add the row return @@ -4517,7 +4584,7 @@ class RosterWindow: else: self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) + gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) renderer.set_property('xpad', 0) renderer.set_property('width', 3) self._set_account_row_background_color(renderer) @@ -4528,10 +4595,11 @@ class RosterWindow: else: self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) renderer.set_property('xpad', 4) self._set_group_row_background_color(renderer) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + elif type_: + # prevent type_ = None, see http://trac.gajim.org/ticket/2534 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: # This can append when at the moment we add the row return @@ -4539,20 +4607,22 @@ class RosterWindow: account = model[titer][C_ACCOUNT].decode('utf-8') color = None if type_ == 'groupchat': - ctrl = gajim.interface.minimized_controls[account].get(jid, None) + ctrl = gajim.interface.minimized_controls[account].get(jid, + None) if ctrl and ctrl.attention_flag: color = gajim.config.get_per('themes', theme, - 'state_muc_directed_msg_color') + 'state_muc_directed_msg_color') renderer.set_property('foreground', 'red') if not color: - color = gajim.config.get_per('themes', theme, 'contacttextcolor') + color = gajim.config.get_per('themes', theme, + 'contacttextcolor') if color: renderer.set_property('foreground', color) else: renderer.set_property('foreground', None) self._set_contact_row_background_color(renderer, jid, account) renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) parent_iter = model.iter_parent(titer) if model[parent_iter][C_TYPE] == 'contact': renderer.set_property('xpad', 16) @@ -4560,11 +4630,10 @@ class RosterWindow: renderer.set_property('xpad', 8) def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer, - data=None): + data=None): """ When a row is added, draw the respective pep icon """ - theme = gajim.config.get('roster_theme') type_ = model[titer][C_TYPE] # allocate space for the icon only if needed @@ -4584,8 +4653,8 @@ class RosterWindow: account = model[titer][C_ACCOUNT].decode('utf-8') self._set_contact_row_background_color(renderer, jid, account) - def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, data - = None): + def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, + data=None): """ When a row is added, set properties for avatar renderer """ @@ -4598,7 +4667,8 @@ class RosterWindow: if model[titer][C_AVATAR_PIXBUF] or \ gajim.config.get('avatar_position_in_roster') == 'left': renderer.set_property('visible', True) - if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if type_: + # prevent type_ = None, see http://trac.gajim.org/ticket/2534 if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: # This can append at the moment we add the row return @@ -4609,12 +4679,14 @@ class RosterWindow: renderer.set_property('visible', False) if gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('width', gajim.config.get('roster_avatar_width')) + renderer.set_property('width', gajim.config.get( + 'roster_avatar_width')) renderer.set_property('xalign', 0.5) else: renderer.set_property('xalign', 1) # align pixbuf to the right - def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, data=None): + def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, + data=None): """ When a row is added, set properties for padlock renderer """ @@ -4642,7 +4714,7 @@ class RosterWindow: 'just_connected_bg_color')) elif jid in gajim.to_be_removed[account]: renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) + 'just_disconnected_bg_color')) else: color = gajim.config.get_per('themes', theme, 'contactbgcolor') renderer.set_property('cell-background', color if color else None) @@ -4673,7 +4745,8 @@ class RosterWindow: muc_icon = gtkgui_helpers.load_icon('muc_active') if muc_icon: join_gc_menuitem.set_image(muc_icon) - add_new_contact_menuitem = self.xml.get_object('add_new_contact_menuitem') + add_new_contact_menuitem = self.xml.get_object( + 'add_new_contact_menuitem') service_disco_menuitem = self.xml.get_object('service_disco_menuitem') advanced_menuitem = self.xml.get_object('advanced_menuitem') profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem') @@ -4682,32 +4755,33 @@ class RosterWindow: for m in self.advanced_menus: m.destroy() - # make it sensitive. it is insensitive only if no accounts are *available* + # make it sensitive. it is insensitive only if no accounts are + # *available* advanced_menuitem.set_sensitive(True) if self.add_new_contact_handler_id: add_new_contact_menuitem.handler_disconnect( - self.add_new_contact_handler_id) + self.add_new_contact_handler_id) self.add_new_contact_handler_id = None if self.service_disco_handler_id: service_disco_menuitem.handler_disconnect( - self.service_disco_handler_id) + self.service_disco_handler_id) self.service_disco_handler_id = None if self.new_chat_menuitem_handler_id: new_chat_menuitem.handler_disconnect( - self.new_chat_menuitem_handler_id) + self.new_chat_menuitem_handler_id) self.new_chat_menuitem_handler_id = None if self.single_message_menuitem_handler_id: single_message_menuitem.handler_disconnect( - self.single_message_menuitem_handler_id) + self.single_message_menuitem_handler_id) self.single_message_menuitem_handler_id = None if self.profile_avatar_menuitem_handler_id: profile_avatar_menuitem.handler_disconnect( - self.profile_avatar_menuitem_handler_id) + self.profile_avatar_menuitem_handler_id) self.profile_avatar_menuitem_handler_id = None # remove the existing submenus @@ -4723,7 +4797,7 @@ class RosterWindow: if self.have_new_chat_accel: ag = gtk.accel_groups_from_object(self.window)[0] new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK) self.have_new_chat_accel = False gc_sub_menu = gtk.Menu() # gc is always a submenu @@ -4745,10 +4819,10 @@ class RosterWindow: # new chat new_chat_item = gtk.MenuItem(_('using account %s') % account, - False) + False) new_chat_sub_menu.append(new_chat_item) new_chat_item.connect('activate', - self.on_new_chat_menuitem_activate, account) + self.on_new_chat_menuitem_activate, account) new_chat_menuitem.set_submenu(new_chat_sub_menu) new_chat_sub_menu.show_all() @@ -4759,15 +4833,16 @@ class RosterWindow: # new chat if not self.new_chat_menuitem_handler_id: self.new_chat_menuitem_handler_id = new_chat_menuitem.\ - connect('activate', self.on_new_chat_menuitem_activate, - account) + connect('activate', + self.on_new_chat_menuitem_activate, account) break # menu items that don't apply to zeroconf connections if connected_accounts == 1 or (connected_accounts == 2 and \ gajim.zeroconf_is_connected()): - # only one 'real' (non-zeroconf) account is connected, don't need submenus + # only one 'real' (non-zeroconf) account is connected, don't need + # submenus for account in accounts_list: if gajim.account_is_connected(account) and \ @@ -4779,14 +4854,14 @@ class RosterWindow: gc_sub_menu.show_all() # add if not self.add_new_contact_handler_id: - self.add_new_contact_handler_id =\ - add_new_contact_menuitem.connect( - 'activate', self.on_add_new_contact, account) + self.add_new_contact_handler_id = \ + add_new_contact_menuitem.connect( + 'activate', self.on_add_new_contact, account) # disco if not self.service_disco_handler_id: self.service_disco_handler_id = service_disco_menuitem.\ - connect('activate', - self.on_service_disco_menuitem_activate, account) + connect('activate', + self.on_service_disco_menuitem_activate, account) # single message if not self.single_message_menuitem_handler_id: @@ -4798,7 +4873,8 @@ class RosterWindow: if not self.have_new_chat_accel: ag = gtk.accel_groups_from_object(self.window)[0] new_chat_menuitem.add_accelerator('activate', ag, - gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + gtk.keysyms.n, gtk.gdk.CONTROL_MASK, + gtk.ACCEL_VISIBLE) self.have_new_chat_accel = True break # No other account connected @@ -4815,11 +4891,11 @@ class RosterWindow: continue # single message - single_message_item = gtk.MenuItem(_('using account %s') % account, - False) + single_message_item = gtk.MenuItem(_('using account %s') % \ + account, False) single_message_sub_menu.append(single_message_item) single_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account) + self.on_send_single_message_menuitem_activate, account) # join gc if gajim.connections[account].private_storage_supported: @@ -4836,10 +4912,11 @@ class RosterWindow: add_item.connect('activate', self.on_add_new_contact, account) # disco - disco_item = gtk.MenuItem(_('using %s account') % account, False) + disco_item = gtk.MenuItem(_('using %s account') % account, + False) disco_sub_menu.append(disco_item) disco_item.connect('activate', - self.on_service_disco_menuitem_activate, account) + self.on_service_disco_menuitem_activate, account) single_message_menuitem.set_submenu(single_message_sub_menu) single_message_sub_menu.show_all() @@ -4851,22 +4928,22 @@ class RosterWindow: if connected_accounts == 0: # no connected accounts, make the menuitems insensitive - for item in (new_chat_menuitem, join_gc_menuitem,\ - add_new_contact_menuitem, service_disco_menuitem,\ - single_message_menuitem): + for item in (new_chat_menuitem, join_gc_menuitem, + add_new_contact_menuitem, service_disco_menuitem, + single_message_menuitem): item.set_sensitive(False) else: # we have one or more connected accounts for item in (new_chat_menuitem, join_gc_menuitem, - add_new_contact_menuitem, service_disco_menuitem, - single_message_menuitem): + add_new_contact_menuitem, service_disco_menuitem, + single_message_menuitem): item.set_sensitive(True) # disable some fields if only local account is there if connected_accounts == 1: for account in gajim.connections: if gajim.account_is_connected(account) and \ - gajim.connections[account].is_zeroconf: + gajim.connections[account].is_zeroconf: for item in (join_gc_menuitem, add_new_contact_menuitem, - service_disco_menuitem, single_message_menuitem): + service_disco_menuitem, single_message_menuitem): item.set_sensitive(False) # Manage GC bookmarks @@ -4875,7 +4952,7 @@ class RosterWindow: newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) newitem.set_image(img) newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate) gc_sub_menu.append(newitem) @@ -4894,19 +4971,20 @@ class RosterWindow: for account in connected_accounts_with_vcard: # profile, avatar profile_avatar_item = gtk.MenuItem(_('of account %s') % account, - False) + False) profile_avatar_sub_menu.append(profile_avatar_item) profile_avatar_item.connect('activate', - self.on_profile_avatar_menuitem_activate, account) + self.on_profile_avatar_menuitem_activate, account) profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu) profile_avatar_sub_menu.show_all() - elif len(connected_accounts_with_vcard) == 1: # user has only one account + elif len(connected_accounts_with_vcard) == 1: + # user has only one account account = connected_accounts_with_vcard[0] # profile, avatar if not self.profile_avatar_menuitem_handler_id: self.profile_avatar_menuitem_handler_id = \ - profile_avatar_menuitem.connect('activate', - self.on_profile_avatar_menuitem_activate, account) + profile_avatar_menuitem.connect('activate', + self.on_profile_avatar_menuitem_activate, account) if len(connected_accounts_with_vcard) == 0: profile_avatar_menuitem.set_sensitive(False) @@ -4918,8 +4996,8 @@ class RosterWindow: advanced_menuitem.set_sensitive(False) elif len(gajim.connections) == 1: # we have one acccount account = gajim.connections.keys()[0] - advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu( - account) + advanced_menuitem_menu = \ + self.get_and_connect_advanced_menuitem_menu(account) self.advanced_menus.append(advanced_menuitem_menu) self.add_history_manager_menuitem(advanced_menuitem_menu) @@ -4933,10 +5011,11 @@ class RosterWindow: accounts.append(account) accounts.sort() for account in accounts: - advanced_item = gtk.MenuItem(_('for account %s') % account, False) + advanced_item = gtk.MenuItem(_('for account %s') % account, + False) advanced_sub_menu.append(advanced_item) advanced_menuitem_menu = \ - self.get_and_connect_advanced_menuitem_menu(account) + self.get_and_connect_advanced_menuitem_menu(account) self.advanced_menus.append(advanced_menuitem_menu) advanced_item.set_submenu(advanced_menuitem_menu) @@ -4960,15 +5039,18 @@ class RosterWindow: status_menuitem = xml.get_object('status_menuitem') start_chat_menuitem = xml.get_object('start_chat_menuitem') - join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem') + join_group_chat_menuitem = xml.get_object( + 'join_group_chat_menuitem') muc_icon = gtkgui_helpers.load_icon('muc_active') if muc_icon: join_group_chat_menuitem.set_image(muc_icon) - open_gmail_inbox_menuitem = xml.get_object('open_gmail_inbox_menuitem') + open_gmail_inbox_menuitem = xml.get_object( + 'open_gmail_inbox_menuitem') add_contact_menuitem = xml.get_object('add_contact_menuitem') service_discovery_menuitem = xml.get_object( - 'service_discovery_menuitem') - execute_command_menuitem = xml.get_object('execute_command_menuitem') + 'service_discovery_menuitem') + execute_command_menuitem = xml.get_object( + 'execute_command_menuitem') edit_account_menuitem = xml.get_object('edit_account_menuitem') sub_menu = gtk.Menu() status_menuitem.set_submenu(sub_menu) @@ -4993,7 +5075,7 @@ class RosterWindow: gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') sub_menu.append(item) item.connect('activate', self.on_change_status_message_activate, - account) + account) if gajim.connections[account].connected < 2: item.set_sensitive(False) @@ -5017,14 +5099,15 @@ class RosterWindow: if not dbus_support.supported: item.set_sensitive(False) else: - activ = gajim.config.get_per('accounts', account, opt_name) + activ = gajim.config.get_per('accounts', account, + opt_name) item.set_active(activ) item.connect('toggled', func, account) add_item(_('Publish Tune'), 'publish_tune', - self.on_publish_tune_toggled) + self.on_publish_tune_toggled) add_item(_('Publish Location'), 'publish_location', - self.on_publish_location_toggled) + self.on_publish_location_toggled) pep_config = gtk.ImageMenuItem(_('Configure Services...')) item = gtk.SeparatorMenuItem() @@ -5032,9 +5115,9 @@ class RosterWindow: pep_config.set_sensitive(True) pep_submenu.append(pep_config) pep_config.connect('activate', - self.on_pep_services_menuitem_activate, account) + self.on_pep_services_menuitem_activate, account) img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) pep_config.set_image(img) else: @@ -5045,21 +5128,22 @@ class RosterWindow: open_gmail_inbox_menuitem.hide() else: open_gmail_inbox_menuitem.connect('activate', - self.on_open_gmail_inbox, account) + self.on_open_gmail_inbox, account) edit_account_menuitem.connect('activate', self.on_edit_account, - account) + account) add_contact_menuitem.connect('activate', self.on_add_new_contact, - account) + account) service_discovery_menuitem.connect('activate', - self.on_service_disco_menuitem_activate, account) + self.on_service_disco_menuitem_activate, account) hostname = gajim.config.get_per('accounts', account, 'hostname') - contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact + contact = gajim.contacts.create_contact(jid=hostname, + account=account) # Fake contact execute_command_menuitem.connect('activate', - self.on_execute_command, contact, account) + self.on_execute_command, contact, account) start_chat_menuitem.connect('activate', - self.on_new_chat_menuitem_activate, account) + self.on_new_chat_menuitem_activate, account) gc_sub_menu = gtk.Menu() # gc is always a submenu join_group_chat_menuitem.set_submenu(gc_sub_menu) @@ -5068,8 +5152,8 @@ class RosterWindow: # make some items insensitive if account is offline if gajim.connections[account].connected < 2: for widget in (add_contact_menuitem, service_discovery_menuitem, - join_group_chat_menuitem, execute_command_menuitem, pep_menuitem, - start_chat_menuitem): + join_group_chat_menuitem, execute_command_menuitem, + pep_menuitem, start_chat_menuitem): widget.set_sensitive(False) else: xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui') @@ -5096,7 +5180,7 @@ class RosterWindow: gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') sub_menu.append(item) item.connect('activate', self.on_change_status_message_activate, - account) + account) if gajim.connections[account].connected < 2: item.set_sensitive(False) @@ -5108,7 +5192,7 @@ class RosterWindow: item.connect('activate', self.change_status, account, 'offline') zeroconf_properties_menuitem.connect('activate', - self.on_zeroconf_properties, account) + self.on_zeroconf_properties, account) return account_context_menu @@ -5161,7 +5245,7 @@ class RosterWindow: group = model[titer][C_JID] for jid in gajim.contacts.get_jid_list(account): contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) + jid) if group in contact.get_shown_groups(): if contact.show not in ('offline', 'error'): list_online.append((contact, account)) @@ -5171,14 +5255,16 @@ class RosterWindow: # Make special context menu if group is Groupchats if group == _('Groupchats'): maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, + gtk.ICON_SIZE_MENU) maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ - list_) + maximize_menuitem.connect('activate', + self.on_all_groupchat_maximized, list_) menu.append(maximize_menuitem) else: # Send Group Message - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + send_group_message_item = gtk.ImageMenuItem( + _('Send Group M_essage')) icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) send_group_message_item.set_image(icon) @@ -5190,13 +5276,14 @@ class RosterWindow: send_group_message_submenu.append(group_message_to_all_item) group_message_to_all_online_item = gtk.MenuItem( - _('To all online users')) + _('To all online users')) send_group_message_submenu.append(group_message_to_all_online_item) group_message_to_all_online_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_online) + self.on_send_single_message_menuitem_activate, account, + list_online) group_message_to_all_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) + self.on_send_single_message_menuitem_activate, account, list_) # Invite to invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) @@ -5209,15 +5296,15 @@ class RosterWindow: # Send Custom Status send_custom_status_menuitem = gtk.ImageMenuItem( - _('Send Cus_tom Status')) + _('Send Cus_tom Status')) # add a special img for this menuitem if helpers.group_is_blocked(account, group): send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) + 'offline')) send_custom_status_menuitem.set_sensitive(False) else: icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) send_custom_status_menuitem.set_image(icon) status_menuitems = gtk.Menu() send_custom_status_menuitem.set_submenu(status_menuitems) @@ -5228,7 +5315,7 @@ class RosterWindow: state_images = gtkgui_helpers.load_iconset(path) status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) status_menuitem.connect('activate', self.on_send_custom_status, - list_, s, group) + list_, s, group) icon = state_images[s] status_menuitem.set_image(icon) status_menuitems.append(status_menuitem) @@ -5249,7 +5336,7 @@ class RosterWindow: gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input') menu.append(rename_item) rename_item.connect('activate', self.on_rename, 'group', group, - account) + account) # Block group is_blocked = False @@ -5261,15 +5348,19 @@ class RosterWindow: if helpers.group_is_blocked(account, group): is_blocked = True - if is_blocked and gajim.connections[account].privacy_rules_supported: + if is_blocked and gajim.connections[account].\ + privacy_rules_supported: unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, + gtk.ICON_SIZE_MENU) unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, list_, group) + unblock_menuitem.connect('activate', self.on_unblock, list_, + group) menu.append(unblock_menuitem) else: block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, + gtk.ICON_SIZE_MENU) block_menuitem.set_image(icon) block_menuitem.connect('activate', self.on_block, list_, group) menu.append(block_menuitem) @@ -5278,11 +5369,12 @@ class RosterWindow: # Remove group remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, + gtk.ICON_SIZE_MENU) remove_item.set_image(icon) menu.append(remove_item) remove_item.connect('activate', self.on_remove_group_item_activated, - group, account) + group, account) # unsensitive if account is not connected if gajim.connections[account].connected < 2: @@ -5307,7 +5399,6 @@ class RosterWindow: """ model = self.modelfilter jid = model[titer][C_JID].decode('utf-8') - tree_path = model.get_path(titer) account = model[titer][C_ACCOUNT].decode('utf-8') contact = gajim.contacts.get_contact_with_highest_priority(account, jid) menu = gui_menu_builder.get_contact_menu(contact, account) @@ -5332,7 +5423,7 @@ class RosterWindow: if not gajim.connections[account].privacy_rules_supported: privacy_rules_supported = False contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) + jid) if helpers.jid_is_blocked(account, jid): is_blocked = False list_.append((contact, account)) @@ -5346,12 +5437,13 @@ class RosterWindow: break account = current_account if account is not None: - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + send_group_message_item = gtk.ImageMenuItem( + _('Send Group M_essage')) icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) send_group_message_item.set_image(icon) menu.append(send_group_message_item) send_group_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) + self.on_send_single_message_menuitem_activate, account, list_) # Invite to Groupchat invite_item = gtk.ImageMenuItem(_('In_vite to')) @@ -5367,7 +5459,8 @@ class RosterWindow: # Manage Transport submenu item = gtk.ImageMenuItem(_('_Manage Contacts')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, + gtk.ICON_SIZE_MENU) item.set_image(icon) manage_contacts_submenu = gtk.Menu() item.set_submenu(manage_contacts_submenu) @@ -5441,20 +5534,21 @@ class RosterWindow: blocked = True # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) + send_custom_status_menuitem = gtk.ImageMenuItem( + _('Send Cus_tom Status')) # add a special img for this menuitem if blocked: send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) + 'offline')) send_custom_status_menuitem.set_sensitive(False) else: if account in gajim.interface.status_sent_to_users and \ jid in gajim.interface.status_sent_to_users[account]: send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - gajim.interface.status_sent_to_users[account][jid])) + gajim.interface.status_sent_to_users[account][jid])) else: icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) send_custom_status_menuitem.set_image(icon) status_menuitems = gtk.Menu() send_custom_status_menuitem.set_submenu(status_menuitems) @@ -5465,7 +5559,7 @@ class RosterWindow: state_images = gtkgui_helpers.load_iconset(path) status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) status_menuitem.connect('activate', self.on_send_custom_status, - [(contact, account)], s) + [(contact, account)], s) icon = state_images[s] status_menuitem.set_image(icon) status_menuitems.append(status_menuitem) @@ -5480,13 +5574,14 @@ class RosterWindow: item.set_image(icon) menu.append(item) item.connect('activate', self.on_execute_command, contact, account, - contact.resource) + contact.resource) if gajim.account_is_disconnected(account): item.set_sensitive(False) # Manage Transport submenu item = gtk.ImageMenuItem(_('_Manage Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, + gtk.ICON_SIZE_MENU) item.set_image(icon) manage_transport_submenu = gtk.Menu() item.set_submenu(manage_transport_submenu) @@ -5494,7 +5589,8 @@ class RosterWindow: # Modify Transport item = gtk.ImageMenuItem(_('_Modify Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, + gtk.ICON_SIZE_MENU) item.set_image(icon) manage_transport_submenu.append(item) item.connect('activate', self.on_edit_agent, contact, account) @@ -5546,7 +5642,6 @@ class RosterWindow: menu.append(information_menuitem) information_menuitem.connect('activate', self.on_info, contact, account) - event_button = gtkgui_helpers.get_possible_button_event(event) menu.attach_to_widget(self.tree, None) @@ -5564,24 +5659,27 @@ class RosterWindow: if jid in gajim.interface.minimized_controls[account]: maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, + gtk.ICON_SIZE_MENU) maximize_menuitem.set_image(icon) maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ - jid, account) + jid, account) menu.append(maximize_menuitem) if not gajim.gc_connected[account].get(jid, False): connect_menuitem = gtk.ImageMenuItem(_('_Reconnect')) connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \ - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) connect_menuitem.set_image(connect_icon) - connect_menuitem.connect('activate', self.on_reconnect, jid, account) + connect_menuitem.connect('activate', self.on_reconnect, jid, + account) menu.append(connect_menuitem) disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) disconnect_menuitem.set_image(disconnect_icon) - disconnect_menuitem.connect('activate', self.on_disconnect, jid, account) + disconnect_menuitem.connect('activate', self.on_disconnect, jid, + account) menu.append(disconnect_menuitem) item = gtk.SeparatorMenuItem() # separator @@ -5589,10 +5687,9 @@ class RosterWindow: history_menuitem = gtk.ImageMenuItem(_('_History')) history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) history_menuitem.set_image(history_icon) - history_menuitem .connect('activate', self.on_history, \ - contact, account) + history_menuitem .connect('activate', self.on_history, contact, account) menu.append(history_menuitem) event_button = gtkgui_helpers.get_possible_button_event(event) @@ -5613,18 +5710,18 @@ class RosterWindow: privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem') administrator_menuitem = xml.get_object('administrator_menuitem') send_server_message_menuitem = xml.get_object( - 'send_server_message_menuitem') + 'send_server_message_menuitem') set_motd_menuitem = xml.get_object('set_motd_menuitem') update_motd_menuitem = xml.get_object('update_motd_menuitem') delete_motd_menuitem = xml.get_object('delete_motd_menuitem') xml_console_menuitem.connect('activate', - self.on_xml_console_menuitem_activate, account) + self.on_xml_console_menuitem_activate, account) if gajim.connections[account] and gajim.connections[account].\ privacy_rules_supported: privacy_lists_menuitem.connect('activate', - self.on_privacy_lists_menuitem_activate, account) + self.on_privacy_lists_menuitem_activate, account) else: privacy_lists_menuitem.set_sensitive(False) @@ -5636,16 +5733,16 @@ class RosterWindow: delete_motd_menuitem.set_sensitive(False) else: send_server_message_menuitem.connect('activate', - self.on_send_server_message_menuitem_activate, account) + self.on_send_server_message_menuitem_activate, account) set_motd_menuitem.connect('activate', - self.on_set_motd_menuitem_activate, account) + self.on_set_motd_menuitem_activate, account) update_motd_menuitem.connect('activate', - self.on_update_motd_menuitem_activate, account) + self.on_update_motd_menuitem_activate, account) delete_motd_menuitem.connect('activate', - self.on_delete_motd_menuitem_activate, account) + self.on_delete_motd_menuitem_activate, account) advanced_menuitem_menu.show_all() @@ -5661,7 +5758,7 @@ class RosterWindow: # History manager item = gtk.ImageMenuItem(_('History Manager')) icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, - gtk.ICON_SIZE_MENU) + gtk.ICON_SIZE_MENU) item.set_image(icon) menu.append(item) item.connect('activate', self.on_history_manager_menuitem_activate) @@ -5755,19 +5852,20 @@ class RosterWindow: self.hpaned = self.xml.get_object('roster_hpaned') gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) gajim.interface.msg_win_mgr.connect('window-delete', - self.on_message_window_delete) + self.on_message_window_delete) self.advanced_menus = [] # We keep them to destroy them if gajim.config.get('roster_window_skip_taskbar'): self.window.set_property('skip-taskbar-hint', True) self.tree = self.xml.get_object('roster_treeview') sel = self.tree.get_selection() sel.set_mode(gtk.SELECTION_MULTIPLE) - #sel.connect('changed', + # sel.connect('changed', # self.on_treeview_selection_changed) - self._last_selected_contact = [] # holds a list of (jid, account) tupples + # holds a list of (jid, account) tupples + self._last_selected_contact = [] self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} + 'closed': {}} self.last_save_dir = None self.editing_path = None # path of row with cell in edit mode @@ -5779,17 +5877,18 @@ class RosterWindow: self.actions_menu_needs_rebuild = True self.regroup = gajim.config.get('mergeaccounts') self.clicked_path = None # Used remember on wich row we clicked - if len(gajim.connections) < 2: # Do not merge accounts if only one exists + if len(gajim.connections) < 2: + # Do not merge accounts if only one exists self.regroup = False #FIXME: When list_accel_closures will be wrapped in pygtk # no need of this variable self.have_new_chat_accel = False # Is the "Ctrl+N" shown ? gtkgui_helpers.resize_window(self.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) gtkgui_helpers.move_window(self.window, - gajim.config.get('roster_x-position'), - gajim.config.get('roster_y-position')) + gajim.config.get('roster_x-position'), + gajim.config.get('roster_y-position')) self.popups_notification_height = 0 self.popup_notification_windows = [] @@ -5800,8 +5899,8 @@ class RosterWindow: gajim.events.event_removed_subscribe(self.on_event_removed) # when this value become 0 we quit main application. If it's more than 0 - # it means we are waiting for this number of accounts to disconnect before - # quitting + # it means we are waiting for this number of accounts to disconnect + # before quitting self.quit_on_next_offline = -1 # uf_show, img, show, sensitive @@ -5830,8 +5929,8 @@ class RosterWindow: for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): uf_show = helpers.get_uf_show(show) - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - show], show, True]) + liststore.append([uf_show, + gajim.interface.jabber_state_images['16'][show], show, True]) # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) liststore.append(['SEPARATOR', None, '', True]) @@ -5840,26 +5939,26 @@ class RosterWindow: img.set_from_file(path) # sensitivity to False because by default we're offline self.status_message_menuitem_iter = liststore.append( - [_('Change Status Message...'), img, '', False]) + [_('Change Status Message...'), img, '', False]) # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) liststore.append(['SEPARATOR', None, '', True]) uf_show = helpers.get_uf_show('offline') liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - 'offline'], 'offline', True]) + 'offline'], 'offline', True]) status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'separator1', 'change_status_msg', 'separator2', - 'offline'] + 'invisible', 'separator1', 'change_status_msg', 'separator2', + 'offline'] self.status_combobox.set_model(liststore) # default to offline number_of_menuitem = status_combobox_items.index('offline') self.status_combobox.set_active(number_of_menuitem) - # holds index to previously selected item so if "change status message..." - # is selected we can fallback to previously selected item and not stay - # with that item selected + # holds index to previously selected item so if + # "change status message..." is selected we can fallback to previously + # selected item and not stay with that item selected self.previous_status_combobox_active = number_of_menuitem showOffline = gajim.config.get('showoffline') @@ -5877,7 +5976,7 @@ class RosterWindow: show_transports_group = gajim.config.get('show_transports_group') self.xml.get_object('show_transports_menuitem').set_active( - show_transports_group) + show_transports_group) self.xml.get_object('show_roster_menuitem').set_active(True) @@ -5892,7 +5991,7 @@ class RosterWindow: col.pack_start(render_pixbuf, expand=False) col.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF) col.set_cell_data_func(render_pixbuf, - self._fill_avatar_pixbuf_renderer, None) + self._fill_avatar_pixbuf_renderer, None) if gajim.config.get('avatar_position_in_roster') == 'left': add_avatar_renderer() @@ -5906,37 +6005,37 @@ class RosterWindow: render_text = gtk.CellRendererText() # contact or group or account name render_text.set_property('ellipsize', pango.ELLIPSIZE_END) col.pack_start(render_text, expand=True) - col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name + col.add_attribute(render_text, 'markup', C_NAME) + # where we hold the name col.set_cell_data_func(render_text, self._nameCellDataFunc, None) render_pixbuf = gtk.CellRendererPixbuf() col.pack_start(render_pixbuf, expand=False) col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF) col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF) + self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF) render_pixbuf = gtk.CellRendererPixbuf() col.pack_start(render_pixbuf, expand=False) col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF) col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF) + self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF) render_pixbuf = gtk.CellRendererPixbuf() col.pack_start(render_pixbuf, expand=False) col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF) col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) + self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) render_pixbuf = gtk.CellRendererPixbuf() col.pack_start(render_pixbuf, expand=False) col.add_attribute(render_pixbuf, 'pixbuf', C_LOCATION_PIXBUF) col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF) + self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF) self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF, - 'activity': C_ACTIVITY_PIXBUF, - 'tune': C_TUNE_PIXBUF, - 'location': C_LOCATION_PIXBUF} + 'activity': C_ACTIVITY_PIXBUF, 'tune': C_TUNE_PIXBUF, + 'location': C_LOCATION_PIXBUF} if gajim.config.get('avatar_position_in_roster') == 'right': add_avatar_renderer() @@ -5945,7 +6044,7 @@ class RosterWindow: col.pack_start(render_pixbuf, expand=False) col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF) col.set_cell_data_func(render_pixbuf, - self._fill_padlock_pixbuf_renderer, None) + self._fill_padlock_pixbuf_renderer, None) self.tree.append_column(col) # do not show gtk arrows workaround @@ -5962,11 +6061,11 @@ class RosterWindow: # signals self.TARGET_TYPE_URI_LIST = 80 TARGETS = [('MY_TREE_MODEL_ROW', - gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)] + gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)] TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), - ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)] + ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)] self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS, - gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) + gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT) self.tree.connect('drag_begin', self.drag_begin) self.tree.connect('drag_end', self.drag_end) @@ -5994,9 +6093,9 @@ class RosterWindow: if len(gajim.connections) == 0: # if we have no account def _open_wizard(): gajim.interface.instances['account_creation_wizard'] = \ - config.AccountCreationWizardWindow() - # Open wizard only after roster is created, so we can make it transient - # for the roster window + config.AccountCreationWizardWindow() + # Open wizard only after roster is created, so we can make it + # transient for the roster window gobject.idle_add(_open_wizard) if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'): # Create zeroconf in config file diff --git a/src/search_window.py b/src/search_window.py index 06362b651..7dc92dbb2 100644 --- a/src/search_window.py +++ b/src/search_window.py @@ -2,7 +2,7 @@ ## src/search_window.py ## ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de> -## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> ## ## This file is part of Gajim. ## diff --git a/src/session.py b/src/session.py index 4b3c530bb..9d0834ce6 100644 --- a/src/session.py +++ b/src/session.py @@ -1,8 +1,8 @@ # -*- coding:utf-8 -*- ## src/session.py ## -## Copyright (C) 2008 Yann Leboulanger <asterix AT lagaule.org> -## Brendan Taylor <whateley AT gmail.com> +## Copyright (C) 2008-2010 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> ## Jonathan Schleifer <js-gajim AT webkeks.org> ## Stephan Erb <steve-e AT h3c.de> ## @@ -95,6 +95,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): self.resource = resource if self.control and self.control.resource: self.control.change_resource(self.resource) + seclabel = None + displaymarking = None if not msg_type or msg_type not in ('chat', 'groupchat', 'error'): msg_type = 'normal' @@ -113,7 +115,9 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): break composing_xep, chatstate = self.get_chatstate(msg, msgtxt) - + seclabel = msg.getTag('securitylabel') + if seclabel and seclabel.getNamespace() == common.xmpp.NS_SECLABEL: + displaymarking = seclabel.getTag('displaymarking') xhtml = msg.getXHTML() if msg_type == 'chat': @@ -236,15 +240,15 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if self.control: # print if a control is open self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml, - encrypted=encrypted) + encrypted=encrypted, displaymarking=displaymarking) else: # otherwise pass it off to the control to be queued groupchat_control.on_private_message(nickname, msgtxt, tim, - xhtml, self, msg_id=msg_id, encrypted=encrypted) + xhtml, self, msg_id=msg_id, encrypted=encrypted, displaymarking=displaymarking) else: self.roster_message(jid, msgtxt, tim, encrypted, msg_type, subject, resource, msg_id, user_nick, advanced_notif_num, - xhtml=xhtml, form_node=form_node) + xhtml=xhtml, form_node=form_node, displaymarking=displaymarking) nickname = gajim.get_name_from_jid(self.conn.name, jid) @@ -268,15 +272,14 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) - gajim.ged.raise_event('NewMessage', - (self.conn.name, [full_jid_with_resource, msgtxt, tim, - encrypted, msg_type, subject, chatstate, msg_id, - composing_xep, user_nick, xhtml, form_node])) - + gajim.ged.raise_event('NewMessage', + (self.conn.name, [full_jid_with_resource, msgtxt, tim, + encrypted, msg_type, subject, chatstate, msg_id, + composing_xep, user_nick, xhtml, form_node])) def roster_message(self, jid, msg, tim, encrypted=False, msg_type='', subject=None, resource='', msg_id=None, user_nick='', - advanced_notif_num=None, xhtml=None, form_node=None): + advanced_notif_num=None, xhtml=None, form_node=None, displaymarking=None): """ Display the message or show notification in the roster """ @@ -351,7 +354,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): typ = 'error' self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted, - subject=subject, xhtml=xhtml) + subject=subject, xhtml=xhtml, displaymarking=displaymarking) if msg_id: gajim.logger.set_read_messages([msg_id]) @@ -372,7 +375,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): contact) event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, - encrypted, resource, msg_id, xhtml, self, form_node), + encrypted, resource, msg_id, xhtml, self, form_node, displaymarking), show_in_roster=show_in_roster, show_in_systray=show_in_systray) gajim.events.add_event(self.conn.name, fjid, event) diff --git a/src/statusicon.py b/src/statusicon.py index f7a97a2f3..2cd3e61be 100644 --- a/src/statusicon.py +++ b/src/statusicon.py @@ -3,7 +3,7 @@ ## ## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> -## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net> ## Julien Pivotto <roidelapluie AT gmail.com> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> @@ -172,6 +172,7 @@ class StatusIcon: status_menuitem = self.xml.get_object('status_menu') join_gc_menuitem = self.xml.get_object('join_gc_menuitem') sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem') + show_roster_menuitem = self.xml.get_object('show_roster_menuitem') if self.single_message_handler_id: single_message_menuitem.handler_disconnect( @@ -316,6 +317,16 @@ class StatusIcon: sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on')) + win = gajim.interface.roster.window + if win.get_property('has-toplevel-focus'): + show_roster_menuitem.get_children()[0].set_label(_('Hide _Roster')) + show_roster_menuitem.connect('activate', + self.on_hide_roster_menuitem_activate) + else: + show_roster_menuitem.get_children()[0].set_label(_('Show _Roster')) + show_roster_menuitem.connect('activate', + self.on_show_roster_menuitem_activate) + if os.name == 'nt': if self.added_hide_menuitem is False: self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) @@ -342,6 +353,10 @@ class StatusIcon: win = gajim.interface.roster.window win.present() + def on_hide_roster_menuitem_activate(self, widget): + win = gajim.interface.roster.window + win.hide() + def on_preferences_menuitem_activate(self, widget): if 'preferences' in gajim.interface.instances: gajim.interface.instances['preferences'].window.present() @@ -362,9 +377,16 @@ class StatusIcon: # we could be in another VD right now. eg vd2 # and we want to show it in vd2 if not gtkgui_helpers.possibly_move_window_in_current_desktop(win): + x, y = win.get_position() + gajim.config.set('roster_x-position', x) + gajim.config.set('roster_y-position', y) win.hide() # else we hide it from VD that was visible in else: - win.show_all() + if not win.get_property('visible'): + win.show_all() + gtkgui_helpers.move_window(win, + gajim.config.get('roster_x-position'), + gajim.config.get('roster_y-position')) if not gajim.config.get('roster_window_skip_taskbar'): win.set_property('skip-taskbar-hint', False) win.present_with_time(gtk.get_current_event_time()) diff --git a/src/tooltips.py b/src/tooltips.py index 08f12b4c2..6d193abe7 100644 --- a/src/tooltips.py +++ b/src/tooltips.py @@ -5,7 +5,7 @@ ## Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> -## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com> ## Stefan Bethge <stefan AT lanpartei.de> ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> @@ -33,12 +33,15 @@ import gobject import os import time import locale +from datetime import datetime +from datetime import timedelta import gtkgui_helpers from common import gajim from common import helpers from common.pep import MOODS, ACTIVITIES +from common.i18n import Q_ class BaseTooltip: """ @@ -176,6 +179,44 @@ class BaseTooltip: self.cur_data = None self.check_last_time = None + @staticmethod + def colorize_status(status): + """ + Colorize the status message inside the tooltip by it's + semantics. Color palette is the Tango. + """ + formatted = "<span foreground='%s'>%s</span>" + if status.startswith(Q_("?user status:Available")): + status = formatted % ('#73D216', status) + elif status.startswith(_("Free for Chat")): + status = formatted % ('#3465A4', status) + elif status.startswith(_("Away")): + status = formatted % ('#EDD400', status) + elif status.startswith(_("Busy")): + status = formatted % ('#F57900', status) + elif status.startswith(_("Not Available")): + status = formatted % ('#CC0000', status) + elif status.startswith(_("Offline")): + status = formatted % ('#555753', status) + return status + + @staticmethod + def colorize_affiliation(affiliation): + """ + Color the affiliation of a MUC participant inside the tooltip by + it's semantics. Color palette is the Tango. + """ + formatted = "<span foreground='%s'>%s</span>" + if affiliation.startswith(Q_("?Group Chat Contact Affiliation:None")): + affiliation = formatted % ('#555753', affiliation) + elif affiliation.startswith(_("Member")): + affiliation = formatted % ('#73D216', affiliation) + elif affiliation.startswith(_("Administrator")): + affiliation = formatted % ('#F57900', affiliation) + elif affiliation.startswith(_("Owner")): + affiliation = formatted % ('#CC0000', affiliation) + return affiliation + class StatusTable: """ Contains methods for creating status table. This is used in Roster and @@ -342,23 +383,25 @@ class GCTooltip(BaseTooltip): status = '<i>' +\ gobject.markup_escape_text(status) + '</i>' properties.append((status, None)) - else: # no status message, show SHOW instead - show = helpers.get_uf_show(contact.show) - show = '<i>' + show + '</i>' - properties.append((show, None)) - if contact.jid.strip() != '': - properties.append((_('Jabber ID: '), contact.jid)) + show = helpers.get_uf_show(contact.show) + show = self.colorize_status(show) + properties.append((show, None)) - if hasattr(contact, 'resource') and contact.resource.strip() != '': + if contact.jid.strip(): + properties.append((_('Jabber ID: '), "<b>%s</b>" % contact.jid)) + + if hasattr(contact, 'resource') and contact.resource.strip(): properties.append((_('Resource: '), - gobject.markup_escape_text(contact.resource) )) + gobject.markup_escape_text(contact.resource))) + if contact.affiliation != 'none': uf_affiliation = helpers.get_uf_affiliation(contact.affiliation) - affiliation_str = \ + uf_affiliation =\ _('%(owner_or_admin_or_member)s of this group chat') %\ {'owner_or_admin_or_member': uf_affiliation} - properties.append((affiliation_str, None)) + uf_affiliation = self.colorize_affiliation(uf_affiliation) + properties.append((uf_affiliation, None)) # Add avatar puny_name = helpers.sanitize_filename(contact.name) @@ -542,8 +585,7 @@ class RosterTooltip(NotificationAreaTooltip): show = _('Connected') else: show = _('Disconnected') - show = '<i>' + show + '</i>' - # we append show below + show = self.colorize_status(show) if contact.status: status = contact.status.strip() @@ -559,7 +601,7 @@ class RosterTooltip(NotificationAreaTooltip): self._append_pep_info(contact, properties) - properties.append((_('Jabber ID: '), prim_contact.jid )) + properties.append((_('Jabber ID: '), "<b>%s</b>" % prim_contact.jid)) # contact has only one ressource if num_resources == 1 and contact.resource: @@ -584,21 +626,25 @@ class RosterTooltip(NotificationAreaTooltip): gobject.markup_escape_text(keyID))) if contact.last_activity_time: - text = _(' since %s') + last_active = datetime(*contact.last_activity_time[:6]) + current = datetime.now() - if time.strftime('%j', time.localtime())== \ - time.strftime('%j', contact.last_activity_time): - # it's today, show only the locale hour representation - local_time = time.strftime('%I:%M %p', - contact.last_activity_time) + diff = current - last_active + diff = timedelta(diff.days, diff.seconds) + + if last_active.date() == current.date(): + formatted = last_active.strftime("%X") else: - # time.strftime returns locale encoded string - local_time = time.strftime('%c', - contact.last_activity_time) - local_time = local_time.decode( - locale.getpreferredencoding()) - text = text % local_time - properties.append(('Idle' + text, None)) + formatted = last_active.strftime("%c") + + # Do not show the "Idle since" and "Idle for" items if there + # is no meaningful difference between last activity time and + # current time. + if diff.days > 0 or diff.seconds > 0: + cs = "<span foreground='#888A85'>%s</span>" + properties.append((str(), None)) + properties.append(((cs % _("Idle since %s")) % formatted, None)) + properties.append(((cs % _("Idle for %s")) % str(diff), None)) while properties: property_ = properties.pop(0) @@ -649,23 +695,19 @@ class RosterTooltip(NotificationAreaTooltip): """ if 'mood' in contact.pep: mood = contact.pep['mood'].asMarkupText() - mood_string = _('Mood:') + ' %s' % mood - properties.append((mood_string, None)) + properties.append((_("Mood: %s") % mood, None)) if 'activity' in contact.pep: activity = contact.pep['activity'].asMarkupText() - activity_string = _('Activity:') + ' %s' % activity - properties.append((activity_string, None)) + properties.append((_("Activity: %s") % activity, None)) if 'tune' in contact.pep: tune = contact.pep['tune'].asMarkupText() - tune_string = _('Tune:') + ' %s' % tune - properties.append((tune_string, None)) + properties.append((_("Tune: %s") % tune, None)) if 'location' in contact.pep: location = contact.pep['location'].asMarkupText() - location_string = _('Location:') + ' %s' % location - properties.append((location_string, None)) + properties.append((_("Location: %s") % location, None)) class FileTransfersTooltip(BaseTooltip): diff --git a/src/vcard.py b/src/vcard.py index 2f30f52a6..fa2f96936 100644 --- a/src/vcard.py +++ b/src/vcard.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- ## src/vcard.py ## -## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> +## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com> @@ -164,7 +164,7 @@ class VcardWindow: menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) menuitem.connect('activate', gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name()) + self.contact.jid, self.contact.get_shown_name()) menu.append(menuitem) menu.connect('selection-done', lambda w:w.destroy()) # show the menu @@ -328,7 +328,7 @@ class VcardWindow: subscription_label = self.xml.get_object('subscription_label') ask_label = self.xml.get_object('ask_label') if self.gc_contact: - self.xml.get_object('subscription_title_label').set_markup(_("<b>Role:</b>")) + self.xml.get_object('subscription_title_label').set_markup(Q_("?Role in Group Chat:<b>Role:</b>")) uf_role = helpers.get_uf_role(self.gc_contact.role) subscription_label.set_text(uf_role) @@ -479,7 +479,7 @@ class ZeroconfVcardWindow: menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) menuitem.connect('activate', gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name()) + self.contact.jid, self.contact.get_shown_name()) menu.append(menuitem) menu.connect('selection-done', lambda w:w.destroy()) # show the menu |