diff --git a/src/Makefile b/src/Makefile
index 6b40fc1df..7d5053885 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,21 +1,24 @@
# Set the C flags to include the GTK+ and Python libraries
PYTHON ?= python
PYTHONVER = `$(PYTHON) -c 'import sys; print sys.version[:3]'`
-CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fPIC -I/usr/include/python$(PYTHONVER) -I.
-LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -lpython$(PYTHONVER)
+gtk_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fPIC -I/usr/include/python$(PYTHONVER) -I.
+gtk_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -lpython$(PYTHONVER)
all: trayicon.so gtkspell.so
# Build the shared objects
trayicon.so: trayicon.o eggtrayicon.o trayiconmodule.o
- $(CC) -shared $^ -o $@ $(LDFLAGS)
+ $(CC) -shared $^ -o $@ $(LDFLAGS) $(gtk_LDFLAGS)
gtkspell.so:
- $(CC) $(OPTFLAGS) $(CFLAGS) `pkg-config --cflags gtkspell-2.0` -shared gtkspellmodule.c $^ -o $@ $(LDFLAGS) `pkg-config --libs gtkspell-2.0`
+ $(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_CFLAGS) $(gtk_LDFLAGS) `pkg-config --libs --cflags gtkspell-2.0` -shared gtkspellmodule.c $^ -o $@
# The path to the GTK+ python types
DEFS=`pkg-config --variable=defsdir pygtk-2.0`
+%.o: %.c
+ $(CC) -o $@ -c $< $(CFLAGS) $(gtk_CFLAGS)
+
# Generate the C wrapper from the defs and our override file
trayicon.c: trayicon.defs trayicon.override
pygtk-codegen-2.0 --prefix trayicon \
diff --git a/src/advanced.py b/src/advanced.py
index c58917bb5..76bf6ed4f 100644
--- a/src/advanced.py
+++ b/src/advanced.py
@@ -25,16 +25,9 @@
##
import gtk
-import gtk.glade
import gtkgui_helpers
from common import gajim
-from common import i18n
-
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
(
OPT_TYPE,
@@ -53,6 +46,8 @@ class AdvancedConfigurationWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade')
self.window = self.xml.get_widget('advanced_configuration_window')
+ self.window.set_transient_for(
+ gajim.interface.instances['preferences'].window)
self.entry = self.xml.get_widget('advanced_entry')
self.desc_label = self.xml.get_widget('advanced_desc_label')
self.restart_label = self.xml.get_widget('restart_label')
diff --git a/src/chat_control.py b/src/chat_control.py
index c1092fea8..b9cf730a7 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -18,13 +18,13 @@
import os
import time
import gtk
-import gtk.glade
import pango
import gobject
import gtkgui_helpers
import message_control
import dialogs
import history_window
+import notify
from common import gajim
from common import helpers
@@ -41,11 +41,14 @@ try:
except:
HAS_GTK_SPELL = False
-####################
-# FIXME: Can't this stuff happen once?
-from common import i18n
-_ = i18n._
-APP = i18n.APP
+
+# the next script, executed in the "po" directory,
+# generates the following list.
+##!/bin/sh
+#LANG=$(for i in *.po; do j=${i/.po/}; echo -n "_('"$j"')":" '"$j"', " ; done)
+#echo "{_('en'):'en'",$LANG"}"
+langs = {_('English'): 'en', _('Bulgarian'): 'bg', _('Briton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basc'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norvegian b'): 'nb', _('Dutch'): 'nl', _('Norvegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'}
+
################################################################################
class ChatControlBase(MessageControl):
@@ -56,7 +59,7 @@ class ChatControlBase(MessageControl):
theme = gajim.config.get('roster_theme')
bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
-
+
if bannerfont:
font = pango.FontDescription(bannerfont)
else:
@@ -67,16 +70,24 @@ class ChatControlBase(MessageControl):
font.set_weight(pango.WEIGHT_HEAVY)
if 'I' in bannerfontattrs:
font.set_style(pango.STYLE_ITALIC)
-
+
font_attrs = 'font_desc="%s"' % font.to_string()
-
+
# in case there is no font specified we use x-large font size
if font.get_size() == 0:
font_attrs = '%s size="x-large"' % font_attrs
font.set_weight(pango.WEIGHT_NORMAL)
font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
return (font_attrs, font_attrs_small)
-
+
+ def get_nb_unread(self):
+ jid = self.contact.jid
+ if self.resource:
+ jid += '/' + self.resource
+ type_ = self.type_id
+ return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
+ type_]))
+
def draw_banner(self):
self._paint_banner()
self._update_banner_state_image()
@@ -97,14 +108,15 @@ class ChatControlBase(MessageControl):
event_keymod):
pass # Derived should implement this rather than connecting to the event itself.
- def __init__(self, type_id, parent_win, widget_name, display_names, contact, acct, resource = None):
+ def __init__(self, type_id, parent_win, widget_name, display_names, contact,
+ acct, resource = None):
MessageControl.__init__(self, type_id, parent_win, widget_name,
display_names, contact, acct, resource = resource);
# when/if we do XHTML we will but formatting buttons back
widget = self.xml.get_widget('emoticons_button')
id = widget.connect('clicked', self.on_emoticons_button_clicked)
self.handlers[id] = widget
-
+
id = self.widget.connect('key_press_event', self._on_keypress_event)
self.handlers[id] = self.widget
@@ -112,10 +124,10 @@ class ChatControlBase(MessageControl):
id = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id] = widget
-
+
# Create textviews and connect signals
self.conv_textview = ConversationTextview(self.account)
-
+
self.conv_scrolledwindow = self.xml.get_widget(
'conversation_scrolledwindow')
self.conv_scrolledwindow.add(self.conv_textview.tv)
@@ -127,20 +139,23 @@ class ChatControlBase(MessageControl):
self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
self.msg_textview = MessageTextView()
id = self.msg_textview.connect('mykeypress',
- self._on_message_textview_mykeypress_event)
+ self._on_message_textview_mykeypress_event)
self.handlers[id] = self.msg_textview
self.msg_scrolledwindow.add(self.msg_textview)
id = self.msg_textview.connect('key_press_event',
- self._on_message_textview_key_press_event)
+ self._on_message_textview_key_press_event)
self.handlers[id] = self.msg_textview
id = self.msg_textview.connect('size-request', self.size_request)
self.handlers[id] = self.msg_textview
+ id = self.msg_textview.connect('populate_popup',
+ self.on_msg_textview_populate_popup)
+ self.handlers[id] = self.msg_textview
+
self.update_font()
# Hook up send button
widget = self.xml.get_widget('send_button')
- id = widget.connect('clicked',
- self._on_send_button_clicked)
+ id = widget.connect('clicked', self._on_send_button_clicked)
self.handlers[id] = widget
# the following vars are used to keep history of user's messages
@@ -149,8 +164,6 @@ class ChatControlBase(MessageControl):
self.typing_new = False
self.orig_msg = ''
- self.nb_unread = 0
-
# Emoticons menu
# set image no matter if user wants at this time emoticons or not
# (so toggle works ok)
@@ -162,8 +175,27 @@ class ChatControlBase(MessageControl):
# Attach speller
if gajim.config.get('use_speller') and HAS_GTK_SPELL:
try:
- gtkspell.Spell(self.msg_textview)
- except gobject.GError, msg:
+ spell = gtkspell.Spell(self.msg_textview)
+ # loop removing non-existant dictionaries
+ # iterating on a copy
+ for lang in dict(langs):
+ try:
+ spell.set_language(langs[lang])
+ except:
+ del langs[lang]
+ # now set the one the user selected
+ per_type = 'contacts'
+ if self.type_id == message_control.TYPE_GC:
+ per_type = 'rooms'
+ lang = gajim.config.get_per(per_type, self.contact.jid,
+ 'speller_language')
+ if not lang:
+ # use the default one
+ lang = gajim.config.get('speller_language')
+ if lang:
+ self.msg_textview.lang = lang
+ spell.set_language(lang)
+ except (gobject.GError, RuntimeError), msg:
#FIXME: add a ui for this use spell.set_language()
dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
'for which you want to highlight misspelled words, then please '
@@ -175,6 +207,48 @@ class ChatControlBase(MessageControl):
self.style_event_id = 0
self.conv_textview.tv.show()
+
+ # For JEP-0172
+ self.user_nick = None
+
+ def on_msg_textview_populate_popup(self, textview, menu):
+ '''we override the default context menu and we prepend an option to switch languages'''
+ def _on_select_dictionary(widget, lang):
+ per_type = 'contacts'
+ if self.type_id == message_control.TYPE_GC:
+ per_type = 'rooms'
+ if not gajim.config.get_per(per_type, self.contact.jid):
+ gajim.config.add_per(per_type, self.contact.jid)
+ gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
+ lang)
+ spell = gtkspell.get_from_text_view(self.msg_textview)
+ self.msg_textview.lang = lang
+ spell.set_language(lang)
+ widget.set_active(True)
+
+ item = gtk.SeparatorMenuItem()
+ menu.prepend(item)
+
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
+ item = gtk.MenuItem(_('Spelling language'))
+ menu.prepend(item)
+ submenu = gtk.Menu()
+ item.set_submenu(submenu)
+ for lang in sorted(langs):
+ item = gtk.CheckMenuItem(lang)
+ if langs[lang] == self.msg_textview.lang:
+ item.set_active(True)
+ submenu.append(item)
+ id = item.connect('activate', _on_select_dictionary, langs[lang])
+ self.handlers[id] = item
+
+ item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+ menu.prepend(item)
+ id = item.connect('activate', self.msg_textview.clear)
+ self.handlers[id] = item
+
+ menu.show_all()
+
# moved from ChatControl
def _on_banner_eventbox_button_press_event(self, widget, event):
'''If right-clicked, show popup'''
@@ -251,7 +325,7 @@ class ChatControlBase(MessageControl):
if event.state & gtk.gdk.CONTROL_MASK:
# CTRL + l|L: clear conv_textview
if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
- self.conv_textview.tv.get_buffer().set_text('')
+ self.conv_textview.clear()
return True
# CTRL + v: Paste into msg_textview
elif event.keyval == gtk.keysyms.v:
@@ -425,13 +499,18 @@ class ChatControlBase(MessageControl):
if not message or message == '\n':
return
+
if not self._process_command(message):
MessageControl.send_message(self, message, keyID, type = type,
chatstate = chatstate, msg_id = msg_id,
- composing_jep = composing_jep, resource = resource)
+ composing_jep = composing_jep, resource = resource,
+ user_nick = self.user_nick)
# Record message history
self.save_sent_message(message)
+ # Be sure to send user nickname only once according to JEP-0172
+ self.user_nick = None
+
# Clear msg input
message_buffer = self.msg_textview.get_buffer()
message_buffer.set_text('') # clear message buffer (and tv of course)
@@ -473,12 +552,25 @@ class ChatControlBase(MessageControl):
gajim.last_message_time[self.account][full_jid] = time.time()
urgent = True
if (not self.parent_win.get_active_jid() or \
- full_jid != self.parent_win.get_active_jid() or \
- not self.parent_win.is_active() or not end) and \
- kind in ('incoming', 'incoming_queue'):
- self.nb_unread += 1
- if gajim.interface.systray_enabled and self.notify_on_new_messages():
- gajim.interface.systray.add_jid(full_jid, self.account, self.type_id)
+ full_jid != self.parent_win.get_active_jid() or \
+ not self.parent_win.is_active() or not end) and \
+ kind in ('incoming', 'incoming_queue'):
+ if self.notify_on_new_messages():
+ type_ = 'printed_' + self.type_id
+ if self.type_id == message_control.TYPE_GC:
+ type_ = 'printed_gc_msg'
+ show_in_roster = notify.get_show_in_roster('message_received',
+ self.account, self.contact)
+ show_in_systray = notify.get_show_in_systray('message_received',
+ self.account, self.contact)
+ event = gajim.events.create_event(type_, None,
+ show_in_roster = show_in_roster,
+ show_in_systray = show_in_systray)
+ gajim.events.add_event(self.account, full_jid, event)
+ # We need to redraw contact if we show in roster
+ if show_in_roster:
+ gajim.interface.roster.draw_contact(self.contact.jid,
+ self.account)
self.parent_win.redraw_tab(self)
if not self.parent_win.is_active():
ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
@@ -503,6 +595,7 @@ class ChatControlBase(MessageControl):
else: # we are the beginning of buffer
buffer.insert_at_cursor('%s ' % str_)
self.msg_textview.grab_focus()
+
def on_emoticons_button_clicked(self, widget):
'''popup emoticons menu'''
gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
@@ -510,12 +603,10 @@ class ChatControlBase(MessageControl):
def on_actions_button_clicked(self, widget):
'''popup action menu'''
- #FIXME: BUG http://bugs.gnome.org/show_bug.cgi?id=316786
- self.button_clicked = widget
-
menu = self.prepare_context_menu()
menu.show_all()
- gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.parent_win)
+ gtkgui_helpers.popup_emoticons_under_button(menu, widget,
+ self.parent_win)
def update_font(self):
font = pango.FontDescription(gajim.config.get('conversation_font'))
@@ -549,15 +640,24 @@ class ChatControlBase(MessageControl):
if state:
jid = self.contact.jid
if self.conv_textview.at_the_end():
- #we are at the end
- if self.nb_unread > 0:
- self.nb_unread = self.get_specific_unread()
+ # we are at the end
+ type_ = 'printed_' + self.type_id
+ if self.type_id == message_control.TYPE_GC:
+ type_ = 'printed_gc_msg'
+ if not gajim.events.remove_events(self.account, self.get_full_jid(),
+ types = [type_]):
+ # There were events to remove
self.parent_win.redraw_tab(self)
self.parent_win.show_title()
- if gajim.interface.systray_enabled:
- gajim.interface.systray.remove_jid(self.get_full_jid(),
- self.account,
- self.type_id)
+ # redraw roster
+ if self.type_id == message_control.TYPE_PM:
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+ groupchat_control = gajim.interface.msg_win_mgr.get_control(
+ room_jid, self.account)
+ groupchat_control.draw_contact(nick)
+ else:
+ gajim.interface.roster.draw_contact(jid, self.account)
+ gajim.interface.roster.show_title()
self.msg_textview.grab_focus()
# Note, we send None chatstate to preserve current
self.parent_win.redraw_tab(self)
@@ -630,19 +730,28 @@ class ChatControlBase(MessageControl):
return True
def on_conversation_vadjustment_value_changed(self, widget):
- if not self.nb_unread:
+ if self.resource:
+ jid = self.contact.get_full_jid()
+ else:
+ jid = self.contact.jid
+ type_ = self.type_id
+ if type_ == message_control.TYPE_GC:
+ type_ = 'gc_msg'
+ if not len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
+ type_])):
return
- jid = self.contact.jid
if self.conv_textview.at_the_end() and \
self.parent_win.get_active_control() == self and \
self.parent_win.window.is_active():
- #we are at the end
- self.nb_unread = self.get_specific_unread()
- self.parent_win.redraw_tab(self)
- self.parent_win.show_title()
- if gajim.interface.systray_enabled:
- gajim.interface.systray.remove_jid(jid, self.account,
- self.type_id)
+ # we are at the end
+ type_ = self.type_id
+ if type_ == message_control.TYPE_GC:
+ type_ = 'gc_msg'
+ if not gajim.events.remove_events(self.account, self.get_full_jid(),
+ types = ['printed_' + type_, type_]):
+ # There were events to remove
+ self.parent_win.redraw_tab(self)
+ self.parent_win.show_title()
def sent_messages_scroll(self, direction, conv_buf):
size = len(self.sent_history)
@@ -701,6 +810,7 @@ class ChatControlBase(MessageControl):
def got_disconnected(self):
self.msg_textview.set_sensitive(False)
self.msg_textview.set_editable(False)
+ self.conv_textview.tv.grab_focus()
self.xml.get_widget('send_button').set_sensitive(False)
################################################################################
@@ -754,10 +864,12 @@ class ChatControl(ChatControlBase):
id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
self.handlers[id] = widget
- widget = self.xml.get_widget('avatar_eventbox')
id = widget.connect('leave-notify-event', self.on_avatar_eventbox_leave_notify_event)
self.handlers[id] = widget
+ id = widget.connect('button-press-event', self.on_avatar_eventbox_button_press_event)
+ self.handlers[id] = widget
+
widget = self.xml.get_widget('gpg_togglebutton')
id = widget.connect('clicked', self.on_toggle_gpg_togglebutton)
self.handlers[id] = widget
@@ -769,6 +881,8 @@ class ChatControl(ChatControlBase):
self.update_ui()
# restore previous conversation
self.restore_conversation()
+ # is account displayed after nick in banner ?
+ self.account_displayed= False
def notify_on_new_messages(self):
return gajim.config.get('trayicon_notification_on_new_messages')
@@ -803,6 +917,23 @@ class ChatControl(ChatControlBase):
if self.show_bigger_avatar_timeout_id is not None:
gobject.source_remove(self.show_bigger_avatar_timeout_id)
+ def on_avatar_eventbox_button_press_event(self, widget, event):
+ '''If right-clicked, show popup'''
+ if event.button == 3: # right click
+ 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.name + '.jpeg')
+ self.handlers[id] = menuitem
+ menu.append(menuitem)
+ menu.show_all()
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+ return True
+
def _on_window_motion_notify(self, widget, event):
'''it gets called no matter if it is the active window or not'''
if self.parent_win.get_active_jid() == self.contact.jid:
@@ -874,17 +1005,27 @@ class ChatControl(ChatControlBase):
if self.resource:
name += '/' + self.resource
avoid_showing_account_too = True
+ if self.TYPE_ID == message_control.TYPE_PM:
+ room_jid = self.contact.jid.split('/')[0]
+ room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid,
+ self.account)
+ name = _('%s from room %s') % (name, room_ctrl.name)
name = gtkgui_helpers.escape_for_pango_markup(name)
# We know our contacts nick, but if there are any other controls
# with the same nick we need to also display the account
# except if we are talking to two different resources of the same contact
acct_info = ''
+ self.account_displayed = False
for ctrl in self.parent_win.controls():
if ctrl == self:
continue
if self.contact.get_shown_name() == ctrl.contact.get_shown_name()\
and not avoid_showing_account_too:
+ self.account_displayed = True
+ if not ctrl.account_displayed:
+ # do that after this instance exists
+ gobject.idle_add(ctrl.draw_banner)
acct_info = ' (%s)' % \
gtkgui_helpers.escape_for_pango_markup(self.account)
break
@@ -901,9 +1042,9 @@ class ChatControl(ChatControlBase):
if cs and st in ('composing_only', 'all'):
if contact.show == 'offline':
chatstate = ''
- elif st == 'all' and contact.composing_jep == 'JEP-0085':
+ elif contact.composing_jep == 'JEP-0085':
chatstate = helpers.get_uf_chatstate(cs)
- elif st == 'composing_only' or contact.composing_jep == 'JEP-0022':
+ elif contact.composing_jep == 'JEP-0022':
if cs in ('composing', 'paused'):
# only print composing, paused
chatstate = helpers.get_uf_chatstate(cs)
@@ -911,16 +1052,16 @@ class ChatControl(ChatControlBase):
chatstate = ''
elif chatstate is None:
chatstate = helpers.get_uf_chatstate(cs)
-
+
label_text = '%s%s %s' % \
- (font_attrs, name, font_attrs_small, acct_info, chatstate)
+ (font_attrs, name, font_attrs_small, acct_info, chatstate)
else:
# weight="heavy" size="x-large"
label_text = '%s%s' % \
- (font_attrs, name, font_attrs_small, acct_info)
+ (font_attrs, name, font_attrs_small, acct_info)
if status_escaped:
label_text += '\n%s' %\
- (font_attrs_small, status_escaped)
+ (font_attrs_small, status_escaped)
banner_eventbox = self.xml.get_widget('banner_eventbox')
self.status_tooltip.set_tip(banner_eventbox, status)
self.status_tooltip.enable()
@@ -928,7 +1069,7 @@ class ChatControl(ChatControlBase):
self.status_tooltip.disable()
# setup the label that holds name and jid
banner_name_label.set_markup(label_text)
-
+
def on_toggle_gpg_togglebutton(self, widget):
gajim.config.set_per('contacts', self.contact.get_full_jid(),
'gpg_enabled', widget.get_active())
@@ -943,12 +1084,12 @@ class ChatControl(ChatControlBase):
tt = _('OpenPGP Encryption')
# restore gpg pref
- gpg_pref = gajim.config.get_per('contacts',
- self.contact.get_full_jid(), 'gpg_enabled')
+ gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
+ 'gpg_enabled')
if gpg_pref == None:
- gajim.config.add_per('contacts', self.contact.get_full_jid())
- gpg_pref = gajim.config.get_per('contacts',
- self.contact.get_full_jid(), 'gpg_enabled')
+ gajim.config.add_per('contacts', self.contact.jid)
+ gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
+ 'gpg_enabled')
tb.set_active(gpg_pref)
else:
@@ -1108,7 +1249,12 @@ class ChatControl(ChatControlBase):
def get_tab_label(self, chatstate):
unread = ''
- num_unread = self.nb_unread
+ if self.resource:
+ jid = self.contact.get_full_jid()
+ else:
+ jid = self.contact.jid
+ num_unread = len(gajim.events.get_events(self.account, jid,
+ ['printed_' + self.type_id, self.type_id]))
if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
unread = '*'
elif num_unread > 1:
@@ -1151,7 +1297,12 @@ class ChatControl(ChatControlBase):
return (label_str, color)
def get_tab_image(self):
- num_unread = self.nb_unread
+ if self.resource:
+ jid = self.contact.get_full_jid()
+ else:
+ jid = self.contact.jid
+ num_unread = len(gajim.events.get_events(self.account, jid,
+ ['printed_' + self.type_id, self.type_id]))
# Set tab image (always 16x16); unread messages show the 'message' image
tab_img = None
@@ -1160,8 +1311,8 @@ class ChatControl(ChatControlBase):
self.contact.jid, icon_name = 'message')
tab_img = img_16['message']
else:
- contact = gajim.contacts.get_contact_with_highest_priority(self.account,
- self.contact.jid)
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, self.contact.jid)
if not contact or self.resource:
# For transient contacts
contact = self.contact
@@ -1334,10 +1485,12 @@ class ChatControl(ChatControlBase):
# Disconnect timer callbacks
gobject.source_remove(self.possible_paused_timeout_id)
gobject.source_remove(self.possible_inactive_timeout_id)
- # Clean up systray
- if gajim.interface.systray_enabled and self.nb_unread > 0:
- gajim.interface.systray.remove_jid(self.contact.jid, self.account,
- self.type_id)
+ # Remove bigger avatar window
+ if self.bigger_avatar_window:
+ self.bigger_avatar_window.destroy()
+ # Clean events
+ gajim.events.remove_events(self.account, self.get_full_jid(),
+ types = ['printed_' + self.type_id, self.type_id])
# remove all register handlers on wigets, created by self.xml
# to prevent circular references among objects
for i in self.handlers.keys():
@@ -1354,7 +1507,7 @@ class ChatControl(ChatControlBase):
# 2 seconds
dialog = dialogs.ConfirmationDialog(
#%s is being replaced in the code with JID
- _('You just received a new message from "%s"' % self.contact.jid),
+ _('You just received a new message from "%s"') % self.contact.jid,
_('If you close this tab and you have history disabled, '\
'this message will be lost.'))
if dialog.get_response() != gtk.RESPONSE_OK:
@@ -1439,17 +1592,14 @@ class ChatControl(ChatControlBase):
if restore_how_many <= 0:
return
timeout = gajim.config.get('restore_timeout') # in minutes
- # number of messages that are in queue and are already logged
- pending_how_many = 0 # we want to avoid duplication
- if gajim.awaiting_events[self.account].has_key(jid):
- events = gajim.awaiting_events[self.account][jid]
- for event in events:
- if event[0] == 'chat':
- pending_how_many += 1
+ events = gajim.events.get_events(self.account, jid, ['chat'])
+ # number of messages that are in queue and are already logged, we want
+ # to avoid duplication
+ pending_how_many = len(events)
rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
- pending_how_many, timeout)
+ pending_how_many, timeout, self.account)
local_old_kind = None
for row in rows: # row[0] time, row[1] has kind, row[2] the message
if not row[2]: # message is empty, we don't print it
@@ -1465,10 +1615,14 @@ class ChatControl(ChatControlBase):
tim = time.localtime(float(row[0]))
+ if gajim.config.get('restored_messages_small'):
+ small_attr = ['small']
+ else:
+ small_attr = []
ChatControlBase.print_conversation_line(self, row[2], kind, name, tim,
- ['small'],
- ['small', 'restored_message'],
- ['small', 'restored_message'],
+ small_attr,
+ small_attr + ['restored_message'],
+ small_attr + ['restored_message'],
False, old_kind = local_old_kind)
if row[2].startswith('/me ') or row[2].startswith('/me\n'):
local_old_kind = None
@@ -1483,7 +1637,7 @@ class ChatControl(ChatControlBase):
jid_with_resource = jid
if self.resource:
jid_with_resource += '/' + self.resource
- l = gajim.awaiting_events[self.account][jid_with_resource]
+ events = gajim.events.get_events(self.account, jid_with_resource)
# Is it a pm ?
is_pm = False
@@ -1491,15 +1645,12 @@ class ChatControl(ChatControlBase):
control = gajim.interface.msg_win_mgr.get_control(room_jid, self.account)
if control and control.type_id == message_control.TYPE_GC:
is_pm = True
- events_to_keep = []
# list of message ids which should be marked as read
message_ids = []
- for event in l:
- typ = event[0]
- if typ != 'chat':
- events_to_keep.append(event)
+ for event in events:
+ if event.type_ != self.type_id:
continue
- data = event[1]
+ data = event.parameters
kind = data[2]
if kind == 'error':
kind = 'info'
@@ -1509,33 +1660,31 @@ class ChatControl(ChatControlBase):
encrypted = data[4], subject = data[1])
if len(data) > 6 and isinstance(data[6], int):
message_ids.append(data[6])
- # remove from gc nb_unread if it's pm or from roster
- if is_pm:
- control.nb_unread -= 1
- else:
- gajim.interface.roster.nb_unread -= 1
if message_ids:
gajim.logger.set_read_messages(message_ids)
- if is_pm:
- control.parent_win.show_title()
- else:
- gajim.interface.roster.show_title()
- # Keep only non-messages events
- if len(events_to_keep):
- gajim.awaiting_events[self.account][jid_with_resource] = events_to_keep
- else:
- del gajim.awaiting_events[self.account][jid_with_resource]
+ gajim.events.remove_events(self.account, jid_with_resource,
+ types = [self.type_id])
+
+ self.parent_win.show_title()
+ self.parent_win.redraw_tab(self)
+ # redraw roster
+ gajim.interface.roster.show_title()
+
typ = 'chat' # Is it a normal chat or a pm ?
# reset to status image in gc if it is a pm
if is_pm:
control.update_ui()
typ = 'pm'
- gajim.interface.roster.draw_contact(jid, self.account)
+ if is_pm:
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+ groupchat_control = gajim.interface.msg_win_mgr.get_control(
+ room_jid, self.account)
+ groupchat_control.draw_contact(nick)
+ else:
+ gajim.interface.roster.draw_contact(jid, self.account)
# Redraw parent too
gajim.interface.roster.draw_parent_contact(jid, self.account)
- if gajim.interface.systray_enabled:
- gajim.interface.systray.remove_jid(jid_with_resource, self.account, typ)
if (self.contact.show == 'offline' or self.contact.show == 'error'):
showOffline = gajim.config.get('showoffline')
if not showOffline and typ == 'chat' and \
@@ -1548,6 +1697,9 @@ class ChatControl(ChatControlBase):
def show_bigger_avatar(self, small_avatar):
'''resizes the avatar, if needed, so it has at max half the screen size
and shows it'''
+ if not small_avatar.window:
+ # Tab has been closed since we hovered the avatar
+ return
is_fake = False
if self.type_id == message_control.TYPE_PM:
is_fake = True
@@ -1562,7 +1714,7 @@ class ChatControl(ChatControlBase):
# It's why I set it transparent.
image = self.xml.get_widget('avatar_image')
pixbuf = image.get_pixbuf()
- pixbuf.fill(0xffffff00) # RGBA
+ pixbuf.fill(0xffffff00L) # RGBA
image.queue_draw()
screen_w = gtk.gdk.screen_width()
@@ -1612,6 +1764,7 @@ class ChatControl(ChatControlBase):
def _on_window_avatar_leave_notify_event(self, widget, event):
'''we just left the popup window that holds avatar'''
self.bigger_avatar_window.destroy()
+ self.bigger_avatar_window = None
# Re-show the small avatar
self.show_avatar()
diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py
index 28b9c2987..bb6753fca 100644
--- a/src/common/GnuPG.py
+++ b/src/common/GnuPG.py
@@ -138,7 +138,7 @@ else:
def verify(self, str, sign):
if not USE_GPG:
return str
- if not str:
+ if str == None:
return ''
f = tmpfile()
fd = f.fileno()
diff --git a/src/common/Makefile b/src/common/Makefile
index aa7ec66f7..c6d05a2d4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -6,19 +6,19 @@ HAVE_XSCRNSAVER = $(shell pkg-config --exists xscrnsaver && echo 'YES')
ifeq ($(HAVE_XSCRNSAVER),YES)
# We link with libXScrnsaver from modular X.Org X11
-CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0 xscrnsaver` -fpic -I/usr/include/python$(PYTHONVER) -I.
-LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0 xscrnsaver` -lpython$(PYTHONVER)
+gtk_and_x_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0 xscrnsaver` -fpic -I/usr/include/python$(PYTHONVER) -I.
+gtk_and_x_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0 xscrnsaver` -lpython$(PYTHONVER)
else
# # We link with libXScrnsaver from monolithic X.Org X11
-CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fpic -I/usr/include/python$(PYTHONVER) -I.
-LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -L/usr/X11R6$(LIBDIR) -lX11 \
- -lXss -lXext -lpython$(PYTHONVER)
+gtk_and_x_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fpic -I/usr/include/python$(PYTHONVER) -I.
+gtk_and_x_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` \
+ -L/usr/X11R6$(LIBDIR) -lX11 -lXss -lXext -lpython$(PYTHONVER)
endif
all: idle.so
idle.so:
- $(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) -shared idle.c $^ -o $@
+ $(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_and_x_CFLAGS) $(gtk_and_x_LDFLAGS) -shared idle.c $^ -o $@
clean:
rm -f *.so
diff --git a/src/common/check_paths.py b/src/common/check_paths.py
index 396db51da..03913473d 100644
--- a/src/common/check_paths.py
+++ b/src/common/check_paths.py
@@ -28,10 +28,6 @@ import stat
from common import gajim
import logger
-import i18n
-
-_ = i18n._
-Q_ = i18n.Q_
from pysqlite2 import dbapi2 as sqlite # DO NOT MOVE ABOVE OF import gajim
@@ -61,6 +57,11 @@ def create_log_db():
jid_id INTEGER
);
+ CREATE TABLE transports_cache (
+ transport TEXT UNIQUE,
+ type INTEGER
+ );
+
CREATE TABLE logs(
log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
jid_id INTEGER,
diff --git a/src/common/config.py b/src/common/config.py
index da7cc93f1..43f717404 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -20,8 +20,6 @@
import sre
import copy
-import i18n
-_ = i18n._
(
@@ -50,7 +48,7 @@ class Config:
'notify_on_signout': [ opt_bool, False ],
'notify_on_new_message': [ opt_bool, True ],
'autopopupaway': [ opt_bool, False ],
- 'use_notif_daemon': [ opt_bool, True , _('Use DBus and Notification-Daemon to show notifications') ],
+ 'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ],
'ignore_unknown_contacts': [ opt_bool, False ],
'showoffline': [ opt_bool, False, '', True ],
'autoaway': [ opt_bool, True ],
@@ -76,17 +74,19 @@ class Config:
'statusmsgcolor': [ opt_color, '#1eaa1e', '', True ],
'markedmsgcolor': [ opt_color, '#ff8080', '', True ],
'urlmsgcolor': [ opt_color, '#0000ff', '', True ],
- 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed'), True ],
+ 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
'roster_theme': [ opt_str, 'gtk+', '', True ],
'saveposition': [ opt_bool, True ],
'mergeaccounts': [ opt_bool, False, '', True ],
'sort_by_show': [ opt_bool, True, '', True ],
'use_speller': [ opt_bool, False, ],
+ 'speller_language': [ opt_str, '', _('Language used by speller')],
'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
+ 'print_time_fuzzy': [ opt_int, 0, _('Value of fuzziness from 1 to 4 or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the less precise one.') ],
'emoticons_theme': [opt_str, 'static', '', True ],
'ascii_formatting': [ opt_bool, True,
_('Treat * / _ pairs as possible formatting characters.'), True],
- 'show_ascii_formatting_chars': [ opt_bool, False , _('If True, do not '
+ 'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not '
'remove */_ . So *abc* will be bold but with * * not removed.')],
'sounds_on': [ opt_bool, True ],
# 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
@@ -95,12 +95,12 @@ class Config:
'custombrowser': [ opt_str, 'firefox' ],
'custommailapp': [ opt_str, 'mozilla-thunderbird -compose' ],
'custom_file_manager': [ opt_str, 'xffm' ],
- 'gc-hpaned-position': [opt_int, 540],
- 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat')],
- 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat')],
+ 'gc-hpaned-position': [opt_int, 430],
+ 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')],
+ 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')],
'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-width': [opt_int, 480],
+ 'msgwin-width': [opt_int, 500],
'msgwin-height': [opt_int, 440],
'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
@@ -108,7 +108,7 @@ class Config:
'chat-msgwin-height': [opt_int, 440],
'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-width': [opt_int, 480],
+ 'gc-msgwin-width': [opt_int, 600],
'gc-msgwin-height': [opt_int, 440],
'single-msg-x-position': [opt_int, 0],
'single-msg-y-position': [opt_int, 0],
@@ -126,6 +126,7 @@ class Config:
'after_nickname': [ opt_str, ':' ],
'send_os_info': [ opt_bool, True ],
'notify_on_new_gmail_email': [ opt_bool, True ],
+ 'notify_on_new_gmail_email_extra': [ opt_bool, False ],
'usegpg': [ opt_bool, False, '', True ],
'use_gpg_agent': [ opt_bool, False ],
'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
@@ -134,7 +135,7 @@ class Config:
'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
'show_roster_on_startup': [opt_bool, True],
'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
- 'version': [ opt_str, '0.10.0.1' ], # which version created the config
+ 'version': [ opt_str, '0.10.1.3' ], # which version created the config
'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'],
'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
'always_english_wikipedia': [opt_bool, False],
@@ -142,7 +143,7 @@ class Config:
'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
'chat_state_notifications': [opt_str, 'all'], # 'all', 'composing_only', 'disabled'
'autodetect_browser_mailer': [opt_bool, False, '', True],
- 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes')],
+ 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of room jids.')],
'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of room jids.')],
@@ -177,29 +178,33 @@ class Config:
'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')],
'set_xmpp://_handler_everytime': [opt_bool, False, _('If True, Gajim registers for xmpp:// on each startup.')],
'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
- 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window'), True],
+ 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
'show_avatars_in_roster': [opt_bool, True, '', True],
'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
- 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", gajim will only print FOO enters/leaves room')],
+ 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", gajim will only print FOO enters/leaves room.')],
'log_contact_status_changes': [opt_bool, False],
'restored_messages_color': [opt_str, 'grey'],
+ 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')],
'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')],
'roster_window_skip_taskbar': [opt_bool, False],
'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')],
'notification_timeout': [opt_int, 5],
- 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected room. Turn this option to False to stop sending sha info in groupchat presences')],
+ 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected room. Turn this option to False to stop sending sha info in group chat presences.')],
'one_message_window': [opt_str, 'always',
- _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window. Note, changing this option requires restarting Gajim before the changes will take effect')],
- 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window')],
- 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window')],
- 'always_hide_groupchat_buttons': [opt_bool, False, _('Hides the buttons in group chat window')],
- 'always_hide_chat_buttons': [opt_bool, False, _('Hides the buttons in two persons chat window')],
+#always, never, peracct, pertype should not be translated
+ _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window. Note, changing this option requires restarting Gajim before the changes will take effect.')],
+ 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
+ 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
+ 'always_hide_groupchat_buttons': [opt_bool, False, _('Hides the buttons in group chat window.')],
+ 'always_hide_chat_buttons': [opt_bool, False, _('Hides the buttons in two persons chat window.')],
'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
- 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the room occupants list in groupchat window')],
- 'chat_merge_consecutive_nickname': [opt_bool, False, _('Merge consecutive nickname in chat window')],
- 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickame')],
+ 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the room occupants list in group chat window.')],
+ 'chat_merge_consecutive_nickname': [opt_bool, False, _('Merge consecutive nickname in chat window.')],
+ 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickame.')],
+ 'gc_nicknames_colors': [ opt_str, '#a34526:#c000ff:#0012ff:#388a99:#38995d:#519938:#ff8a00:#94452d:#244b5a:#32645a', _('List of colors that will be used to color nicknames in group chats.'), True ],
+ 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
'zeroconf_enabled': [opt_bool, True, _('Enable zeroconf network')],
}
@@ -246,6 +251,10 @@ class Config:
'statusmsg': ({
'message': [ opt_str, '' ],
}, {}),
+ 'defaultstatusmsg': ({
+ 'enabled': [ opt_bool, False ],
+ 'message': [ opt_str, '' ],
+ }, {}),
'soundevents': ({
'enabled': [ opt_bool, True ],
'path': [ opt_str, '' ],
@@ -288,7 +297,27 @@ class Config:
'state_muc_directed_msg_color': [ opt_color, 'red2' ],
}, {}),
'contacts': ({
- 'gpg_enabled': [ opt_bool, True ],
+ 'gpg_enabled': [ opt_bool, True, _('Is OpenPGP enabled for this contact?')],
+ 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
+ }, {}),
+ 'rooms': ({
+ 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
+ }, {}),
+ 'notifications': ({
+ 'event': [opt_str, ''],
+ 'recipient_type': [opt_str, 'all'],
+ 'recipients': [opt_str, ''],
+ 'status': [opt_str, 'all', _('all or space separated status')],
+ 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")],
+ 'sound': [opt_str, '', _("'yes', 'no' or ''")],
+ 'sound_file': [opt_str, ''],
+ 'popup': [opt_str, '', _("'yes', 'no' or ''")],
+ 'auto_open': [opt_str, '', _("'yes', 'no' or ''")],
+ 'run_command': [opt_bool, False],
+ 'command': [opt_str, ''],
+ 'systray': [opt_str, '', _("'yes', 'no' or ''")],
+ 'roster': [opt_str, '', _("'yes', 'no' or ''")],
+ 'urgency_hint': [opt_bool, False],
}, {}),
}
@@ -302,6 +331,16 @@ class Config:
_('Out'): _("I'm out enjoying life"),
}
+ defaultstatusmsg_default = {
+ 'online': [ False, _("I'm available") ],
+ 'chat': [ False, _("I'm free for chat") ],
+ 'away': [ False, _('Be right back') ],
+ 'xa': [ False, _("I'm not available") ],
+ 'dnd': [ False, _('Do not disturb') ],
+ 'invisible': [ False, _('Bye!') ],
+ 'offline': [ False, _('Bye!') ],
+ }
+
soundevents_default = {
'first_message_received': [ True, '../data/sounds/message1.wav' ],
'next_message_received': [ True, '../data/sounds/message2.wav' ],
@@ -515,3 +554,8 @@ class Config:
self.set_per('soundevents', event, 'enabled', default[0])
self.set_per('soundevents', event, 'path', default[1])
+ for status in self.defaultstatusmsg_default:
+ default = self.defaultstatusmsg_default[status]
+ self.add_per('defaultstatusmsg', status)
+ self.set_per('defaultstatusmsg', status, 'enabled', default[0])
+ self.set_per('defaultstatusmsg', status, 'message', default[1])
diff --git a/src/common/connection.py b/src/common/connection.py
index a361b1b59..96abbc3c8 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -42,9 +42,6 @@ from common import GnuPG
from connection_handlers import *
USE_GPG = GnuPG.USE_GPG
-from common import i18n
-_ = i18n._
-
class Connection(ConnectionHandlers):
'''Connection class'''
def __init__(self, name):
@@ -88,8 +85,12 @@ class Connection(ConnectionHandlers):
self.on_connect_failure = None
self.retrycount = 0
self.jids_for_auto_auth = [] # list of jid to auto-authorize
-
+ self.muc_jid = {} # jid of muc server for each transport type
+ self.available_transports = {} # list of available transports on this
+ # server {'icq': ['icq.server.com', 'icq2.server.com'], }
+ self.vcard_supported = True
# END __init__
+
def put_event(self, ev):
if gajim.handlers.has_key(ev[0]):
gajim.handlers[ev[0]](self.name, ev[1])
@@ -137,22 +138,21 @@ class Connection(ConnectionHandlers):
if not self.on_purpose:
self.disconnect()
if gajim.config.get_per('accounts', self.name, 'autoreconnect') \
- and self.retrycount <= 10:
+ and self.retrycount <= 10:
self.connected = 1
self.dispatch('STATUS', 'connecting')
- self.time_to_reconnect = 10
# this check has moved from _reconnect method
if self.retrycount > 5:
self.time_to_reconnect = 20
else:
self.time_to_reconnect = 10
- gajim.idlequeue.set_alarm(self._reconnect_alarm,
- self.time_to_reconnect)
+ gajim.idlequeue.set_alarm(self._reconnect_alarm,
+ self.time_to_reconnect)
elif self.on_connect_failure:
self.on_connect_failure()
self.on_connect_failure = None
else:
- # show error dialog
+ # show error dialog
self._connection_lost()
else:
self.disconnect()
@@ -162,9 +162,9 @@ class Connection(ConnectionHandlers):
def _connection_lost(self):
self.disconnect(on_purpose = False)
self.dispatch('STATUS', 'offline')
- self.dispatch('ERROR',
- (_('Connection with account "%s" has been lost') % self.name,
- _('To continue sending and receiving messages, you will need to reconnect.')))
+ self.dispatch('CONNECTION_LOST',
+ (_('Connection with account "%s" has been lost') % self.name,
+ _('To continue sending and receiving messages, you will need to reconnect.')))
def _event_dispatcher(self, realm, event, data):
if realm == common.xmpp.NS_REGISTER:
@@ -211,6 +211,34 @@ class Connection(ConnectionHandlers):
else:
conf = data[1].asDict()
self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
+ elif realm == common.xmpp.NS_PRIVACY:
+ if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
+ # data is (list)
+ self.dispatch('PRIVACY_LISTS_RECEIVED', (data))
+ elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
+ # data is (resp)
+ if not data:
+ return
+ rules = []
+ name = data.getTag('query').getTag('list').getAttr('name')
+ for child in data.getTag('query').getTag('list').getChildren():
+ dict_item = child.getAttrs()
+ childs = []
+ if dict_item.has_key('type'):
+ for scnd_child in child.getChildren():
+ childs += [scnd_child.getName()]
+ rules.append({'action':dict_item['action'],
+ 'type':dict_item['type'], 'order':dict_item['order'],
+ 'value':dict_item['value'], 'child':childs})
+ else:
+ for scnd_child in child.getChildren():
+ childs.append(scnd_child.getName())
+ rules.append({'action':dict_item['action'],
+ 'order':dict_item['order'], 'child':childs})
+ self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules))
+ elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
+ # data is (dict)
+ self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
elif realm == '':
if event == common.xmpp.transports.DATA_RECEIVED:
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
@@ -360,7 +388,8 @@ class Connection(ConnectionHandlers):
if not self.retrycount and self.connected != 0:
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
- self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later.')))
def _connect_success(self, con, con_type):
@@ -396,7 +425,8 @@ class Connection(ConnectionHandlers):
if not con:
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
- self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later')))
if self.on_connect_auth:
self.on_connect_auth(None)
@@ -433,6 +463,41 @@ class Connection(ConnectionHandlers):
if kill_core and self.connected > 1:
self.disconnect(on_purpose = True)
+ def get_privacy_lists(self):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.getPrivacyLists(self.connection)
+
+ def get_active_default_lists(self):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
+
+ def del_privacy_list(self, privacy_list):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list)
+
+ def get_privacy_list(self, title):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.getPrivacyList(self.connection, title)
+
+ def set_privacy_list(self, listname, tags):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
+
+ def set_active_list(self, listname):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
+
+ def set_default_list(self, listname):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
+
def build_privacy_rule(self, name, action):
'''Build a Privacy rule stanza for invisibility'''
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
@@ -442,6 +507,8 @@ class Connection(ConnectionHandlers):
return iq
def activate_privacy_rule(self, name):
+ if not self.connection:
+ return
'''activate a privacy rule'''
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
iq.getTag('query').setTag('active', {'name': name})
@@ -528,7 +595,7 @@ class Connection(ConnectionHandlers):
# Ask metacontacts before roster
self.get_metacontacts()
- def change_status(self, show, msg, sync = False, auto = False):
+ def change_status(self, show, msg, auto = False):
if not show in STATUS_LIST:
return -1
sshow = helpers.get_xmpp_show(show)
@@ -607,7 +674,8 @@ class Connection(ConnectionHandlers):
self.connection.send(msg_iq)
def send_message(self, jid, msg, keyID, type = 'chat', subject='',
- chatstate = None, msg_id = None, composing_jep = None, resource = None):
+ chatstate = None, msg_id = None, composing_jep = None, resource = None,
+ user_nick = None):
if not self.connection:
return
if not msg and chatstate is None:
@@ -638,6 +706,11 @@ class Connection(ConnectionHandlers):
if msgenc:
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
+ # JEP-0172: user_nickname
+ if user_nick:
+ msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
+ user_nick)
+
# chatstates - if peer supports jep85 or jep22, send chatstates
# please note that the only valid tag inside a message containing a
# tag is the active event
@@ -692,7 +765,7 @@ class Connection(ConnectionHandlers):
self.connection.send(p)
def request_subscription(self, jid, msg = '', name = '', groups = [],
- auto_auth = False):
+ auto_auth = False, user_nick = ''):
if not self.connection:
return
gajim.log.debug('subscription request for %s' % jid)
@@ -710,10 +783,11 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
p = common.xmpp.Presence(jid, 'subscribe')
+ if user_nick:
+ p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick)
p = self.add_sha(p)
- if not msg:
- msg = _('I would like to add you to my roster.')
- p.setStatus(msg)
+ if msg:
+ p.setStatus(msg)
self.connection.send(p)
def send_authorization(self, jid):
@@ -796,6 +870,10 @@ class Connection(ConnectionHandlers):
def request_os_info(self, jid, resource):
if not self.connection:
return
+ # If we are invisible, do not request
+ if self.connected == gajim.SHOW_LIST.index('invisible'):
+ self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
+ return
to_whom_jid = jid
if resource:
to_whom_jid += '/' + resource
@@ -852,6 +930,9 @@ class Connection(ConnectionHandlers):
iq = common.xmpp.Iq(typ='get')
iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
iq2.addChild(name='storage', namespace='storage:metacontacts')
+ id = self.connection.getAnID()
+ iq.setID(id)
+ self.awaiting_answers[id] = (METACONTACTS_ARRIVED, )
self.connection.send(iq)
def store_metacontacts(self, tags_list):
@@ -873,7 +954,8 @@ class Connection(ConnectionHandlers):
def send_agent_status(self, agent, ptype):
if not self.connection:
return
- p = common.xmpp.Presence(to = agent, typ = ptype)
+ show = helpers.get_xmpp_show(STATUS_LIST[self.connected])
+ p = common.xmpp.Presence(to = agent, typ = ptype, show = show)
p = self.add_sha(p, ptype != 'unavailable')
self.connection.send(p)
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 082148b4a..dc15c9b8e 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -24,6 +24,7 @@ import sha
import socket
import sys
+from time import localtime, strftime, gmtime
from calendar import timegm
import socks5
@@ -32,8 +33,6 @@ import common.xmpp
from common import GnuPG
from common import helpers
from common import gajim
-from common import i18n
-_ = i18n._
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
@@ -41,6 +40,7 @@ STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
VCARD_PUBLISHED = 'vcard_published'
VCARD_ARRIVED = 'vcard_arrived'
AGENT_REMOVED = 'agent_removed'
+METACONTACTS_ARRIVED = 'metacontacts_arrived'
HAS_IDLE = True
try:
import common.idle as idle # when we launch gajim from sources
@@ -90,7 +90,7 @@ class ConnectionBytestream:
if contact.jid == receiver_jid:
file_props['error'] = -5
self.remove_transfer(file_props)
- self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
+ self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
sender_jid = unicode(file_props['sender']).split('/')[0]
if contact.jid == sender_jid:
file_props['error'] = -3
@@ -169,12 +169,18 @@ class ConnectionBytestream:
file_props['sha_str'] = sha_str
if not ft_override_host_to_send:
ft_override_host_to_send = self.peerhost[0]
- ft_override_host_to_send = socket.gethostbyname(ft_override_host_to_send)
+ try:
+ ft_override_host_to_send = socket.gethostbyname(
+ ft_override_host_to_send)
+ except socket.gaierror:
+ self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.')))
+ ft_override_host_to_send = self.peerhost[0]
listener = gajim.socks5queue.start_listener(self.peerhost[0], port,
sha_str, self._result_socks5_sid, file_props['sid'])
if listener == None:
file_props['error'] = -5
- self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props))
+ self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props,
+ ''))
self._connect_error(unicode(receiver), file_props['sid'],
file_props['sid'], code = 406)
return
@@ -216,8 +222,8 @@ class ConnectionBytestream:
iq = common.xmpp.Protocol(name = 'iq',
to = unicode(file_props['sender']), typ = 'error')
iq.setAttr('id', file_props['request-id'])
- err = common.xmpp.ErrorNode(code = '406', typ = 'auth', name =
- 'not-acceptable')
+ err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name =
+ 'forbidden', text = 'Offer Declined')
iq.addChild(node=err)
self.connection.send(iq)
@@ -309,8 +315,8 @@ class ConnectionBytestream:
if file_props is not None:
self.disconnect_transfer(file_props)
file_props['error'] = -3
- self.dispatch('FILE_REQUEST_ERROR', (to, file_props))
-
+ self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
+
def _proxy_auth_ok(self, proxy):
'''cb, called after authentication to proxy server '''
file_props = self.files_props[proxy['sid']]
@@ -339,7 +345,7 @@ class ConnectionBytestream:
return
file_props = self.files_props[id]
file_props['error'] = -4
- self.dispatch('FILE_REQUEST_ERROR', (jid, file_props))
+ self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise common.xmpp.NodeProcessed
def _bytestreamSetCB(self, con, iq_obj):
@@ -556,7 +562,7 @@ class ConnectionBytestream:
return
jid = helpers.get_jid_from_iq(iq_obj)
file_props['error'] = -3
- self.dispatch('FILE_REQUEST_ERROR', (jid, file_props))
+ self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise common.xmpp.NodeProcessed
class ConnectionDisco:
@@ -580,8 +586,8 @@ class ConnectionDisco:
iq.setID(id)
# Wait the answer during 30 secondes
self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id,
- _('Registration information for transport %s has not arrived in time' % \
- agent))
+ _('Registration information for transport %s has not arrived in time') % \
+ agent)
self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo,
{'agent': agent})
@@ -726,17 +732,28 @@ class ConnectionDisco:
qc = iq_obj.getQueryChildren()
if not qc:
qc = []
+ is_muc = False
+ transport_type = ''
for i in qc:
if i.getName() == 'identity':
attr = {}
for key in i.getAttrs().keys():
attr[key] = i.getAttr(key)
+ if attr.has_key('category') and attr['category'] in ('gateway', 'headline')\
+ and attr.has_key('type'):
+ transport_type = attr['type']
+ if attr.has_key('category') and attr['category'] == 'conference' \
+ and attr.has_key('type') and attr['type'] == 'text':
+ is_muc = True
identities.append(attr)
elif i.getName() == 'feature':
features.append(i.getAttr('var'))
- elif i.getName() == 'x' and i.getAttr('xmlns') == common.xmpp.NS_DATA:
+ elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA:
data.append(common.xmpp.DataForm(node=i))
jid = helpers.get_full_jid_from_iq(iq_obj)
+ if transport_type and jid not in gajim.transport_type:
+ gajim.transport_type[jid] = transport_type
+ gajim.logger.save_transport_type(jid, transport_type)
id = iq_obj.getID()
if not identities: # ejabberd doesn't send identities when we browse online users
#FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
@@ -744,6 +761,14 @@ class ConnectionDisco:
if id[0] == 'p':
if features.__contains__(common.xmpp.NS_BYTESTREAM):
gajim.proxy65_manager.resolve(jid, self.connection, self.name)
+ if features.__contains__(common.xmpp.NS_MUC) and is_muc:
+ type_ = transport_type or 'jabber'
+ self.muc_jid[type_] = jid
+ if transport_type:
+ if self.available_transports.has_key(transport_type):
+ self.available_transports[transport_type].append(jid)
+ else:
+ self.available_transports[transport_type] = [jid]
self.dispatch('AGENT_INFO_INFO', (jid, node, identities,
features, data))
@@ -851,7 +876,10 @@ class ConnectionVcard:
id = self.connection.getAnID()
iq.setID(id)
- self.awaiting_answers[id] = (VCARD_ARRIVED, jid)
+ j = jid
+ if not j:
+ j = gajim.get_jid_from_account(self.name)
+ self.awaiting_answers[id] = (VCARD_ARRIVED, j)
if is_fake_jid:
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
if not room_jid in self.room_jids:
@@ -939,9 +967,12 @@ class ConnectionVcard:
elif self.awaiting_answers[id][0] == VCARD_ARRIVED:
# If vcard is empty, we send to the interface an empty vcard so that
# it knows it arrived
- if not iq_obj.getTag('vCard'):
- jid = self.awaiting_answers[id][1]
- our_jid = gajim.get_jid_from_account(self.name)
+ jid = self.awaiting_answers[id][1]
+ our_jid = gajim.get_jid_from_account(self.name)
+ if iq_obj.getType() == 'error' and jid == our_jid:
+ # our server doesn't support vcard
+ self.vcard_supported = False
+ if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error':
if jid and jid != our_jid:
# Write an empty file
self.save_vcard_to_hd(jid, '')
@@ -951,6 +982,29 @@ class ConnectionVcard:
elif self.awaiting_answers[id][0] == AGENT_REMOVED:
jid = self.awaiting_answers[id][1]
self.dispatch('AGENT_REMOVED', jid)
+ elif self.awaiting_answers[id][0] == METACONTACTS_ARRIVED:
+ if iq_obj.getType() == 'result':
+ # Metacontact tags
+ # http://www.jabber.org/jeps/jep-XXXX.html
+ meta_list = {}
+ query = iq_obj.getTag('query')
+ storage = query.getTag('storage')
+ metas = storage.getTags('meta')
+ for meta in metas:
+ jid = meta.getAttr('jid')
+ tag = meta.getAttr('tag')
+ data = {'jid': jid}
+ order = meta.getAttr('order')
+ if order != None:
+ data['order'] = order
+ if meta_list.has_key(tag):
+ meta_list[tag].append(data)
+ else:
+ meta_list[tag] = [data]
+ self.dispatch('METACONTACTS', meta_list)
+ # We can now continue connection by requesting the roster
+ self.connection.initRoster()
+
del self.awaiting_answers[id]
def _vCardCB(self, con, vc):
@@ -1051,6 +1105,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
# keep the jids we auto added (transports contacts) to not send the
# SUBSCRIBED event to gui
self.automatically_added = []
+ # keep the latest subscribed event for each jid to prevent loop when we
+ # acknoledge presences
+ self.subscribed_events = {}
try:
idle.init()
except:
@@ -1077,7 +1134,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
raise common.xmpp.NodeProcessed
def _ErrorCB(self, con, iq_obj):
- errmsg = iq_obj.getError()
+ errmsg = iq_obj.getErrorMsg()
errcode = iq_obj.getErrorCode()
jid_from = helpers.get_full_jid_from_iq(iq_obj)
id = unicode(iq_obj.getID())
@@ -1113,26 +1170,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
self.bookmarks.append(bm)
self.dispatch('BOOKMARKS', self.bookmarks)
- elif ns == 'storage:metacontacts':
- # Metacontact tags
- # http://www.jabber.org/jeps/jep-XXXX.html
- meta_list = {}
- metas = storage.getTags('meta')
- for meta in metas:
- jid = meta.getAttr('jid')
- tag = meta.getAttr('tag')
- data = {'jid': jid}
- order = meta.getAttr('order')
- if order != None:
- data['order'] = order
- if meta_list.has_key(tag):
- meta_list[tag].append(data)
- else:
- meta_list[tag] = [data]
- self.dispatch('METACONTACTS', meta_list)
- # We can now continue connection by requesting the roster
- self.connection.initRoster()
-
elif ns == 'gajim:prefs':
# Preferences data
# http://www.jabber.org/jeps/jep-0049.html
@@ -1215,6 +1252,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info))
+ def _TimeCB(self, con, iq_obj):
+ gajim.log.debug('TimeCB')
+ iq_obj = iq_obj.buildReply('result')
+ qp = iq_obj.getTag('query')
+ qp.setTagData('utc', strftime("%Y%m%dT%T", gmtime()))
+ qp.setTagData('tz', strftime("%Z", gmtime()))
+ qp.setTagData('display', strftime("%c", localtime()))
+ self.connection.send(iq_obj)
+ raise common.xmpp.NodeProcessed
+
def _gMailNewMailCB(self, con, gm):
'''Called when we get notified of new mail messages in gmail account'''
@@ -1239,9 +1286,20 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
newmsgs = gm.getTag('mailbox').getAttr('total-matched')
if newmsgs != '0':
# there are new messages
+ gmail_messages_list = []
+ if gm.getTag('mailbox').getTag('mail-thread-info'):
+ gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
+ for gmessage in gmail_messages:
+ gmail_from = gmessage.getTag('senders').getTag('sender').getAttr('address')
+ gmail_subject = gmessage.getTag('subject').getData()
+ gmail_snippet = gmessage.getTag('snippet').getData()
+ gmail_messages_list.append({ \
+ 'From': gmail_from, \
+ 'Subject': gmail_subject, \
+ 'Snippet': gmail_snippet})
jid = gajim.get_jid_from_account(self.name)
gajim.log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid))
- self.dispatch('GMAIL_NOTIFY', (jid, newmsgs))
+ self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list))
raise common.xmpp.NodeProcessed
def _messageCB(self, con, msg):
@@ -1295,7 +1353,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
composing_jep = 'JEP-0022'
if not msgtxt and chatstate_child.getTag('composing'):
chatstate = 'composing'
-
+ # JEP-0172 User Nickname
+ user_nick = msg.getTagData('nick')
+ if not user_nick:
+ user_nick = ''
+
if encTag and GnuPG.USE_GPG:
#decrypt
encmsg = encTag.getData()
@@ -1325,9 +1387,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
# Ignore message from room in which we are not
if not self.last_history_line.has_key(jid):
return
- self.dispatch('GC_MSG', (frm, msgtxt, tim))
- if self.name not in no_log_for and jid in self.last_history_line \
- and not int(float(time.mktime(tim))) <= \
+ has_timestamp = False
+ if msg.timestamp:
+ has_timestamp = True
+ self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp))
+ if self.name not in no_log_for and not int(float(time.mktime(tim))) <= \
self.last_history_line[jid] and msgtxt:
gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
elif mtype == 'chat': # it's type 'chat'
@@ -1338,7 +1402,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
subject = subject)
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
- chatstate, msg_id, composing_jep))
+ chatstate, msg_id, composing_jep, user_nick))
else: # it's single message
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
@@ -1352,7 +1416,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
else:
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
- subject, chatstate, msg_id, composing_jep))
+ subject, chatstate, msg_id, composing_jep, user_nick))
# END messageCB
def _presenceCB(self, con, prs):
@@ -1361,27 +1425,42 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
if ptype == 'available':
ptype = None
gajim.log.debug('PresenceCB: %s' % ptype)
- who = helpers.get_full_jid_from_iq(prs)
+ try:
+ who = helpers.get_full_jid_from_iq(prs)
+ except:
+ if prs.getTag('error').getTag('jid-malformed'):
+ # wrong jid, we probably tried to change our nick in a room to a non valid
+ # one
+ who = str(prs.getFrom())
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+ self.dispatch('GC_MSG', (jid_stripped, _('Nickname not allowed: %s') % \
+ resource, None, False))
+ return
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
timestamp = None
is_gc = False # is it a GC presence ?
sigTag = None
avatar_sha = None
+ # JEP-0172 User Nickname
+ user_nick = prs.getTagData('nick')
+ if not user_nick:
+ user_nick = ''
transport_auto_auth = False
xtags = prs.getTags('x')
for x in xtags:
- if x.getNamespace().startswith(common.xmpp.NS_MUC):
+ namespace = x.getNamespace()
+ if namespace.startswith(common.xmpp.NS_MUC):
is_gc = True
- if x.getNamespace() == common.xmpp.NS_SIGNED:
+ if namespace == common.xmpp.NS_SIGNED:
sigTag = x
- if x.getNamespace() == common.xmpp.NS_VCARD_UPDATE:
+ if namespace == common.xmpp.NS_VCARD_UPDATE:
avatar_sha = x.getTagData('photo')
- if x.getNamespace() == common.xmpp.NS_DELAY:
+ if namespace == common.xmpp.NS_DELAY:
# JEP-0091
tim = prs.getTimestamp()
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
timestamp = time.localtime(timegm(tim))
- if x.getNamespace() == 'http://delx.cjb.net/protocol/roster-subsync':
+ if namespace == 'http://delx.cjb.net/protocol/roster-subsync':
# see http://trac.gajim.org/ticket/326
agent = gajim.get_server_from_jid(jid_stripped)
if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact
@@ -1389,7 +1468,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
no_log_for = gajim.config.get_per('accounts', self.name,
'no_log_for').split()
- status = prs.getStatus()
+ status = prs.getStatus() or ''
show = prs.getShow()
if not show in STATUS_LIST:
show = '' # We ignore unknown show
@@ -1448,7 +1527,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
if not ptype or ptype == 'unavailable':
if gajim.config.get('log_contact_status_changes') and self.name\
not in no_log_for and jid_stripped not in no_log_for:
- gajim.logger.write('gcstatus', who, status, show)
+ gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, resource)
+ st = status or ''
+ if gc_c:
+ jid = gc_c.jid
+ else:
+ jid = prs.getJid()
+ if jid:
+ # we know real jid, save it in db
+ st += ' (%s)' % jid
+ gajim.logger.write('gcstatus', who, st, show)
if avatar_sha:
if self.vcard_shas.has_key(who):
if avatar_sha != self.vcard_shas[who]:
@@ -1475,23 +1563,49 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
resource, prio, keyID, timestamp))
if transport_auto_auth:
self.automatically_added.append(jid_stripped)
- self.request_subscription(jid_stripped)
+ self.request_subscription(jid_stripped, name = user_nick)
else:
if not status:
status = _('I would like to add you to my roster.')
- self.dispatch('SUBSCRIBE', (who, status))
+ self.dispatch('SUBSCRIBE', (who, status, user_nick))
elif ptype == 'subscribed':
if jid_stripped in self.automatically_added:
self.automatically_added.remove(jid_stripped)
else:
- self.dispatch('SUBSCRIBED', (jid_stripped, resource))
+ # detect a subscription loop
+ if not self.subscribed_events.has_key(jid_stripped):
+ self.subscribed_events[jid_stripped] = []
+ self.subscribed_events[jid_stripped].append(time.time())
+ block = False
+ if len(self.subscribed_events[jid_stripped]) > 5:
+ if time.time() - self.subscribed_events[jid_stripped][0] < 5:
+ block = True
+ self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
+ if block:
+ gajim.config.set_per('account', self.name,
+ 'dont_ack_subscription', True)
+ else:
+ self.dispatch('SUBSCRIBED', (jid_stripped, resource))
# BE CAREFUL: no con.updateRosterItem() in a callback
gajim.log.debug(_('we are now subscribed to %s') % who)
elif ptype == 'unsubscribe':
gajim.log.debug(_('unsubscribe request from %s') % who)
elif ptype == 'unsubscribed':
gajim.log.debug(_('we are now unsubscribed from %s') % who)
- self.dispatch('UNSUBSCRIBED', jid_stripped)
+ # detect a unsubscription loop
+ if not self.subscribed_events.has_key(jid_stripped):
+ self.subscribed_events[jid_stripped] = []
+ self.subscribed_events[jid_stripped].append(time.time())
+ block = False
+ if len(self.subscribed_events[jid_stripped]) > 5:
+ if time.time() - self.subscribed_events[jid_stripped][0] < 5:
+ block = True
+ self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
+ if block:
+ gajim.config.set_per('account', self.name, 'dont_ack_subscription',
+ True)
+ else:
+ self.dispatch('UNSUBSCRIBED', jid_stripped)
elif ptype == 'error':
errmsg = prs.getError()
errcode = prs.getErrorCode()
@@ -1650,13 +1764,18 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid
else:
infos = raw_roster[jid]
- if jid != our_jid and (not infos['subscription'] or infos['subscription'] == \
- 'none') and (not infos['ask'] or infos['ask'] == 'none') and not infos['name'] \
- and not infos['groups']:
+ if jid != our_jid and (not infos['subscription'] or \
+ infos['subscription'] == 'none') and (not infos['ask'] or \
+ infos['ask'] == 'none') and not infos['name'] and \
+ not infos['groups']:
# remove this useless item, it won't be shown in roster anyway
self.connection.getRoster().delItem(jid)
elif jid != our_jid: # don't add our jid
roster[j] = raw_roster[jid]
+ if gajim.jid_is_transport(jid) and \
+ not gajim.get_transport_name_from_jid(jid):
+ # we can't determine which iconset to use
+ self.discoverInfo(jid)
self.dispatch('ROSTER', roster)
@@ -1694,7 +1813,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
# If it's a gmail account,
# inform the server that we want e-mail notifications
- if gajim.get_server_from_jid(our_jid) == 'gmail.com':
+ if gajim.get_server_from_jid(our_jid) in gajim.gmail_domains:
gajim.log.debug(('%s is a gmail account. Setting option '
'to get e-mail notifications on the server.') % (our_jid))
iq = common.xmpp.Iq(typ = 'set', to = our_jid)
@@ -1748,6 +1867,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
common.xmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._VersionCB, 'get',
common.xmpp.NS_VERSION)
+ con.RegisterHandler('iq', self._TimeCB, 'get',
+ common.xmpp.NS_TIME)
con.RegisterHandler('iq', self._LastCB, 'get',
common.xmpp.NS_LAST)
con.RegisterHandler('iq', self._LastResultCB, 'result',
@@ -1762,8 +1883,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
common.xmpp.NS_ROSTER)
con.RegisterHandler('iq', self._PrivateCB, 'result',
common.xmpp.NS_PRIVATE)
- con.RegisterHandler('iq', self._PrivateErrorCB, 'error',
- common.xmpp.NS_PRIVATE)
con.RegisterHandler('iq', self._HttpAuthCB, 'get',
common.xmpp.NS_HTTP_AUTH)
con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
diff --git a/src/common/contacts.py b/src/common/contacts.py
index e04df52d2..e6d095641 100644
--- a/src/common/contacts.py
+++ b/src/common/contacts.py
@@ -210,7 +210,7 @@ class Contacts:
contacts = self.get_contacts_from_jid(account, jid)
if not contacts and '/' in jid:
# jid may be a fake jid, try it
- room, nick = jid.split('/')
+ room, nick = jid.split('/', 1)
contact = self.get_gc_contact(account, room, nick)
return contact
return self.get_highest_prio_contact_from_contacts(contacts)
@@ -324,8 +324,11 @@ class Contacts:
max_order = data_['order']
contact = self.get_contact_with_highest_priority(account, jid)
score = (max_order - order)*10000
- if not common.gajim.jid_is_transport(jid):
- score += contact.priority*10
+
+ if common.gajim.get_transport_name_from_jid(jid) is None:
+ score += 10
+ if contact.priority > 0:
+ score += contact.priority * 10
score += ['not in roster', 'error', 'offline', 'invisible', 'dnd', 'xa',
'away', 'chat', 'online', 'requested', 'message'].index(contact.show)
return score
diff --git a/src/common/events.py b/src/common/events.py
new file mode 100644
index 000000000..4a7ba9e87
--- /dev/null
+++ b/src/common/events.py
@@ -0,0 +1,233 @@
+## common/events.py
+##
+## Contributors for this file:
+## - Yann Le Boulanger
+##
+## Copyright (C) 2006 Yann Le Boulanger
+## Vincent Hanquez
+## Nikos Kouremenos
+## Dimitur Kirov
+## Travis Shirk
+## Norman Rasmussen
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+import time
+import gajim
+
+class Event:
+ '''Information concerning each event'''
+ def __init__(self, type_, time_, parameters, show_in_roster = False,
+ show_in_systray = True):
+ ''' type_ in chat, normal, file-request, file-error, file-completed,
+ file-request-error, file-send-error, file-stopped, gc_msg, pm,
+ printed_chat, printed_gc_msg, printed_pm
+ parameters is (per type_):
+ chat, normal: [message, subject, kind, time, encrypted, resource,
+ msg_id]
+ where kind in error, incoming
+ file-*: file_props
+ gc_msg: None
+ printed_*: None
+ messages that are already printed in chat, but not read'''
+ self.type_ = type_
+ self.time_ = time_
+ self.parameters = parameters
+ self.show_in_roster = show_in_roster
+ self.show_in_systray = show_in_systray
+
+class Events:
+ '''Information concerning all events'''
+ def __init__(self):
+ self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
+
+ def change_account_name(self, old_name, new_name):
+ if self._events.has_key(old_name):
+ self._events[new_name] = self._events[old_name]
+ del self._events[old_name]
+
+ def add_account(self, account):
+ self._events[account] = {}
+
+ def get_accounts(self):
+ return self._events.keys()
+
+ def remove_account(self, account):
+ del self._events[account]
+
+ def create_event(self, type_, parameters, time_ = time.time(),
+ show_in_roster = False, show_in_systray = True):
+ return Event(type_, time_, parameters, show_in_roster,
+ show_in_systray)
+
+ def add_event(self, account, jid, event):
+ # No such account before ?
+ if not self._events.has_key(account):
+ self._events[account] = {jid: [event]}
+ # no such jid before ?
+ elif not self._events[account].has_key(jid):
+ self._events[account][jid] = [event]
+ else:
+ self._events[account][jid].append(event)
+ if event.show_in_systray and gajim.interface.systray_capabilities:
+ gajim.interface.systray.set_img()
+
+ def remove_events(self, account, jid, event = None, types = []):
+ '''if event is not speficied, remove all events from this jid,
+ optionnaly only from given type
+ return True if no such event found'''
+ if not self._events.has_key(account):
+ return True
+ if not self._events[account].has_key(jid):
+ return True
+ if event: # remove only one event
+ if event in self._events[account][jid]:
+ if len(self._events[account][jid]) == 1:
+ del self._events[account][jid]
+ else:
+ self._events[account][jid].remove(event)
+ if event.show_in_systray and gajim.interface.systray_capabilities:
+ gajim.interface.systray.set_img()
+ return
+ else:
+ return True
+ if types:
+ new_list = [] # list of events to keep
+ for ev in self._events[account][jid]:
+ if ev.type_ not in types:
+ new_list.append(ev)
+ if len(new_list) == len(self._events[account][jid]):
+ return True
+ if new_list:
+ self._events[account][jid] = new_list
+ else:
+ del self._events[account][jid]
+ if gajim.interface.systray_capabilities:
+ gajim.interface.systray.set_img()
+ return
+ # no event nor type given, remove them all
+ del self._events[account][jid]
+ if gajim.interface.systray_capabilities:
+ gajim.interface.systray.set_img()
+
+ def get_nb_events(self, types = []):
+ return self._get_nb_events(types = types)
+
+ def get_events(self, account, jid = None, types = []):
+ '''if event is not speficied, remove all events from this jid,
+ optionnaly only from given type'''
+ if not self._events.has_key(account):
+ return []
+ if not jid:
+ return self._events[account]
+ if not self._events[account].has_key(jid):
+ return []
+ events_list = [] # list of events
+ for ev in self._events[account][jid]:
+ if not types or ev.type_ in types:
+ events_list.append(ev)
+ return events_list
+
+ def get_first_event(self, account, jid = None, type_ = None):
+ '''Return the first event of type type_ if given'''
+ events_list = self.get_events(account, jid, type_)
+ # be sure it's bigger than latest event
+ first_event_time = time.time() + 1
+ first_event = None
+ for event in events_list:
+ if event.time_ < first_event_time:
+ first_event_time = event.time_
+ first_event = event
+ return first_event
+
+ def _get_nb_events(self, account = None, jid = None, attribute = None, types = []):
+ '''return the number of events'''
+ nb = 0
+ if account:
+ accounts = [account]
+ else:
+ accounts = self._events.keys()
+ for acct in accounts:
+ if not self._events.has_key(acct):
+ continue
+ if jid:
+ jids = [jid]
+ else:
+ jids = self._events[acct].keys()
+ for j in jids:
+ if not self._events[acct].has_key(j):
+ continue
+ for event in self._events[acct][j]:
+ if types and event.type_ not in types:
+ continue
+ if not attribute or \
+ attribute == 'systray' and event.show_in_systray or \
+ attribute == 'roster' and event.show_in_roster:
+ nb += 1
+ return nb
+
+ def _get_some_events(self, attribute):
+ '''attribute in systray, roster'''
+ events = {}
+ for account in self._events:
+ events[account] = {}
+ for jid in self._events[account]:
+ events[account][jid] = []
+ for event in self._events[account][jid]:
+ if attribute == 'systray' and event.show_in_systray or \
+ attribute == 'roster' and event.show_in_roster:
+ events[account][jid].append(event)
+ if not events[account][jid]:
+ del events[account][jid]
+ if not events[account]:
+ del events[account]
+ return events
+
+ def _get_first_event_with_attribute(self, events):
+ '''get the first event
+ events is in the form {account1: {jid1: [ev1, ev2], },. }'''
+ # be sure it's bigger than latest event
+ first_event_time = time.time() + 1
+ first_account = None
+ first_jid = None
+ first_event = None
+ for account in events:
+ for jid in events[account]:
+ for event in events[account][jid]:
+ if event.time_ < first_event_time:
+ first_event_time = event.time_
+ first_account = account
+ first_jid = jid
+ first_event = event
+ return first_account, first_jid, first_event
+
+ def get_nb_systray_events(self, types = []):
+ '''returns the number of events displayedin roster'''
+ return self._get_nb_events(attribute = 'systray', types = types)
+
+ def get_systray_events(self):
+ '''return all events that must be displayed in systray:
+ {account1: {jid1: [ev1, ev2], },. }'''
+ return self._get_some_events('systray')
+
+ def get_first_systray_event(self):
+ events = self.get_systray_events()
+ return self._get_first_event_with_attribute(events)
+
+ def get_nb_roster_events(self, account = None, jid = None, types = []):
+ '''returns the number of events displayedin roster'''
+ return self._get_nb_events(attribute = 'roster', account = account,
+ jid = jid, types = types)
+
+ def get_roster_events(self):
+ '''return all events that must be displayed in roster:
+ {account1: {jid1: [ev1, ev2], },. }'''
+ return self._get_some_events('roster')
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
index 3389f3aab..0b1bc8c4a 100644
--- a/src/common/exceptions.py
+++ b/src/common/exceptions.py
@@ -23,9 +23,6 @@
## GNU General Public License for more details.
##
-from common import i18n
-_ = i18n._
-
class PysqliteNotAvailable(Exception):
'''sqlite2 is not installed or python bindings are missing'''
def __init__(self):
diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py
new file mode 100755
index 000000000..c7e1f6732
--- /dev/null
+++ b/src/common/fuzzyclock.py
@@ -0,0 +1,134 @@
+## fuzzyclock.py
+##
+## Contributors for this file:
+##
+## - Yann Le Boulanger
+## - Christoph Neuroth
+##
+## Copyright (C) 2006 Christoph Neuroth
+## Yann Le Boulanger
+## Dimitur Kirov
+## Travis Shirk
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+'''
+Python class to show a "fuzzy clock".
+Homepage: http://home.gna.org/fuzzyclock/
+Project Page: http://gna.org/projects/fuzzyclock
+
+The class has been ported from PHP code by
+Henrique Recidive which was
+in turn based on the Fuzzy Clock Applet of Frerich Raabe (KDE).
+So most of the credit goes to this guys, thanks :-)
+'''
+
+import time
+
+class FuzzyClock:
+ def __init__(self):
+ self.__hour = 0
+ self.__minute = 0
+ self.__dayOfWeek = 0
+
+ self.__hourNames = [ _('one'), _('two'), _('three'), _('four'), _('five'), _('six'),
+ _('seven'), _('eight'), _('nine'), _('ten'), _('eleven'),
+ _('twelve')]
+
+ #Strings to use for the output. %0 will be replaced with the preceding hour (e.g. "x PAST %0"), %1 with the coming hour (e.g. "x TO %1). '''
+ self.__normalFuzzy = [ _("%0 o'clock"), _('five past %0'), _('ten past %0'),
+ _('quarter past %0'), _('twenty past %0'),
+ _('twenty five past %0'), _('half past %0'),
+ _('twenty five to %1'), _('twenty to %1'),
+ _('quarter to %1'), _('ten to %1'), _('five to %1'),
+ _("%1 o'clock") ]
+
+ #A "singular-form". It is used when talking about hour 0
+ self.__normalFuzzyOne = [ _("%0 o'clock"), _('five past %0'),
+ _('ten past %0'), _('quarter past %0'),
+ _('twenty past %0'), _('twenty five past %0'),
+ _('half past %0'), _('twenty five to %1'),
+ _('twenty to %1'), _('quarter to %1'),
+ _('ten to %1'), _('five to %1'), _("%1 o'clock") ]
+
+ self.__dayTime = [ _('Night'), _('Early morning'), _('Morning'), _('Almost noon'),
+ _('Noon'), _('Afternoon'), _('Evening'), _('Late evening') ]
+
+ self.__fuzzyWeek = [ _('Start of week'), _('Middle of week'), _('End of week'),
+ _('Weekend!') ]
+
+ self.setCurrent()
+
+ def setHour(self,hour):
+ self.__hour = int(hour)
+
+ def setMinute(self,minute):
+ self.__minute=int(minute)
+
+ def setDayOfWeek(self,day):
+ self.__dayOfWeek=int(day)
+
+ def setTime(self,time):
+ timeArray = time.split(":")
+ self.setHour(timeArray[0])
+ self.setMinute(timeArray[1])
+
+ def setCurrent(self):
+ hour=time.strftime("%H")
+ minute=time.strftime("%M")
+ day=time.strftime("%w")
+
+ self.setTime(hour+":"+minute)
+ self.setDayOfWeek(day)
+
+ def getFuzzyTime(self, fuzzyness = 1):
+ sector = 0
+ realHour = 0
+
+ if fuzzyness == 1 or fuzzyness == 2:
+ if fuzzyness == 1:
+ if self.__minute >2:
+ sector = (self.__minute - 3) / 5 +1
+ else:
+ if self.__minute > 6:
+ sector = ((self.__minute - 7) / 15 + 1) * 3
+
+ newTimeStr = self.__normalFuzzy[sector]
+ #%0 or %1?
+ deltaHour = int(newTimeStr[newTimeStr.find("%")+1])
+
+ if (self.__hour + deltaHour) % 12 > 0:
+ realHour = (self.__hour + deltaHour) % 12 - 1
+ else:
+ realHour = 12 - ((self.__hour + deltaHour) % 12 + 1)
+
+ if realHour == 0:
+ newTimeStr = self.__normalFuzzyOne[sector]
+
+ newTimeStr = newTimeStr.replace("%"+str(deltaHour),
+ self.__hourNames[realHour])
+
+
+ elif fuzzyness == 3:
+ newTimeStr = self.__dayTime[self.__hour / 3]
+
+ else:
+ dayOfWeek = self.__dayOfWeek
+ if dayOfWeek == 1:
+ newTimeStr = self.__fuzzyWeek[0]
+ elif dayOfWeek >= 2 and dayOfWeek <= 4:
+ newTimeStr = self.__fuzzyWeek[1]
+ elif dayOfWeek == 5:
+ newTimeStr = self.__fuzzyWeek[2]
+ else:
+ newTimeStr = self.__fuzzyWeek[3]
+
+ return newTimeStr
diff --git a/src/common/gajim.py b/src/common/gajim.py
index b4afc2508..dc7794e6e 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -23,10 +23,11 @@ import locale
import config
from contacts import Contacts
+from events import Events
interface = None # The actual interface (the gtk one for the moment)
-version = '0.10'
config = config.Config()
+version = config.get('version')
connections = {}
verbose = False
@@ -81,6 +82,10 @@ if LANG is None:
else:
LANG = LANG[:2] # en, fr, el etc..
+gmail_domains = ['gmail.com', 'googlemail.com']
+
+transport_type = {} # list the type of transport
+
last_message_time = {} # list of time of the latest incomming message
# {acct1: {jid1: time1, jid2: time2}, }
encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
@@ -88,23 +93,20 @@ encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
contacts = Contacts()
gc_connected = {} # tell if we are connected to the room or not {acct: {room_jid: True}}
gc_passwords = {} # list of the pass required to enter a room {room_jid: password}
+automatic_rooms = {} # list of rooms that must be automaticaly configured and for which we have a list of invities {account: {room_jid: {'invities': []}}}
groups = {} # list of groups
newly_added = {} # list of contacts that has just signed in
to_be_removed = {} # list of contacts that has just signed out
-awaiting_events = {} # list of messages/FT reveived but not printed
- # awaiting_events[jid] = (type, (data1, data2, ...))
- # if type in ('chat', 'normal'): data = (message, subject, kind, time,
- # encrypted, resource)
- # kind can be (incoming, error)
- # if type in file-request, file-request-error, file-send-error, file-error,
- # file-completed, file-stopped:
- # data = file_props
+events = Events()
+
nicks = {} # list of our nick names in each account
# should we block 'contact signed in' notifications for this account?
# this is only for the first 30 seconds after we change our show
# to something else than offline
+# can also contain account/transport_jid to block notifications for contacts
+# from this transport
block_signed_in_notifications = {}
con_types = {} # type of each connection (ssl, tls, tcp, ...)
@@ -218,8 +220,11 @@ def get_transport_name_from_jid(jid, use_config_setting = True):
# jid was None. Yann why?
if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
return
-
+
host = get_server_from_jid(jid)
+ if host in transport_type:
+ return transport_type[host]
+
# host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
host_splitted = host.split('.')
if len(host_splitted) != 0:
@@ -229,7 +234,7 @@ def get_transport_name_from_jid(jid, use_config_setting = True):
if host == 'aim':
return 'aim'
elif host == 'gg':
- return 'gadugadu'
+ return 'gadu-gadu'
elif host == 'irc':
return 'irc'
elif host == 'icq':
@@ -277,18 +282,6 @@ def get_hostname_from_account(account_name, use_srv = False):
return config.get_per('accounts', account_name, 'custom_host')
return config.get_per('accounts', account_name, 'hostname')
-def get_first_event(account, jid, typ = None):
- '''returns the first event of the given type from the awaiting_events queue'''
- if not awaiting_events[account].has_key(jid):
- return None
- q = awaiting_events[account][jid]
- if not typ:
- return q[0]
- for ev in q:
- if ev[0] == typ:
- return ev
- return None
-
def get_notification_image_prefix(jid):
'''returns the prefix for the notification images'''
transport_name = get_transport_name_from_jid(jid)
diff --git a/src/common/helpers.py b/src/common/helpers.py
index a5574cc96..1226f2cd5 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -18,6 +18,7 @@
import sre
import os
+import subprocess
import urllib
import errno
import select
@@ -26,7 +27,7 @@ import sha
from encodings.punycode import punycode_encode
import gajim
-import i18n
+from i18n import Q_
from xmpp_stringprep import nodeprep, resourceprep, nameprep
try:
@@ -36,9 +37,6 @@ try:
except:
pass
-_ = i18n._
-Q_ = i18n.Q_
-
special_groups = (_('Transports'), _('Not in Roster'), _('Observers'))
class InvalidFormat(Exception):
@@ -363,6 +361,11 @@ def is_in_path(name_of_command, return_abs_path = False):
else:
return is_in_dir
+def exec_command(command):
+ '''command is a string that contain arguments'''
+# os.system(command)
+ subprocess.Popen(command.split())
+
def launch_browser_mailer(kind, uri):
#kind = 'url' or 'mail'
if os.name == 'nt':
@@ -386,11 +389,9 @@ def launch_browser_mailer(kind, uri):
command = gajim.config.get('custommailapp')
if command == '': # if no app is configured
return
- # we add the uri in "" so we have good parsing from shell
- uri = uri.replace('"', '\\"') # escape "
- command = command + ' "' + uri + '" &'
- try: #FIXME: when we require python2.4+ use subprocess module
- os.system(command)
+ command = command + ' ' + uri
+ try:
+ exec_command(command)
except:
pass
@@ -409,11 +410,9 @@ def launch_file_manager(path_to_open):
command = gajim.config.get('custom_file_manager')
if command == '': # if no app is configured
return
- # we add the path in "" so we have good parsing from shell
- path_to_open = path_to_open.replace('"', '\\"') # escape "
- command = command + ' "' + path_to_open + '" &'
- try: #FIXME: when we require python2.4+ use subprocess module
- os.system(command)
+ command = command + ' ' + path_to_open
+ try:
+ exec_command(command)
except:
pass
@@ -421,6 +420,9 @@ def play_sound(event):
if not gajim.config.get('sounds_on'):
return
path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
+ play_sound_file(path_to_soundfile)
+
+def play_sound_file(path_to_soundfile):
if path_to_soundfile == 'beep':
print '\a' # make a speaker beep
return
@@ -436,11 +438,8 @@ def play_sound(event):
if gajim.config.get('soundplayer') == '':
return
player = gajim.config.get('soundplayer')
- # we add the path in "" so we have good parsing from shell
- path_to_soundfile = path_to_soundfile.replace('"', '\\"') # escape "
- command = player + ' "' + path_to_soundfile + '" &'
- #FIXME: when we require 2.4+ use subprocess module
- os.system(command)
+ command = player + ' ' + path_to_soundfile
+ exec_command(command)
def get_file_path_from_dnd_dropped_uri(uri):
path = urllib.url2pathname(uri) # escape special chars
@@ -464,14 +463,6 @@ def from_xs_boolean_to_python_boolean(value):
return val
-def ensure_unicode_string(s):
- # py23 u'abc'.decode('utf-8') raises
- # python24 does not. if python23 is ooold we can remove this func
- # FIXME: remove this when we abandon py23
- if isinstance(s, str):
- s = s.decode('utf-8')
- return s
-
def get_xmpp_show(show):
if show in ('online', 'offline'):
return None
@@ -514,9 +505,9 @@ def get_global_status():
def get_icon_name_to_show(contact, account = None):
'''Get the icon name to show in online, away, requested, ...'''
- if account and gajim.awaiting_events[account].has_key(contact.jid):
+ if account and gajim.events.get_nb_roster_events(account, contact.jid):
return 'message'
- if account and gajim.awaiting_events[account].has_key(
+ if account and gajim.events.get_nb_roster_events(account,
contact.get_full_jid()):
return 'message'
if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
@@ -543,6 +534,14 @@ def decode_string(string):
return string
+def ensure_utf8_string(string):
+ '''make sure string is in UTF-8'''
+ try:
+ string = decode_string(string).encode('utf-8')
+ except:
+ pass
+ return string
+
def get_windows_reg_env(varname, default=''):
'''asks for paths commonly used but not exposed as ENVs
in english Windows 2003 those are:
@@ -692,8 +691,11 @@ def get_os_info():
text = fd.readline().strip() # get only first line
fd.close()
if path_to_file.endswith('version'):
- # sourcemage_version has all the info we need
- if not os.path.basename(path_to_file).startswith('sourcemage'):
+ # sourcemage_version and slackware-version files
+ # have all the info we need (name and version of distro)
+ if not os.path.basename(path_to_file).startswith(
+ 'sourcemage') or not\
+ os.path.basename(path_to_file).startswith('slackware'):
text = distro_name + ' ' + text
elif path_to_file.endswith('aurox-release'):
# file doesn't have version
@@ -724,20 +726,73 @@ def sanitize_filename(filename):
return filename
-def allow_showing_notification(account):
+def allow_showing_notification(account, type = None, advanced_notif_num = None,
+first = True):
'''is it allowed to show nofication?
- check OUR status and if we allow notifications for that status'''
+ check OUR status and if we allow notifications for that status
+ type is the option that need to be True ex: notify_on_signing
+ first: set it to false when it's not the first message'''
+ if advanced_notif_num != None:
+ popup = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'popup')
+ if popup == 'yes':
+ return True
+ if popup == 'no':
+ return False
+ if type and (not gajim.config.get(type) or not first):
+ return False
+ if type and gajim.config.get(type) and first:
+ return True
if gajim.config.get('autopopupaway'): # always show notification
return True
if gajim.connections[account].connected in (2, 3): # we're online or chat
return True
return False
-def allow_popup_window(account):
+def allow_popup_window(account, advanced_notif_num = None):
'''is it allowed to popup windows?'''
+ if advanced_notif_num != None:
+ popup = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'auto_open')
+ if popup == 'yes':
+ return True
+ if popup == 'no':
+ return False
autopopup = gajim.config.get('autopopup')
autopopupaway = gajim.config.get('autopopupaway')
if autopopup and (autopopupaway or \
gajim.connections[account].connected in (2, 3)): # we're online or chat
return True
return False
+
+def allow_sound_notification(sound_event, advanced_notif_num = None):
+ if advanced_notif_num != None:
+ sound = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'sound')
+ if sound == 'yes':
+ return True
+ if sound == 'no':
+ return False
+ if gajim.config.get_per('soundevents', sound_event, 'enabled'):
+ return True
+ return False
+
+def get_chat_control(account, contact):
+ full_jid_with_resource = contact.jid
+ if contact.resource:
+ full_jid_with_resource += '/' + contact.resource
+ highest_contact = gajim.contacts.get_contact_with_highest_priority(
+ account, contact.jid)
+ # Look for a chat control that has the given resource, or default to
+ # one without resource
+ ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
+ account)
+ if ctrl:
+ return ctrl
+ elif not highest_contact or not highest_contact.resource:
+ # unknow contact or offline message
+ return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
+ elif highest_contact and contact.resource != \
+ highest_contact.resource:
+ return None
+ return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
diff --git a/src/common/i18n.py b/src/common/i18n.py
index 7af267990..ae23f0e2f 100644
--- a/src/common/i18n.py
+++ b/src/common/i18n.py
@@ -48,21 +48,11 @@ if os.name == 'nt':
if lang:
os.environ['LANG'] = lang
-_translation = None
-
-def init():
- global _translation
- try:
- _translation = gettext.translation(APP, DIR)
- except IOError:
- _translation = gettext.NullTranslations()
-
-init()
-
-def _(s):
- if s == '':
- return s
- return _translation.ugettext(s)
+gettext.install(APP, DIR, unicode = True)
+if gettext._translations:
+ _translation = gettext._translations.values()[0]
+else:
+ _translation = gettext.NullTranslations()
def Q_(s):
# Qualified translatable strings
diff --git a/src/common/logger.py b/src/common/logger.py
index 01077cc78..11f784406 100644
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -29,8 +29,7 @@ import time
import datetime
import exceptions
-import i18n
-_ = i18n._
+import gajim
try:
from pysqlite2 import dbapi2 as sqlite
@@ -79,30 +78,50 @@ class Constants:
self.SHOW_OFFLINE
) = range(6)
+ (
+ self.TYPE_AIM,
+ self.TYPE_GG,
+ self.TYPE_HTTP_WS,
+ self.TYPE_ICQ,
+ self.TYPE_MSN,
+ self.TYPE_QQ,
+ self.TYPE_SMS,
+ self.TYPE_SMTP,
+ self.TYPE_TLEN,
+ self.TYPE_YAHOO,
+ self.TYPE_NEWMAIL,
+ self.TYPE_RSS,
+ self.TYPE_WEATHER,
+ ) = range(13)
+
constants = Constants()
class Logger:
def __init__(self):
self.jids_already_in = [] # holds jids that we already have in DB
-
+ self.con = None
+
if not os.path.exists(LOG_DB_PATH):
# this can happen only the first time (the time we create the db)
# db is not created here but in src/common/checks_paths.py
return
self.init_vars()
-
+
def init_vars(self):
# if locked, wait up to 20 sec to unlock
# before raise (hopefully should be enough)
+ if self.con:
+ self.con.close()
self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
isolation_level = 'IMMEDIATE')
self.cur = self.con.cursor()
-
+
self.get_jids_already_in_db()
def get_jids_already_in_db(self):
self.cur.execute('SELECT jid FROM jids')
rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
+ self.jids_already_in = []
for row in rows:
# row[0] is first item of row (the only result here, the jid)
self.jids_already_in.append(row[0])
@@ -193,7 +212,66 @@ class Logger:
show_col = 'UNKNOWN'
return kind_col, show_col
-
+
+ def convert_human_transport_type_to_db_api_values(self, type_):
+ '''converts from string style to constant ints for db'''
+ if type_ == 'aim':
+ return constants.TYPE_AIM
+ if type_ == 'gadu-gadu':
+ return constants.TYPE_GG
+ if type_ == 'http-ws':
+ return constants.TYPE_HTTP_WS
+ if type_ == 'icq':
+ return constants.TYPE_ICQ
+ if type_ == 'msn':
+ return constants.TYPE_MSN
+ if type_ == 'qq':
+ return constants.TYPE_QQ
+ if type_ == 'sms':
+ return constants.TYPE_SMS
+ if type_ == 'smtp':
+ return constants.TYPE_SMTP
+ if type_ == 'tlen':
+ return constants.TYPE_TLEN
+ if type_ == 'yahoo':
+ return constants.TYPE_YAHOO
+ if type_ == 'newmail':
+ return constants.TYPE_NEWMAIL
+ if type_ == 'rss':
+ return constants.TYPE_RSS
+ if type_ == 'weather':
+ return constants.TYPE_WEATHER
+ return None
+
+ def convert_api_values_to_human_transport_type(self, type_id):
+ '''converts from constant ints for db to string style'''
+ if type_id == constants.TYPE_AIM:
+ return 'aim'
+ if type_id == constants.TYPE_GG:
+ return 'gadu-gadu'
+ if type_id == constants.TYPE_HTTP_WS:
+ return 'http-ws'
+ if type_id == constants.TYPE_ICQ:
+ return 'icq'
+ if type_id == constants.TYPE_MSN:
+ return 'msn'
+ if type_id == constants.TYPE_QQ:
+ return 'qq'
+ if type_id == constants.TYPE_SMS:
+ return 'sms'
+ if type_id == constants.TYPE_SMTP:
+ return 'smtp'
+ if type_id == constants.TYPE_TLEN:
+ return 'tlen'
+ if type_id == constants.TYPE_YAHOO:
+ return 'yahoo'
+ if type_id == constants.TYPE_NEWMAIL:
+ return 'newmail'
+ if type_id == constants.TYPE_RSS:
+ return 'rss'
+ if type_id == constants.TYPE_WEATHER:
+ return 'weather'
+
def commit_to_db(self, values, write_unread = False):
#print 'saving', values
sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)'
@@ -239,25 +317,8 @@ class Logger:
self.cur.execute(
'SELECT message_id from unread_messages WHERE jid_id = %d' % jid_id)
results = self.cur.fetchall()
- # Remove before 0.10
except:
- try:
- self.cur.executescript('DROP TABLE unread_messages;')
- self.con.commit()
- except:
- pass
- try:
- self.cur.executescript('''CREATE TABLE unread_messages(
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER
- );''')
- self.con.commit()
- except:
- pass
- self.con.close()
- self.jids_already_in = []
- self.init_vars()
- return []
+ pass
for message in results:
msg_id = message[0]
@@ -340,7 +401,7 @@ class Logger:
return self.commit_to_db(values, write_unread)
def get_last_conversation_lines(self, jid, restore_how_many_rows,
- pending_how_many, timeout):
+ pending_how_many, timeout, account):
'''accepts how many rows to restore and when to time them out (in minutes)
(mark them as too old) and number of messages that are in queue
and are already logged but pending to be viewed,
@@ -348,15 +409,17 @@ class Logger:
list with empty tupple if nothing found to meet our demands'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
+ where_sql = self._build_contact_where(account, jid)
+
now = int(float(time.time()))
timed_out = now - (timeout * 60) # before that they are too old
# so if we ask last 5 lines and we have 2 pending we get
# 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
self.cur.execute('''
SELECT time, kind, message FROM logs
- WHERE jid_id = %d AND kind IN (%d, %d, %d, %d) AND time > %d
+ WHERE (%s) AND kind IN (%d, %d, %d, %d) AND time > %d
ORDER BY time DESC LIMIT %d OFFSET %d
- ''' % (jid_id, constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV,
+ ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV,
constants.KIND_SINGLE_MSG_SENT, constants.KIND_CHAT_MSG_SENT,
timed_out, restore_how_many_rows, pending_how_many)
)
@@ -374,35 +437,36 @@ class Logger:
start_of_day = int(time.mktime(local_time)) # we have time since epoch baby :)
return start_of_day
- def get_conversation_for_date(self, jid, year, month, day):
+ def get_conversation_for_date(self, jid, year, month, day, account):
'''returns contact_name, time, kind, show, message
for each row in a list of tupples,
returns list with empty tupple if we found nothing to meet our demands'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
-
+ where_sql = self._build_contact_where(account, jid)
+
start_of_day = self.get_unix_time_from_date(year, month, day)
-
seconds_in_a_day = 86400 # 60 * 60 * 24
last_second_of_day = start_of_day + seconds_in_a_day - 1
self.cur.execute('''
SELECT contact_name, time, kind, show, message FROM logs
- WHERE jid_id = %d
+ WHERE (%s)
AND time BETWEEN %d AND %d
ORDER BY time
- ''' % (jid_id, start_of_day, last_second_of_day))
+ ''' % (where_sql, start_of_day, last_second_of_day))
results = self.cur.fetchall()
return results
- def get_search_results_for_query(self, jid, query):
+ def get_search_results_for_query(self, jid, query, account):
'''returns contact_name, time, kind, show, message
for each row in a list of tupples,
returns list with empty tupple if we found nothing to meet our demands'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
- if False: #query.startswith('SELECT '): # it's SQL query
+
+ if False: #query.startswith('SELECT '): # it's SQL query (FIXME)
try:
self.cur.execute(query)
except sqlite.OperationalError, e:
@@ -410,22 +474,24 @@ class Logger:
return results
else: # user just typed something, we search in message column
+ where_sql = self._build_contact_where(account, jid)
like_sql = '%' + query + '%'
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject FROM logs
- WHERE jid_id = ? AND message LIKE ?
+ WHERE (%s) AND message LIKE '%s'
ORDER BY time
- ''', (jid_id, like_sql))
+ ''' % (where_sql, like_sql))
results = self.cur.fetchall()
return results
- def get_days_with_logs(self, jid, year, month, max_day):
+ def get_days_with_logs(self, jid, year, month, max_day, account):
'''returns the list of days that have logs (not status messages)'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
days_with_logs = []
-
+ where_sql = self._build_contact_where(account, jid)
+
# First select all date of month whith logs we want
start_of_month = self.get_unix_time_from_date(year, month, 1)
seconds_in_a_day = 86400 # 60 * 60 * 24
@@ -433,11 +499,11 @@ class Logger:
self.cur.execute('''
SELECT time FROM logs
- WHERE jid_id = %d
+ WHERE (%s)
AND time BETWEEN %d AND %d
AND kind NOT IN (%d, %d)
ORDER BY time
- ''' % (jid_id, start_of_month, last_second_of_month,
+ ''' % (where_sql, start_of_month, last_second_of_month,
constants.KIND_STATUS, constants.KIND_GCSTATUS))
result = self.cur.fetchall()
@@ -468,17 +534,23 @@ class Logger:
result = self.cur.fetchone()
return days_with_logs
- def get_last_date_that_has_logs(self, jid, is_room = False):
+ def get_last_date_that_has_logs(self, jid, account = None, is_room = False):
'''returns last time (in seconds since EPOCH) for which
we had logs (excluding statuses)'''
jid = jid.lower()
- jid_id = self.get_jid_id(jid, 'ROOM')
+
+ where_sql = ''
+ if not is_room:
+ where_sql = self._build_contact_where(account, jid)
+ else:
+ jid_id = self.get_jid_id(jid, 'ROOM')
+ where_sql = 'jid_id = %s' % jid_id
self.cur.execute('''
SELECT time FROM logs
- WHERE jid_id = ?
- AND kind NOT IN (?, ?)
+ WHERE (%s)
+ AND kind NOT IN (%d, %d)
ORDER BY time DESC LIMIT 1
- ''', (jid_id, constants.KIND_STATUS, constants.KIND_GCSTATUS))
+ ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS))
results = self.cur.fetchone()
if results is not None:
@@ -487,3 +559,61 @@ class Logger:
result = None
return result
+
+ def _build_contact_where(self, account, jid):
+ '''build the where clause for a jid, including metacontacts
+ jid(s) if any'''
+ where_sql = ''
+ # will return empty list if jid is not associated with
+ # any metacontacts
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ for user in family:
+ jid_id = self.get_jid_id(user['jid'])
+ where_sql += 'jid_id = %s' % jid_id
+ if user != family[-1]:
+ where_sql += ' OR '
+ else: # if jid was not associated with metacontacts
+ jid_id = self.get_jid_id(jid)
+ where_sql = 'jid_id = %s' % jid_id
+ return where_sql
+
+ def save_transport_type(self, jid, type_):
+ '''save the type of the transport in DB'''
+ type_id = self.convert_human_transport_type_to_db_api_values(type_)
+ if not type_id:
+ # unknown type
+ return
+ self.cur.execute(
+ 'SELECT type from transports_cache WHERE transport = "%s"' % jid)
+ results = self.cur.fetchall()
+ if results:
+ result = results[0][0]
+ if result == type_id:
+ return
+ self.cur.execute(
+ 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' % (type_id,
+ jid))
+ try:
+ self.con.commit()
+ except sqlite.OperationalError, e:
+ print >> sys.stderr, str(e)
+ return
+ self.cur.execute(
+ 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id))
+ try:
+ self.con.commit()
+ except sqlite.OperationalError, e:
+ print >> sys.stderr, str(e)
+
+ def get_transports_type(self):
+ '''return all the type of the transports in DB'''
+ self.cur.execute(
+ 'SELECT * from transports_cache')
+ results = self.cur.fetchall()
+ if not results:
+ return {}
+ answer = {}
+ for result in results:
+ answer[result[0]] = self.convert_api_values_to_human_transport_type(result[1])
+ return answer
diff --git a/src/common/optparser.py b/src/common/optparser.py
index e2167c2c4..5459905b7 100644
--- a/src/common/optparser.py
+++ b/src/common/optparser.py
@@ -26,8 +26,6 @@ import os
import sys
import locale
from common import gajim
-from common import i18n
-_ = i18n._
class OptionsParser:
def __init__(self, filename):
@@ -129,21 +127,28 @@ class OptionsParser:
def update_config(self, old_version, new_version):
# Convert '0.x.y' to (0, x, y)
- old_version = old_version.split('.')
+ old_version_list = old_version.split('.')
old = []
- while len(old_version):
- old.append(int(old_version.pop(0)))
- new_version = new_version.split('.')
+ while len(old_version_list):
+ old.append(int(old_version_list.pop(0)))
+ new_version_list = new_version.split('.')
new = []
- while len(new_version):
- new.append(int(new_version.pop(0)))
+ while len(new_version_list):
+ new.append(int(new_version_list.pop(0)))
if old < [0, 9] and new >= [0, 9]:
self.update_config_x_to_09()
if old < [0, 10] and new >= [0, 10]:
self.update_config_09_to_010()
- if old < [0, 10, 0, 1] and new >= [0, 10, 0, 1]:
- self.update_config_to_01001()
+ if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]:
+ self.update_config_to_01011()
+ if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]:
+ self.update_config_to_01012()
+ if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
+ self.update_config_to_01013()
+
+ gajim.logger.init_vars()
+ gajim.config.set('version', new_version)
def update_config_x_to_09(self):
# Var name that changed:
@@ -258,6 +263,41 @@ class OptionsParser:
gajim.config.set('version', '0.10')
- def update_config_to_01001(self):
- gajim.config.set('print_status_in_muc', 'in_and_out')
- gajim.config.set('version', '0.10.0.1')
+ def update_config_to_01011(self):
+ if self.old_values.has_key('print_status_in_muc') and \
+ self.old_values['print_status_in_muc'] in (True, False):
+ gajim.config.set('print_status_in_muc', 'in_and_out')
+ gajim.config.set('version', '0.10.1.1')
+
+ def update_config_to_01012(self):
+ # See [6456]
+ if self.old_values.has_key('emoticons_theme') and \
+ self.old_values['emoticons_theme'] == 'Disabled':
+ gajim.config.set('emoticons_theme', '')
+ gajim.config.set('version', '0.10.1.2')
+
+ def update_config_to_01013(self):
+ '''create table transports_cache if there is no such table'''
+ import exceptions
+ try:
+ from pysqlite2 import dbapi2 as sqlite
+ except ImportError:
+ raise exceptions.PysqliteNotAvailable
+ import logger
+
+ con = sqlite.connect(logger.LOG_DB_PATH)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE transports_cache (
+ transport TEXT UNIQUE,
+ type INTEGER
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError, e:
+ pass
+ con.close()
+ gajim.config.set('version', '0.10.1.3')
diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py
index 5f2a9f528..5145632db 100644
--- a/src/common/proxy65_manager.py
+++ b/src/common/proxy65_manager.py
@@ -186,6 +186,9 @@ class HostTester(Socks5, IdleObject):
def connect(self):
''' create the socket and plug it to the idlequeue '''
+ if self.host is None:
+ self.on_failure()
+ return None
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.setblocking(False)
self.fd = self._sock.fileno()
diff --git a/src/common/socks5.py b/src/common/socks5.py
index da74297bd..6e472e1b8 100644
--- a/src/common/socks5.py
+++ b/src/common/socks5.py
@@ -32,6 +32,7 @@ import os
import struct
import sha
import time
+from dialogs import BindPortError
from errno import EWOULDBLOCK
from errno import ENOBUFS
@@ -84,12 +85,9 @@ class SocksQueue:
self.listener.bind()
if self.listener.started is False:
self.listener = None
- import sys
- print >> sys.stderr, '================================================='
- print >> sys.stderr, 'Unable to bind to port %s.' % port
- print >> sys.stderr, 'Maybe you have another running instance of Gajim.'
- print >> sys.stderr, 'File Transfer will be canceled.'
- print >> sys.stderr, '================================================='
+ # We cannot bind port, call error
+ # dialog from dialogs.py and fail
+ BindPortError(port)
return None
self.connected += 1
return self.listener
@@ -352,7 +350,10 @@ class SocksQueue:
class Socks5:
def __init__(self, idlequeue, host, port, initiator, target, sid):
if host is not None:
- self.host = socket.gethostbyname(host)
+ try:
+ self.host = socket.gethostbyname(host)
+ except socket.gaierror:
+ self.host = None
self.idlequeue = idlequeue
self.fd = -1
self.port = port
diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py
index 145b17113..40753ef4e 100644
--- a/src/common/xmpp/client_nb.py
+++ b/src/common/xmpp/client_nb.py
@@ -126,12 +126,11 @@ class NBCommonClient(CommonClient):
def _on_connected(self):
self.connected = 'tcp'
- if (self._Ssl is None and self.Connection.getPort() in (5223, 443)) or self._Ssl:
- try:
- transports_nb.NonBlockingTLS().PlugIn(self, now=1)
- self.connected = 'ssl'
- except socket.sslerror:
+ if self._Ssl:
+ transports_nb.NonBlockingTLS().PlugIn(self, now=1)
+ if not self.Connection: # ssl error, stream is closed
return
+ self.connected = 'ssl'
self.onreceive(self._on_receive_document_attrs)
dispatcher_nb.Dispatcher().PlugIn(self)
@@ -194,6 +193,8 @@ class NonBlockingClient(NBCommonClient):
self.isplugged = True
self.onreceive(None)
transports_nb.NonBlockingTLS().PlugIn(self)
+ if not self.Connection: # ssl error, stream is closed
+ return True
if not self.Dispatcher.Stream._document_attrs.has_key('version') or \
not self.Dispatcher.Stream._document_attrs['version']=='1.0':
self._is_connected()
diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py
index dbdaf4d63..ca13af184 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -134,6 +134,7 @@ class Dispatcher(PlugIn):
return 0
except ExpatError:
sys.exc_clear()
+ self.DEBUG('Invalid XML received from server. Forcing disconnect.')
self._owner.Connection.pollend()
return 0
if len(self._pendingExceptions) > 0:
diff --git a/src/common/xmpp/features.py b/src/common/xmpp/features.py
index 8fad7fc24..5b4b6fea6 100644
--- a/src/common/xmpp/features.py
+++ b/src/common/xmpp/features.py
@@ -27,6 +27,9 @@ All these methods takes 'disp' first argument that should be already connected
from protocol import *
REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED'
+PRIVACY_LISTS_RECEIVED='PRIVACY LISTS RECEIVED'
+PRIVACY_LIST_RECEIVED='PRIVACY LIST RECEIVED'
+PRIVACY_LISTS_ACTIVE_DEFAULT='PRIVACY LISTS ACTIVE DEFAULT'
### DISCO ### http://jabber.org/protocol/disco ### JEP-0030 ####################
### Browse ### jabber:iq:browse ### JEP-0030 ###################################
diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py
index 453f7229b..ed195ceca 100644
--- a/src/common/xmpp/features_nb.py
+++ b/src/common/xmpp/features_nb.py
@@ -14,7 +14,7 @@
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
-from features import REGISTER_DATA_RECEIVED
+from features import REGISTER_DATA_RECEIVED, PRIVACY_LISTS_RECEIVED, PRIVACY_LIST_RECEIVED, PRIVACY_LISTS_ACTIVE_DEFAULT
from protocol import *
def _on_default_response(disp, iq, cb):
@@ -146,9 +146,9 @@ def register(disp, host, info, cb):
attributes lastErrNode, lastErr and lastErrCode.
"""
iq=Iq('set', NS_REGISTER, to=host)
- if not isinstance(info, dict):
+ if not isinstance(info, dict):
info=info.asDict()
- for i in info.keys():
+ for i in info.keys():
iq.setTag('query').setTagData(i,info[i])
disp.SendAndCallForResponse(iq, cb)
@@ -172,37 +172,46 @@ def changePasswordTo(disp, newpassword, host=None, cb = None):
#type=[jid|group|subscription]
#action=[allow|deny]
-def getPrivacyLists(disp, cb):
+def getPrivacyLists(disp):
""" Requests privacy lists from connected server.
Returns dictionary of existing lists on success."""
iq = Iq('get', NS_PRIVACY)
def _on_response(resp):
dict = {'lists': []}
- try:
- if not isResultNode(resp):
- cb(False)
- return
- for list in resp.getQueryPayload():
- if list.getName()=='list':
- dict['lists'].append(list.getAttr('name'))
- else:
- dict[list.getName()]=list.getAttr('name')
- cb(dict)
- except:
- pass
- cb(False)
- disp.SendAndCallForResponse(iq, _on_respons)
+ if not isResultNode(resp):
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False))
+ return
+ for list in resp.getQueryPayload():
+ if list.getName()=='list':
+ dict['lists'].append(list.getAttr('name'))
+ else:
+ dict[list.getName()]=list.getAttr('name')
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict))
+ disp.SendAndCallForResponse(iq, _on_response)
-def getPrivacyList(disp, listname, cb):
+def getActiveAndDefaultPrivacyLists(disp):
+ iq = Iq('get', NS_PRIVACY)
+ def _on_response(resp):
+ dict = {'active': '', 'default': ''}
+ if not isResultNode(resp):
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False))
+ return
+ for list in resp.getQueryPayload():
+ if list.getName() == 'active':
+ dict['active'] = list.getAttr('name')
+ elif list.getName() == 'default':
+ dict['default'] = list.getAttr('name')
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict))
+ disp.SendAndCallForResponse(iq, _on_response)
+
+def getPrivacyList(disp, listname):
""" Requests specific privacy list listname. Returns list of XML nodes (rules)
taken from the server responce."""
def _on_response(resp):
- try:
- if isResultNode(resp):
- return cb(resp.getQueryPayload()[0])
- except:
- pass
- cb(False)
+ if not isResultNode(resp):
+ disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
+ return
+ disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp))
iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})])
disp.SendAndCallForResponse(iq, _on_response)
@@ -220,14 +229,25 @@ def setDefaultPrivacyList(disp, listname=None):
""" Sets the default privacy list as 'listname'. Returns true on success."""
return setActivePrivacyList(disp, listname,'default')
-def setPrivacyList(disp, list, cb):
+def setPrivacyList(disp, listname, tags):
""" Set the ruleset. 'list' should be the simpleXML node formatted
according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )
Returns true on success."""
- iq=Iq('set',NS_PRIVACY,payload=[list])
- _on_default_response(disp, iq, cb)
+ iq = Iq('set', NS_PRIVACY, xmlns = '')
+ list_query = iq.getTag('query').setTag('list', {'name': listname})
+ for item in tags:
+ if item.has_key('type') and item.has_key('value'):
+ item_tag = list_query.setTag('item', {'action': item['action'],
+ 'order': item['order'], 'type': item['type'], 'value': item['value']})
+ else:
+ item_tag = list_query.setTag('item', {'action': item['action'],
+ 'order': item['order']})
+ if item.has_key('child'):
+ for child_tag in item['child']:
+ item_tag.setTag(child_tag)
+ _on_default_response(disp, iq, None)
-def delPrivacyList(disp,listname, cb):
+def delPrivacyList(disp,listname):
""" Deletes privacy list 'listname'. Returns true on success."""
iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})])
- _on_default_response(disp, iq, cb)
+ _on_default_response(disp, iq, None)
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 9b0224e44..2ac25069d 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -62,11 +62,13 @@ NS_MUC ='http://jabber.org/protocol/muc'
NS_MUC_USER =NS_MUC+'#user'
NS_MUC_ADMIN =NS_MUC+'#admin'
NS_MUC_OWNER =NS_MUC+'#owner'
+NS_NICK ='http://jabber.org/protocol/nick' # JEP-0172
NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # JEP-0013
NS_PHYSLOC ='http://jabber.org/protocol/physloc' # JEP-0112
NS_PRESENCE ='presence' # Jabberd2
NS_PRIVACY ='jabber:iq:privacy'
NS_PRIVATE ='jabber:iq:private'
+NS_PROFILE ='http://jabber.org/protocol/profile' # JEP-0154
NS_PUBSUB ='http://jabber.org/protocol/pubsub' # JEP-0060
NS_REGISTER ='jabber:iq:register'
NS_ROSTER ='jabber:iq:roster'
@@ -82,7 +84,7 @@ NS_SIGNED ='jabber:x:signed' # JEP-00
NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
NS_STREAM ='http://affinix.com/jabber/stream'
NS_STREAMS ='http://etherx.jabber.org/streams'
-NS_TIME ='jabber:iq:time'
+NS_TIME ='jabber:iq:time' # JEP-0900
NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
NS_VACATION ='http://jabber.org/protocol/vacation'
NS_VCARD ='vcard-temp'
@@ -346,6 +348,13 @@ class Protocol(Node):
for tag in errtag.getChildren():
if tag.getName()<>'text': return tag.getName()
return errtag.getData()
+ def getErrorMsg(self):
+ """ Return the textual description of the error (if present) or the error condition """
+ errtag=self.getTag('error')
+ if errtag:
+ for tag in errtag.getChildren():
+ if tag.getName()=='text': return tag.getData()
+ return self.getError()
def getErrorCode(self):
""" Return the error code. Obsolette. """
return self.getTagAttr('error','code')
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index b21b436dd..ad3456e6a 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -371,8 +371,12 @@ class NonBlockingTLS(PlugIn):
PlugIn.PlugIn(self, owner)
DBG_LINE='NonBlockingTLS'
self.on_tls_start = on_tls_start
- if now:
- res = self._startSSL()
+ if now:
+ try:
+ res = self._startSSL()
+ except Exception, e:
+ self._owner.socket.pollend()
+ return
self.tls_start()
return res
if self._owner.Dispatcher.Stream.features:
@@ -434,7 +438,11 @@ class NonBlockingTLS(PlugIn):
self.DEBUG('Got starttls response: ' + self.starttls,'error')
return
self.DEBUG('Got starttls proceed response. Switching to TLS/SSL...','ok')
- self._startSSL()
+ try:
+ self._startSSL()
+ except Exception, e:
+ self._owner.socket.pollend()
+ return
self._owner.Dispatcher.PlugOut()
dispatcher_nb.Dispatcher().PlugIn(self._owner)
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
index 68cc14a41..12d46ab3a 100644
--- a/src/common/zeroconf/connection_handlers_zeroconf.py
+++ b/src/common/zeroconf/connection_handlers_zeroconf.py
@@ -33,8 +33,6 @@ import common.xmpp
from common import GnuPG
from common import helpers
from common import gajim
-from common import i18n
-_ = i18n._
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
index 1ff695585..544553158 100644
--- a/src/common/zeroconf/connection_zeroconf.py
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -48,9 +48,6 @@ from connection_handlers_zeroconf import *
USE_GPG = GnuPG.USE_GPG
-from common import i18n
-_ = i18n._
-
class ConnectionZeroconf(ConnectionHandlersZeroconf):
'''Connection class'''
def __init__(self, name):
diff --git a/src/config.py b/src/config.py
index 6093239f6..dd8e7bce3 100644
--- a/src/config.py
+++ b/src/config.py
@@ -16,7 +16,6 @@
##
import gtk
-import gtk.glade
import gobject
import os
import common.config
@@ -38,12 +37,6 @@ except:
from common import helpers
from common import gajim
from common import connection
-from common import i18n
-
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain (APP, i18n.DIR)
-gtk.glade.textdomain (APP)
#---------- PreferencesWindow class -------------#
class PreferencesWindow:
@@ -60,6 +53,7 @@ class PreferencesWindow:
'''Initialize Preferences window'''
self.xml = gtkgui_helpers.get_glade('preferences_window.glade')
self.window = self.xml.get_widget('preferences_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.iconset_combobox = self.xml.get_widget('iconset_combobox')
self.notify_on_new_message_radiobutton = self.xml.get_widget(
'notify_on_new_message_radiobutton')
@@ -124,7 +118,7 @@ class PreferencesWindow:
for dir in emoticons_list:
if dir != '.svn':
l.append(dir)
- l.append('Disabled')
+ l.append(_('Disabled'))
for i in xrange(len(l)):
model.append([l[i]])
if gajim.config.get('emoticons_theme') == l[i]:
@@ -256,11 +250,6 @@ class PreferencesWindow:
# try to set default font for the current desktop env
fontbutton = self.xml.get_widget('conversation_fontbutton')
if font == '':
- font = gtkgui_helpers.get_default_font()
- if font is not None:
- font = 'Sans 10'
- gajim.config.set('conversation_font', font)
- fontbutton.set_font_name(font)
fontbutton.set_sensitive(False)
self.xml.get_widget('default_chat_font').set_active(True)
else:
@@ -322,6 +311,8 @@ class PreferencesWindow:
commands = ('aplay', 'play', 'esdplay', 'artsplay')
for command in commands:
if helpers.is_in_path(command):
+ if command == 'aplay':
+ command += ' -q'
self.xml.get_widget('soundplayer_entry').set_text(command)
gajim.config.set('soundplayer', command)
break
@@ -385,6 +376,32 @@ class PreferencesWindow:
self.xml.get_widget('prompt_offline_status_message_checkbutton').\
set_active(st)
+ # Default Status messages
+ self.default_msg_tree = self.xml.get_widget('default_msg_treeview')
+ # (status, translated_status, message, enabled)
+ model = gtk.ListStore(str, str, str, bool)
+ self.default_msg_tree.set_model(model)
+ col = gtk.TreeViewColumn('Status')
+ self.default_msg_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, False)
+ col.set_attributes(renderer, text = 1)
+ col = gtk.TreeViewColumn('Message')
+ self.default_msg_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text = 2)
+ renderer.connect('edited', self.on_default_msg_cell_edited)
+ renderer.set_property('editable', True)
+ col = gtk.TreeViewColumn('Enabled')
+ self.default_msg_tree.append_column(col)
+ renderer = gtk.CellRendererToggle()
+ col.pack_start(renderer, False)
+ col.set_attributes(renderer, active = 3)
+ renderer.set_property('activatable', True)
+ renderer.connect('toggled', self.default_msg_toggled_cb)
+ self.fill_default_msg_treeview()
+
#Status messages
self.msg_tree = self.xml.get_widget('msg_treeview')
model = gtk.ListStore(str, str)
@@ -440,17 +457,22 @@ class PreferencesWindow:
# Notify user of new gmail e-mail messages,
# only show checkbox if user has a gtalk account
+ frame_gmail = self.xml.get_widget('frame_gmail')
notify_gmail_checkbutton = self.xml.get_widget('notify_gmail_checkbutton')
- notify_gmail_checkbutton.set_no_show_all(True)
+ notify_gmail_extra_checkbutton = self.xml.get_widget('notify_gmail_extra_checkbutton')
+ frame_gmail.set_no_show_all(True)
+
for account in gajim.config.get_per('accounts'):
jid = gajim.get_jid_from_account(account)
- if gajim.get_server_from_jid(jid) == 'gmail.com':
+ if gajim.get_server_from_jid(jid) in gajim.gmail_domains:
+ frame_gmail.show_all()
st = gajim.config.get('notify_on_new_gmail_email')
notify_gmail_checkbutton.set_active(st)
- notify_gmail_checkbutton.show()
+ st = gajim.config.get('notify_on_new_gmail_email_extra')
+ notify_gmail_extra_checkbutton.set_active(st)
break
else:
- notify_gmail_checkbutton.hide()
+ frame_gmail.hide()
self.xml.signal_autoconnect(self)
@@ -460,11 +482,14 @@ class PreferencesWindow:
self.on_msg_treemodel_row_changed)
self.msg_tree.get_model().connect('row-deleted',
self.on_msg_treemodel_row_deleted)
+ self.default_msg_tree.get_model().connect('row-changed',
+ self.on_default_msg_treemodel_row_changed)
self.theme_preferences = None
self.notebook.set_current_page(0)
self.window.show_all()
+ gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
def on_preferences_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
@@ -595,7 +620,23 @@ class PreferencesWindow:
gajim.config.set('use_speller', active)
gajim.interface.save_config()
if active:
- self.apply_speller()
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ tv = gtk.TextView()
+ try:
+ spell = gtkspell.Spell(tv, lang)
+ except:
+ dialogs.ErrorDialog(
+ _('Dictionary for lang %s not available') % lang,
+ _('You have to install %s dictionary to use spellchecking, or '
+ 'choose another language by setting the speller_language option.'
+ ) % lang)
+ gajim.config.set('use_speller', False)
+ widget.set_active(False)
+ else:
+ gajim.config.set('speller_language', lang)
+ self.apply_speller()
else:
self.remove_speller()
@@ -792,6 +833,36 @@ class PreferencesWindow:
def on_auto_xa_message_entry_changed(self, widget):
gajim.config.set('autoxa_message', widget.get_text().decode('utf-8'))
+ def fill_default_msg_treeview(self):
+ model = self.default_msg_tree.get_model()
+ model.clear()
+ status = []
+ for status_ in gajim.config.get_per('defaultstatusmsg'):
+ status.append(status_)
+ status.sort()
+ for status_ in status:
+ msg = gajim.config.get_per('defaultstatusmsg', status_, 'message')
+ enabled = gajim.config.get_per('defaultstatusmsg', status_, 'enabled')
+ iter = model.append()
+ uf_show = helpers.get_uf_show(status_)
+ model.set(iter, 0, status_, 1, uf_show, 2, msg, 3, enabled)
+
+ def on_default_msg_cell_edited(self, cell, row, new_text):
+ model = self.default_msg_tree.get_model()
+ iter = model.get_iter_from_string(row)
+ model.set_value(iter, 2, new_text)
+
+ def default_msg_toggled_cb(self, cell, path):
+ model = self.default_msg_tree.get_model()
+ model[path][3] = not model[path][3]
+
+ def on_default_msg_treemodel_row_changed(self, model, path, iter):
+ status = model[iter][0]
+ message = model[iter][2].decode('utf-8')
+ gajim.config.set_per('defaultstatusmsg', status, 'enabled',
+ model[iter][3])
+ gajim.config.set_per('defaultstatusmsg', status, 'message', message)
+
def save_status_messages(self, model):
for msg in gajim.config.get_per('statusmsg'):
gajim.config.del_per('statusmsg', msg)
@@ -846,18 +917,25 @@ class PreferencesWindow:
def on_send_os_info_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'send_os_info')
- def on_notify_gmail_checkbutton_toggled(self, widget):
+ def on_notify_gmail_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email')
+
+ def on_notify_gmail_extra_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email_extra')
def fill_msg_treeview(self):
self.xml.get_widget('delete_msg_button').set_sensitive(False)
model = self.msg_tree.get_model()
model.clear()
- for msg in gajim.config.get_per('statusmsg'):
+ preset_status = []
+ for msg_name in gajim.config.get_per('statusmsg'):
+ preset_status.append(msg_name)
+ preset_status.sort()
+ for msg_name in preset_status:
+ msg_text = gajim.config.get_per('statusmsg', msg_name, 'message')
+ msg_text = helpers.from_one_line(msg_text)
iter = model.append()
- val = gajim.config.get_per('statusmsg', msg, 'message')
- val = helpers.from_one_line(val)
- model.set(iter, 0, msg, 1, val)
+ model.set(iter, 0, msg_name, 1, msg_text)
def on_msg_cell_edited(self, cell, row, new_text):
model = self.msg_tree.get_model()
@@ -970,7 +1048,6 @@ class PreferencesWindow:
path_to_snd_file = widget.get_text()
model, iter = self.sound_tree.get_selection().get_selected()
model[iter][2] = path_to_snd_file # set new path to sounds_model
- model[iter][0] = True # set the sound to enabled
def on_play_button_clicked(self, widget):
model, iter = self.sound_tree.get_selection().get_selected()
@@ -1004,6 +1081,7 @@ class AccountModificationWindow:
def __init__(self, account):
self.xml = gtkgui_helpers.get_glade('account_modification_window.glade')
self.window = self.xml.get_widget('account_modification_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.account = account
# init proxy list
@@ -1152,7 +1230,7 @@ class AccountModificationWindow:
_('You are currently connected to the server'),
_('To change the account name, you must be disconnected.'))
return
- if len(gajim.awaiting_events[self.account]):
+ if len(gajim.events.get_events(self.account)):
dialogs.ErrorDialog(_('Unread events'),
_('To change the account name, you must read all pending '
'events.'))
@@ -1261,12 +1339,12 @@ class AccountModificationWindow:
if name != self.account:
#update variables
gajim.interface.instances[name] = gajim.interface.instances[self.account]
- gajim.awaiting_events[name] = gajim.awaiting_events[self.account]
gajim.nicks[name] = gajim.nicks[self.account]
gajim.block_signed_in_notifications[name] = \
gajim.block_signed_in_notifications[self.account]
gajim.groups[name] = gajim.groups[self.account]
gajim.gc_connected[name] = gajim.gc_connected[self.account]
+ gajim.automatic_rooms[name] = gajim.automatic_rooms[self.account]
gajim.newly_added[name] = gajim.newly_added[self.account]
gajim.to_be_removed[name] = gajim.to_be_removed[self.account]
gajim.sleeper_state[name] = gajim.sleeper_state[self.account]
@@ -1277,27 +1355,25 @@ class AccountModificationWindow:
gajim.status_before_autoaway[self.account]
gajim.contacts.change_account_name(self.account, name)
+ gajim.events.change_account_name(self.account, name)
- #upgrade account variable in opened windows
- for kind in ('infos', 'disco', 'chats', 'gc', 'gc_config'):
+ # change account variable for chat / gc controls
+ for ctrl in gajim.interface.msg_win_mgr.get_controls():
+ ctrl.account = name
+ # upgrade account variable in opened windows
+ for kind in ('infos', 'disco', 'gc_config'):
for j in gajim.interface.instances[name][kind]:
gajim.interface.instances[name][kind][j].account = name
- #upgrade account in systray
- if gajim.interface.systray_enabled:
- for list in gajim.interface.systray.jids:
- if list[0] == self.account:
- list[0] = name
-
# ServiceCache object keep old property account
if hasattr(gajim.connections[self.account], 'services_cache'):
gajim.connections[self.account].services_cache.account = name
del gajim.interface.instances[self.account]
- del gajim.awaiting_events[self.account]
del gajim.nicks[self.account]
del gajim.block_signed_in_notifications[self.account]
del gajim.groups[self.account]
del gajim.gc_connected[self.account]
+ del gajim.automatic_rooms[self.account]
del gajim.newly_added[self.account]
del gajim.to_be_removed[self.account]
del gajim.sleeper_state[self.account]
@@ -1316,11 +1392,13 @@ class AccountModificationWindow:
relogin_needed = False
else: # we're connected to the account we want to apply changes
# check if relogin is needed
- relogin_needed = self.options_changed_need_relogin(config,
+ relogin_needed = False
+ if self.options_changed_need_relogin(config,
('resource', 'proxy', 'usessl', 'keyname',
- 'use_custom_host', 'custom_host'))
+ 'use_custom_host', 'custom_host')):
+ relogin_needed = True
- if config['use_custom_host'] and (self.option_changed(config,
+ elif config['use_custom_host'] and (self.option_changed(config,
'custom_host') or self.option_changed(config, 'custom_port')):
relogin_needed = True
@@ -1400,7 +1478,6 @@ class AccountModificationWindow:
dialogs.ErrorDialog(_('No such account available'),
_('You must create your account before editing your personal information.'))
return
- jid = self.xml.get_widget('jid_entry').get_text().decode('utf-8')
# show error dialog if account is newly created (not in gajim.connections)
if not gajim.connections.has_key(self.account) or \
@@ -1409,12 +1486,12 @@ class AccountModificationWindow:
_('Without a connection, you can not edit your personal information.'))
return
- # in infos the key jid is OUR jid so we save the vcardwindow instance there
- if gajim.interface.instances[self.account]['infos'].has_key(jid):
- gajim.interface.instances[self.account]['infos'][jid].window.present()
- else:
- gajim.interface.instances[self.account]['infos'][jid] = \
- vcard.VcardWindow(jid, self.account, True)
+ if not gajim.connections[self.account].vcard_supported:
+ dialogs.ErrorDialog(_("Your server doesn't support Vcard"),
+ _("Your server can't save your personal information."))
+ return
+
+ gajim.interface.edit_own_details(self.account)
def on_manage_proxies_button_clicked(self, widget):
if gajim.interface.instances.has_key('manage_proxies'):
@@ -1438,7 +1515,7 @@ class AccountModificationWindow:
dialogs.ErrorDialog(_('Failed to get secret keys'),
_('There was a problem retrieving your OpenPGP secret keys.'))
return
- secret_keys['None'] = 'None'
+ secret_keys[_('None')] = _('None')
instance = dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
_('Choose your OpenPGP key'), secret_keys)
keyID = instance.run()
@@ -1447,7 +1524,7 @@ class AccountModificationWindow:
checkbutton = self.xml.get_widget('gpg_save_password_checkbutton')
gpg_key_label = self.xml.get_widget('gpg_key_label')
gpg_name_label = self.xml.get_widget('gpg_name_label')
- if keyID[0] == 'None':
+ if keyID[0] == _('None'):
gpg_key_label.set_text(_('No key selected'))
gpg_name_label.set_text('')
checkbutton.set_sensitive(False)
@@ -1495,6 +1572,7 @@ class ManageProxiesWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('manage_proxies_window.glade')
self.window = self.xml.get_widget('manage_proxies_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.proxies_treeview = self.xml.get_widget('proxies_treeview')
self.proxyname_entry = self.xml.get_widget('proxyname_entry')
self.init_list()
@@ -1659,6 +1737,7 @@ class AccountsWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('accounts_window.glade')
self.window = self.xml.get_widget('accounts_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.accounts_treeview = self.xml.get_widget('accounts_treeview')
self.modify_button = self.xml.get_widget('modify_button')
self.remove_button = self.xml.get_widget('remove_button')
@@ -1714,7 +1793,7 @@ class AccountsWindow:
if not iter:
return
account = model.get_value(iter, 0).decode('utf-8')
- if len(gajim.awaiting_events[account]):
+ if len(gajim.events.get_events(account)):
dialogs.ErrorDialog(_('Unread events'),
_('Read all pending events before removing this account.'))
return
@@ -1761,6 +1840,7 @@ class DataFormWindow:
self.config = config
self.xml = gtkgui_helpers.get_glade('data_form_window.glade')
self.window = self.xml.get_widget('data_form_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.config_vbox = self.xml.get_widget('config_vbox')
if config:
self.fill_vbox()
@@ -1910,10 +1990,11 @@ class ServiceRegistrationWindow(DataFormWindow):
else:
self.xml = gtkgui_helpers.get_glade('service_registration_window.glade')
self.window = self.xml.get_widget('service_registration_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
if infos.has_key('registered'):
- self.window.set_title(_('Edit %s' % service))
+ self.window.set_title(_('Edit %s') % service)
else:
- self.window.set_title(_('Register to %s' % service))
+ self.window.set_title(_('Register to %s') % service)
self.xml.get_widget('label').set_text(infos['instructions'])
self.entries = {}
self.draw_table()
@@ -1983,7 +2064,7 @@ class GroupchatConfigWindow(DataFormWindow):
self.room_jid = room_jid
self.remove_button = {}
self.affiliation_treeview = {}
- self.removed_jid = {}
+ self.list_init = {} # list at the begining
ui_list = {'outcast': _('Ban List'),
'member': _('Member List'),
'owner': _('Owner List'),
@@ -1993,7 +2074,7 @@ class GroupchatConfigWindow(DataFormWindow):
add_on_vbox = self.xml.get_widget('add_on_vbox')
for affiliation in ('outcast', 'member', 'owner', 'admin'):
- self.removed_jid[affiliation] = []
+ self.list_init[affiliation] = {}
hbox = gtk.HBox(spacing = 5)
add_on_vbox.pack_start(hbox, False)
@@ -2086,8 +2167,6 @@ class GroupchatConfigWindow(DataFormWindow):
return
model = self.affiliation_treeview[affiliation].get_model()
model.append((jid,'', '', ''))
- if jid in self.removed_jid[affiliation]:
- self.removed_jid[affiliation].remove(jid)
def on_remove_button_clicked(self, widget, affiliation):
selection = self.affiliation_treeview[affiliation].get_selection()
@@ -2100,7 +2179,6 @@ class GroupchatConfigWindow(DataFormWindow):
iter = model.get_iter(path)
jid = model[iter][0]
model.remove(iter)
- self.removed_jid[affiliation].append(jid)
self.remove_button[affiliation].set_sensitive(False)
def on_affiliation_treeview_cursor_changed(self, widget, affiliation):
@@ -2108,6 +2186,7 @@ class GroupchatConfigWindow(DataFormWindow):
def affiliation_list_received(self, affiliation, list):
'''Fill the affiliation treeview'''
+ self.list_init[affiliation] = list
if not affiliation:
return
tv = self.affiliation_treeview[affiliation]
@@ -2134,18 +2213,28 @@ class GroupchatConfigWindow(DataFormWindow):
self.config)
for affiliation in ('outcast', 'member', 'owner', 'admin'):
list = {}
+ actual_jid_list = []
model = self.affiliation_treeview[affiliation].get_model()
iter = model.get_iter_first()
+ # add new jid
while iter:
jid = model[iter][0].decode('utf-8')
- list[jid] = {'affiliation': affiliation}
- if affiliation == 'outcast':
- list[jid]['reason'] = model[iter][1].decode('utf-8')
+ actual_jid_list.append(jid)
+ if jid not in self.list_init[affiliation] or \
+ (affiliation == 'outcast' and self.list_init[affiliation]\
+ [jid].has_key('reason') and self.list_init[affiliation][jid]\
+ ['reason'] != model[iter][1].decode('utf-8')):
+ list[jid] = {'affiliation': affiliation}
+ if affiliation == 'outcast':
+ list[jid]['reason'] = model[iter][1].decode('utf-8')
iter = model.iter_next(iter)
- for jid in self.removed_jid[affiliation]:
- list[jid] = {'affiliation': 'none'}
- gajim.connections[self.account].send_gc_affiliation_list(self.room_jid,
- list)
+ # remove removed one
+ for jid in self.list_init[affiliation]:
+ if jid not in actual_jid_list:
+ list[jid] = {'affiliation': 'none'}
+ if list:
+ gajim.connections[self.account].send_gc_affiliation_list(
+ self.room_jid, list)
self.window.destroy()
#---------- RemoveAccountWindow class -------------#
@@ -2164,8 +2253,9 @@ class RemoveAccountWindow:
self.account = account
xml = gtkgui_helpers.get_glade('remove_account_window.glade')
self.window = xml.get_widget('remove_account_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.remove_and_unregister_radiobutton = xml.get_widget(
- 'remove_and_unregister_radiobutton')
+ 'remove_and_unregister_radiobutton')
self.window.set_title(_('Removing %s account') % self.account)
xml.signal_autoconnect(self)
self.window.show_all()
@@ -2198,7 +2288,7 @@ class RemoveAccountWindow:
self.dialog = None
if gajim.connections[self.account].connected:
self.dialog = dialogs.ConfirmationDialog(
- _('Account "%s" is connected to the server' % self.account),
+ _('Account "%s" is connected to the server') % self.account,
_('If you remove it, the connection will be lost.'),
on_response_ok = remove)
else:
@@ -2210,18 +2300,18 @@ class RemoveAccountWindow:
if not res:
return
# Close all opened windows
- gajim.interface.roster.close_all(gajim.interface.instances[self.account])
+ gajim.interface.roster.close_all(self.account)
gajim.connections[self.account].disconnect(on_purpose = True)
del gajim.connections[self.account]
gajim.config.del_per('accounts', self.account)
gajim.interface.save_config()
del gajim.interface.instances[self.account]
- del gajim.awaiting_events[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]
@@ -2243,6 +2333,7 @@ class ManageBookmarksWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('manage_bookmarks_window.glade')
self.window = self.xml.get_widget('manage_bookmarks_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
#Account-JID, RoomName, Room-JID, Autojoin, Passowrd, Nick, Show_Status
self.treestore = gtk.TreeStore(str, str, str, bool, str, str, str)
@@ -2541,8 +2632,11 @@ class AccountCreationWizardWindow:
# Connect events from comboboxentry.child
server_comboboxentry = self.xml.get_widget('server_comboboxentry')
- server_comboboxentry.child.connect('key_press_event',
- self.on_server_comboboxentry_key_press_event)
+ entry = server_comboboxentry.child
+ entry.connect('key_press_event',
+ self.on_server_comboboxentry_key_press_event)
+ completion = gtk.EntryCompletion()
+ entry.set_completion(completion)
# parse servers.xml
servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml')
@@ -2551,6 +2645,9 @@ class AccountCreationWizardWindow:
for server in servers:
servers_model.append((str(server[0]), int(server[1])))
+ completion.set_model(servers_model)
+ completion.set_text_column(0)
+
# Put servers into comboboxentries
server_comboboxentry.set_model(servers_model)
server_comboboxentry.set_text_column(0)
@@ -2563,7 +2660,8 @@ class AccountCreationWizardWindow:
self.finish_button = self.xml.get_widget('finish_button')
self.advanced_button = self.xml.get_widget('advanced_button')
self.finish_label = self.xml.get_widget('finish_label')
- self.go_online_checkbutton = self.xml.get_widget('go_online_checkbutton')
+ self.go_online_checkbutton = self.xml.get_widget(
+ 'go_online_checkbutton')
self.show_vcard_checkbutton = self.xml.get_widget(
'show_vcard_checkbutton')
self.progressbar = self.xml.get_widget('progressbar')
@@ -2581,7 +2679,8 @@ class AccountCreationWizardWindow:
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')
+ helpers.launch_browser_mailer('url',
+ 'http://www.jabber.org/network/oldnetwork.shtml')
def on_save_password_checkbutton_toggled(self, widget):
self.xml.get_widget('pass1_entry').grab_focus()
@@ -2837,12 +2936,12 @@ _('You can set advanced account options by pressing Advanced button, or later by
# update variables
gajim.interface.instances[self.account] = {'infos': {}, 'disco': {},
- 'chats': {}, 'gc': {}, 'gc_config': {}}
- gajim.awaiting_events[self.account] = {}
+ 'gc_config': {}}
gajim.connections[self.account].connected = 0
gajim.groups[self.account] = {}
gajim.contacts.add_account(self.account)
gajim.gc_connected[self.account] = {}
+ gajim.automatic_rooms[self.account] = {}
gajim.newly_added[self.account] = []
gajim.to_be_removed[self.account] = []
gajim.nicks[self.account] = config['name']
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
index 50c20d55f..888c9d2b3 100644
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -24,11 +24,11 @@
##
import gtk
-import gtk.glade
import pango
import gobject
import time
import sys
+import os
import tooltips
import dialogs
import locale
@@ -36,13 +36,8 @@ import locale
import gtkgui_helpers
from common import gajim
from common import helpers
-from common import i18n
from calendar import timegm
-
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
+from common.fuzzyclock import FuzzyClock
class ConversationTextview:
'''Class for the conversation textview (where user reads already said messages)
@@ -56,7 +51,7 @@ class ConversationTextview:
self.tv.set_accepts_tab(True)
self.tv.set_editable(False)
self.tv.set_cursor_visible(False)
- self.tv.set_wrap_mode(gtk.WRAP_WORD)
+ self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
self.tv.set_left_margin(2)
self.tv.set_right_margin(2)
self.handlers = {}
@@ -89,6 +84,14 @@ class ConversationTextview:
color = gajim.config.get('statusmsgcolor')
self.tagStatus.set_property('foreground', color)
+ colors = gajim.config.get('gc_nicknames_colors')
+ colors = colors.split(':')
+ for color in xrange(len(colors)):
+ tagname = 'gc_nickname_color_' + str(color)
+ tag = buffer.create_tag(tagname)
+ color = colors[color]
+ tag.set_property('foreground', color)
+
tag = buffer.create_tag('marked')
color = gajim.config.get('markedmsgcolor')
tag.set_property('foreground', color)
@@ -130,6 +133,10 @@ class ConversationTextview:
buffer.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER)
+ self.allow_focus_out_line = True
+ # holds the iter's offset which points to the end of --- line
+ self.focus_out_end_iter_offset = None
+
self.line_tooltip = tooltips.BaseTooltip()
def del_handlers(self):
@@ -180,9 +187,73 @@ class ConversationTextview:
def scroll_to_end_iter(self):
buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter()
+ if not end_iter:
+ return False
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once
+ def show_focus_out_line(self):
+ if not self.allow_focus_out_line:
+ # if room did not receive focus-in from the last time we added
+ # --- line then do not readd
+ return
+
+ print_focus_out_line = False
+ buffer = self.tv.get_buffer()
+
+ if self.focus_out_end_iter_offset is None:
+ # this happens only first time we focus out on this room
+ print_focus_out_line = True
+
+ else:
+ if self.focus_out_end_iter_offset != buffer.get_end_iter().\
+ get_offset():
+ # this means after last-focus something was printed
+ # (else end_iter's offset is the same as before)
+ # only then print ---- line (eg. we avoid printing many following
+ # ---- lines)
+ print_focus_out_line = True
+
+ if print_focus_out_line and buffer.get_char_count() > 0:
+ buffer.begin_user_action()
+
+ # remove previous focus out line if such focus out line exists
+ if self.focus_out_end_iter_offset is not None:
+ end_iter_for_previous_line = buffer.get_iter_at_offset(
+ self.focus_out_end_iter_offset)
+ begin_iter_for_previous_line = end_iter_for_previous_line.copy()
+ # img_char+1 (the '\n')
+ begin_iter_for_previous_line.backward_chars(2)
+
+ # remove focus out line
+ buffer.delete(begin_iter_for_previous_line,
+ end_iter_for_previous_line)
+
+ # add the new focus out line
+ # FIXME: Why is this loaded from disk everytime
+ path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
+ focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
+ end_iter = buffer.get_end_iter()
+ buffer.insert(end_iter, '\n')
+ buffer.insert_pixbuf(end_iter, focus_out_line_pixbuf)
+
+ end_iter = buffer.get_end_iter()
+ before_img_iter = end_iter.copy()
+ before_img_iter.backward_char() # one char back (an image also takes one char)
+ buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
+ #FIXME: remove this workaround when bug is fixed
+ # c http://bugzilla.gnome.org/show_bug.cgi?id=318569
+
+ self.allow_focus_out_line = False
+
+ # update the iter we hold to make comparison the next time
+ self.focus_out_end_iter_offset = buffer.get_end_iter().get_offset()
+
+ buffer.end_user_action()
+
+ # scroll to the end (via idle in case the scrollbar has appeared)
+ gobject.idle_add(self.scroll_to_end)
+
def show_line_tooltip(self):
pointer = self.tv.get_pointer()
x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0],
@@ -237,6 +308,7 @@ class ConversationTextview:
buffer = self.tv.get_buffer()
start, end = buffer.get_bounds()
buffer.delete(start, end)
+ self.focus_out_end_iter_offset = None
def visit_url_from_menuitem(self, widget, link):
'''basically it filters out the widget instance'''
@@ -572,9 +644,6 @@ class ConversationTextview:
other_tags_for_name = [], other_tags_for_time = [],
other_tags_for_text = [], subject = None, old_kind = None):
'''prints 'chat' type messages'''
- # kind = info, we print things as if it was a status: same color, ...
- if kind == 'info':
- kind = 'status'
buffer = self.tv.get_buffer()
buffer.begin_user_action()
end_iter = buffer.get_end_iter()
@@ -593,7 +662,7 @@ class ConversationTextview:
# We don't have tim for outgoing messages...
tim = time.localtime()
current_print_time = gajim.config.get('print_time')
- if current_print_time == 'always':
+ if current_print_time == 'always' and kind != 'info':
before_str = gajim.config.get('before_time')
after_str = gajim.config.get('after_time')
# get difference in days since epoch (86400 = 24*3600)
@@ -612,25 +681,38 @@ class ConversationTextview:
if day_str:
format += day_str + ' '
format += '%X' + after_str
- # format comes as unicode, because of day_str.
- # we convert it to the encoding that we want
- tim_format = time.strftime(format, tim).encode('utf-8')
+ tim_format = time.strftime(format, tim)
+ # if tim_format comes as unicode because of day_str.
+ # we convert it to the encoding that we want (and that is utf-8)
+ tim_format = helpers.ensure_utf8_string(tim_format)
+ tim_format = tim_format.encode('utf-8')
buffer.insert_with_tags_by_name(end_iter, tim_format + ' ',
*other_tags_for_time)
- elif current_print_time == 'sometimes':
+ elif current_print_time == 'sometimes' and kind != 'info':
every_foo_seconds = 60 * gajim.config.get(
'print_ichat_every_foo_minutes')
seconds_passed = time.mktime(tim) - self.last_time_printout
if seconds_passed > every_foo_seconds:
self.last_time_printout = time.mktime(tim)
end_iter = buffer.get_end_iter()
- tim_format = time.strftime('%H:%M', tim).decode(
- locale.getpreferredencoding())
+ if gajim.config.get('print_time_fuzzy') > 0:
+ fc = FuzzyClock()
+ fc.setTime(time.strftime('%H:%M', tim))
+ ft = fc.getFuzzyTime(gajim.config.get('print_time_fuzzy'))
+ tim_format = ft.decode(locale.getpreferredencoding())
+ else:
+ tim_format = time.strftime('%H:%M', tim).decode(
+ locale.getpreferredencoding())
+
buffer.insert_with_tags_by_name(end_iter, tim_format + '\n',
'time_sometimes')
+ # kind = info, we print things as if it was a status: same color, ...
+ if kind == 'info':
+ kind = 'status'
other_text_tag = self.detect_other_text_tag(text, kind)
text_tags = other_tags_for_text[:] # create a new list
if other_text_tag:
+ # note that color of /me may be overwritten in gc_control
text_tags.append(other_text_tag)
else: # not status nor /me
if gajim.config.get(
diff --git a/src/dbus_support.py b/src/dbus_support.py
index 9d8d74bd6..59e751c41 100644
--- a/src/dbus_support.py
+++ b/src/dbus_support.py
@@ -20,8 +20,6 @@ import sys
from common import gajim
from common import exceptions
-from common import i18n
-_ = i18n._
try:
import dbus
diff --git a/src/dialogs.py b/src/dialogs.py
index 699987a7b..6a8ec5de3 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -19,10 +19,8 @@
##
import gtk
-import gtk.glade
import gobject
import os
-import sys
import gtkgui_helpers
import vcard
@@ -42,88 +40,100 @@ from advanced import AdvancedConfigurationWindow
from common import gajim
from common import helpers
-from common import i18n
-
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain (APP, i18n.DIR)
-gtk.glade.textdomain (APP)
class EditGroupsDialog:
'''Class for the edit group dialog window'''
- def __init__(self, user, account):
+ def __init__(self, list_):
+ '''list_ is a list of (contact, account) tuples'''
self.xml = gtkgui_helpers.get_glade('edit_groups_dialog.glade')
self.dialog = self.xml.get_widget('edit_groups_dialog')
- self.account = account
- self.user = user
+ self.dialog.set_transient_for(gajim.interface.roster.window)
+ self.list_ = list_
self.changes_made = False
self.list = self.xml.get_widget('groups_treeview')
- self.xml.get_widget('nickname_label').set_markup(
- _("Contact's name: %s") % user.get_shown_name())
- self.xml.get_widget('jid_label').set_markup(
- _('JID: %s') % user.jid)
-
+ if len(list_) == 1:
+ contact = list_[0][0]
+ self.xml.get_widget('nickname_label').set_markup(
+ _("Contact name: %s") % contact.get_shown_name())
+ self.xml.get_widget('jid_label').set_markup(
+ _('JID: %s') % contact.jid)
+ else:
+ self.xml.get_widget('nickname_label').set_no_show_all(True)
+ self.xml.get_widget('nickname_label').hide()
+ self.xml.get_widget('jid_label').set_no_show_all(True)
+ self.xml.get_widget('jid_label').hide()
+
self.xml.signal_autoconnect(self)
self.init_list()
def run(self):
self.dialog.show_all()
if self.changes_made:
- gajim.connections[self.account].update_contact(self.user.jid,
- self.user.name, self.user.groups)
+ for (contact, account) in self.list_:
+ gajim.connections[account].update_contact(contact.jid, contact.name,
+ contact.groups)
def on_edit_groups_dialog_response(self, widget, response_id):
if response_id == gtk.RESPONSE_CLOSE:
self.dialog.destroy()
def update_contact(self):
- tag = gajim.contacts.get_metacontacts_tag(self.account, self.user.jid)
- if not tag:
- gajim.interface.roster.remove_contact(self.user, self.account)
- gajim.interface.roster.add_contact_to_roster(self.user.jid,
- self.account)
- gajim.connections[self.account].update_contact(self.user.jid,
- self.user.name, self.user.groups)
- return
- all_jid = gajim.contacts.get_metacontacts_jids(tag)
- for _account in all_jid:
- for _jid in all_jid[_account]:
- c = gajim.contacts.get_first_contact_from_jid(_account, _jid)
- if not c:
+ for (contact, account) in self.list_:
+ tag = gajim.contacts.get_metacontacts_tag(account, contact.jid)
+ if not tag:
+ gajim.interface.roster.remove_contact(contact, account)
+ gajim.interface.roster.add_contact_to_roster(contact.jid, account)
+ gajim.connections[account].update_contact(contact.jid, contact.name,
+ contact.groups)
+ continue
+ all_jid = gajim.contacts.get_metacontacts_jids(tag)
+ for _account in all_jid:
+ if not gajim.interface.roster.regroup and _account != account:
continue
- gajim.interface.roster.remove_contact(c, _account)
- gajim.interface.roster.add_contact_to_roster(_jid, _account)
- gajim.connections[_account].update_contact(_jid, c.name, c.groups)
+ for _jid in all_jid[_account]:
+ c = gajim.contacts.get_first_contact_from_jid(_account, _jid)
+ if not c:
+ continue
+ gajim.interface.roster.remove_contact(c, _account)
+ gajim.interface.roster.add_contact_to_roster(_jid, _account)
+ gajim.connections[_account].update_contact(_jid, c.name,
+ c.groups)
def remove_group(self, group):
- '''add group group to self.user and all his brothers'''
- tag = gajim.contacts.get_metacontacts_tag(self.account, self.user.jid)
- if not tag:
- if group in self.user.groups:
- self.user.groups.remove(group)
- return
- all_jid = gajim.contacts.get_metacontacts_jids(tag)
- for _account in all_jid:
- for _jid in all_jid[_account]:
- contacts = gajim.contacts.get_contact(_account, _jid)
- for contact in contacts:
- if group in contact.groups:
- contact.groups.remove(group)
+ '''remove group group from all contacts and all their brothers'''
+ for (contact, account) in self.list_:
+ tag = gajim.contacts.get_metacontacts_tag(account, contact.jid)
+ if not tag:
+ if group in contact.groups:
+ contact.groups.remove(group)
+ continue
+ all_jid = gajim.contacts.get_metacontacts_jids(tag)
+ for _account in all_jid:
+ if not gajim.interface.roster.regroup and _account != account:
+ continue
+ for _jid in all_jid[_account]:
+ contacts = gajim.contacts.get_contact(_account, _jid)
+ for c in contacts:
+ if group in c.groups:
+ c.groups.remove(group)
def add_group(self, group):
- '''add group group to self.user and all his brothers'''
- tag = gajim.contacts.get_metacontacts_tag(self.account, self.user.jid)
- if not tag:
- if group not in self.user.groups:
- self.user.groups.append(group)
- return
- all_jid = gajim.contacts.get_metacontacts_jids(tag)
- for _account in all_jid:
- for _jid in all_jid[_account]:
- contacts = gajim.contacts.get_contact(_account, _jid)
- for contact in contacts:
- if not group in contact.groups:
- contact.groups.append(group)
+ '''add group group to all contacts and all their brothers'''
+ for (contact, account) in self.list_:
+ tag = gajim.contacts.get_metacontacts_tag(account, contact.jid)
+ if not tag:
+ if group not in contact.groups:
+ contact.groups.append(group)
+ continue
+ all_jid = gajim.contacts.get_metacontacts_jids(tag)
+ for _account in all_jid:
+ if not gajim.interface.roster.regroup and _account != account:
+ continue
+ for _jid in all_jid[_account]:
+ contacts = gajim.contacts.get_contact(_account, _jid)
+ for c in contacts:
+ if not group in c.groups:
+ c.groups.append(group)
def on_add_button_clicked(self, widget):
group = self.xml.get_widget('group_entry').get_text().decode('utf-8')
@@ -137,7 +147,7 @@ class EditGroupsDialog:
return
iter = model.iter_next(iter)
self.changes_made = True
- model.append((group, True))
+ model.append((group, True, False))
self.add_group(group)
self.update_contact()
self.init_list() # Re-draw list to sort new item
@@ -145,7 +155,11 @@ class EditGroupsDialog:
def group_toggled_cb(self, cell, path):
self.changes_made = True
model = self.list.get_model()
- model[path][1] = not model[path][1]
+ if model[path][2]:
+ model[path][2] = False
+ model[path][1] = True
+ else:
+ model[path][1] = not model[path][1]
group = model[path][0].decode('utf-8')
if model[path][1]:
self.add_group(group)
@@ -154,23 +168,39 @@ class EditGroupsDialog:
self.update_contact()
def init_list(self):
- store = gtk.ListStore(str, bool)
+ store = gtk.ListStore(str, bool, bool)
self.list.set_model(store)
for column in self.list.get_columns(): # Clear treeview when re-drawing
self.list.remove_column(column)
- groups = [] # Store accounts in a list so we can sort them
- for g in gajim.groups[self.account].keys():
- if g in helpers.special_groups:
- continue
- in_group = False
- if g in self.user.groups:
- in_group = True
- groups.append([g, in_group])
- groups.sort()
- for group in groups:
+ accounts = []
+ # Store groups in a list so we can sort them and the number of contacts in
+ # it
+ groups = {}
+ for (contact, account) in self.list_:
+ if account not in accounts:
+ accounts.append(account)
+ for g in gajim.groups[account].keys():
+ if g in helpers.special_groups:
+ continue
+ if g in groups:
+ continue
+ groups[g] = 0
+ for g in contact.groups:
+ groups[g] += 1
+ group_list = groups.keys()
+ group_list.sort()
+ for group in group_list:
iter = store.append()
- store.set(iter, 0, group[0]) # Group name
- store.set(iter, 1, group[1]) # In group boolean
+ store.set(iter, 0, group) # Group name
+ if groups[group] == 0:
+ store.set(iter, 1, False)
+ else:
+ store.set(iter, 1, True)
+ if groups[group] == len(self.list_):
+ # all contacts are in this group
+ store.set(iter, 2, False)
+ else:
+ store.set(iter, 2, True)
column = gtk.TreeViewColumn(_('Group'))
column.set_expand(True)
self.list.append_column(column)
@@ -185,7 +215,7 @@ class EditGroupsDialog:
column.pack_start(renderer)
renderer.set_property('activatable', True)
renderer.connect('toggled', self.group_toggled_cb)
- column.set_attributes(renderer, active = 1)
+ column.set_attributes(renderer, active = 1, inconsistent = 2)
class PassphraseDialog:
'''Class for Passphrase dialog'''
@@ -224,6 +254,7 @@ class ChooseGPGKeyDialog:
prompt_label = xml.get_widget('prompt_label')
prompt_label.set_text(prompt_text)
model = gtk.ListStore(str, str)
+ model.set_sort_func(1, self.sort_keys)
model.set_sort_column_id(1, gtk.SORT_ASCENDING)
self.keys_treeview.set_model(model)
#columns
@@ -236,6 +267,17 @@ class ChooseGPGKeyDialog:
self.fill_tree(secret_keys, selected)
self.window.show_all()
+ def sort_keys(self, model, iter1, iter2):
+ value1 = model[iter1][1]
+ value2 = model[iter2][1]
+ if value1 == _('None'):
+ return -1
+ elif value2 == _('None'):
+ return 1
+ elif value1 < value2:
+ return -1
+ return 1
+
def run(self):
rep = self.window.run()
if rep == gtk.RESPONSE_OK:
@@ -262,6 +304,7 @@ class ChangeStatusMessageDialog:
self.show = show
self.xml = gtkgui_helpers.get_glade('change_status_message_dialog.glade')
self.window = self.xml.get_widget('change_status_message_dialog')
+ self.window.set_transient_for(gajim.interface.roster.window)
if show:
uf_show = helpers.get_uf_show(show)
title_text = _('%s Status Message') % uf_show
@@ -362,7 +405,14 @@ class ChangeStatusMessageDialog:
class AddNewContactWindow:
'''Class for AddNewContactWindow'''
- def __init__(self, account = None, jid = None):
+ uid_labels = {'jabber': _('Jabber ID'),
+ 'aim': _('AIM Address'),
+ 'gadu-gadu': _('GG Number'),
+ 'icq': _('ICQ Number'),
+ 'msn': _('MSN Address'),
+ 'yahoo': _('Yahoo! Address')}
+ def __init__(self, account = None, jid = None, user_nick = None,
+ group = None):
self.account = account
if account == None:
# fill accounts with active accounts
@@ -376,80 +426,123 @@ class AddNewContactWindow:
self.account = account
else:
accounts = [self.account]
+ if self.account:
+ location = gajim.interface.instances[self.account]
+ else:
+ location = gajim.interface.instances
+ if location.has_key('add_contact'):
+ location['add_contact'].window.present()
+ # An instance is already opened
+ return
+ location['add_contact'] = self
self.xml = gtkgui_helpers.get_glade('add_new_contact_window.glade')
- self.account_combobox = self.xml.get_widget('account_combobox')
- self.account_hbox = self.xml.get_widget('account_hbox')
- self.account_label = self.xml.get_widget('account_label')
self.window = self.xml.get_widget('add_new_contact_window')
- self.uid_entry = self.xml.get_widget('uid_entry')
- self.protocol_combobox = self.xml.get_widget('protocol_combobox')
- self.protocol_hbox = self.xml.get_widget('protocol_hbox')
- self.jid_entry = self.xml.get_widget('jid_entry')
- self.nickname_entry = self.xml.get_widget('nickname_entry')
+ for w in ('account_combobox', 'account_hbox', 'account_label',
+ 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox',
+ 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow',
+ 'register_hbox', 'subscription_table', 'add_button',
+ 'message_textview', 'connected_label', 'group_comboboxentry'):
+ self.__dict__[w] = self.xml.get_widget(w)
if account and len(gajim.connections) >= 2:
prompt_text =\
_('Please fill in the data of the contact you want to add in account %s') %account
else:
prompt_text = _('Please fill in the data of the contact you want to add')
self.xml.get_widget('prompt_label').set_text(prompt_text)
- self.old_uid_value = ''
- liststore = gtk.ListStore(str, str)
- liststore.append(['Jabber', ''])
- self.agents = ['Jabber']
- jid_agents = []
+ self.agents = {'jabber': []}
+ # types to which we are not subscribed but account has an agent for it
+ self.available_types = []
for acct in accounts:
for j in gajim.contacts.get_jid_list(acct):
contact = gajim.contacts.get_first_contact_from_jid(acct, j)
- if _('Transports') in contact.groups and contact.show != 'offline' and\
- contact.show != 'error':
- jid_agents.append(j)
- for a in jid_agents:
- if a.find('aim') > -1:
- name = 'AIM'
- elif a.find('icq') > -1:
- name = 'ICQ'
- elif a.find('msn') > -1:
- name = 'MSN'
- elif a.find('yahoo') > -1:
- name = 'Yahoo!'
- else:
- name = a
- liststore.append([name, a])
- self.agents.append(name)
- self.protocol_combobox.set_model(liststore)
- self.protocol_combobox.set_active(0)
- self.fill_jid()
- if jid:
- self.jid_entry.set_text(jid)
- self.uid_entry.set_sensitive(False)
- jid_splited = jid.split('@')
- if jid_splited[1] in jid_agents:
- uid = jid_splited[0].replace('%', '@')
- self.uid_entry.set_text(uid)
- self.protocol_combobox.set_active(jid_agents.index(jid_splited[1])\
- + 1)
- else:
- self.uid_entry.set_text(jid)
- self.protocol_combobox.set_active(0)
- self.set_nickname()
- self.nickname_entry.grab_focus()
- self.group_comboboxentry = self.xml.get_widget('group_comboboxentry')
+ if _('Transports') in contact.groups:
+ type_ = gajim.get_transport_name_from_jid(j)
+ if self.agents.has_key(type_):
+ self.agents[type_].append(j)
+ else:
+ self.agents[type_] = [j]
+ # Now add the one to which we can register
+ for acct in accounts:
+ for type_ in gajim.connections[account].available_transports:
+ if type_ in self.agents:
+ continue
+ self.agents[type_] = []
+ for jid_ in gajim.connections[account].available_transports[type_]:
+ self.agents[type_].append(jid_)
+ self.available_types.append(type_)
liststore = gtk.ListStore(str)
self.group_comboboxentry.set_model(liststore)
+ liststore = gtk.ListStore(str, str)
+ uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu',
+ 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'}
+ # Jabber as first
+ liststore.append(['Jabber', 'jabber'])
+ for type_ in self.agents:
+ if type_ == 'jabber':
+ continue
+ if type_ in uf_type:
+ liststore.append([uf_type[type_], type_])
+ else:
+ liststore.append([type_, type_])
+ self.protocol_combobox.set_model(liststore)
+ self.protocol_combobox.set_active(0)
+ self.protocol_jid_combobox.set_sensitive(False)
+ self.subscription_table.set_no_show_all(True)
+ self.message_scrolledwindow.set_no_show_all(True)
+ self.register_hbox.set_no_show_all(True)
+ self.register_hbox.hide()
+ self.connected_label.set_no_show_all(True)
+ self.connected_label.hide()
+ liststore = gtk.ListStore(str)
+ self.protocol_jid_combobox.set_model(liststore)
+ self.xml.signal_autoconnect(self)
+ if jid:
+ type_ = gajim.get_transport_name_from_jid(jid) or 'jabber'
+ if type_ == 'jabber':
+ self.uid_entry.set_text(jid)
+ else:
+ uid, transport = gajim.get_room_name_and_server_from_room_jid(jid)
+ self.uid_entry.set_text(uid.replace('%', '@', 1))
+ #set protocol_combobox
+ model = self.protocol_combobox.get_model()
+ iter = model.get_iter_first()
+ i = 0
+ while iter:
+ if model[iter][1] == type_:
+ self.protocol_combobox.set_active(i)
+ break
+ iter = model.iter_next(iter)
+ i += 1
+
+ # set protocol_jid_combobox
+ self.protocol_combobox.set_active(0)
+ model = self.protocol_jid_combobox.get_model()
+ iter = model.get_iter_first()
+ i = 0
+ while iter:
+ if model[iter][0] == transport:
+ self.protocol_combobox.set_active(i)
+ break
+ iter = model.iter_next(iter)
+ i += 1
+ if user_nick:
+ self.nickname_entry.set_text(user_nick)
+ self.nickname_entry.grab_focus()
+ else:
+ self.uid_entry.grab_focus()
group_names = []
for acct in accounts:
for g in gajim.groups[acct].keys():
if g not in helpers.special_groups and g not in group_names:
group_names.append(g)
- self.group_comboboxentry.append_text(g)
+ group_names.sort()
+ i = 0
+ for g in group_names:
+ self.group_comboboxentry.append_text(g)
+ if group == g:
+ self.group_comboboxentry.set_active(i)
+ i += 1
- if not jid_agents:
- # There are no transports, so hide the protocol combobox and label
- self.protocol_hbox.hide()
- self.protocol_hbox.set_no_show_all(True)
- protocol_label = self.xml.get_widget('protocol_label')
- protocol_label.hide()
- protocol_label.set_no_show_all(True)
if self.account:
self.account_label.hide()
self.account_hbox.hide()
@@ -461,9 +554,19 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
liststore.append([acct, acct])
self.account_combobox.set_model(liststore)
self.account_combobox.set_active(0)
- self.xml.signal_autoconnect(self)
self.window.show_all()
+ def on_add_new_contact_window_destroy(self, widget):
+ if self.account:
+ location = gajim.interface.instances[self.account]
+ else:
+ location = gajim.interface.instances
+ del location['add_contact']
+
+ def on_register_button_clicked(self, widget):
+ jid = self.protocol_jid_combobox.get_active_text().decode('utf-8')
+ gajim.connections[self.account].request_register_agent_info(jid)
+
def on_add_new_contact_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape: # ESCAPE
self.window.destroy()
@@ -472,13 +575,20 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
'''When Cancel button is clicked'''
self.window.destroy()
- def on_subscribe_button_clicked(self, widget):
+ def on_add_button_clicked(self, widget):
'''When Subscribe button is clicked'''
- jid = self.jid_entry.get_text().decode('utf-8')
- nickname = self.nickname_entry.get_text().decode('utf-8')
+ jid = self.uid_entry.get_text().decode('utf-8')
if not jid:
return
-
+
+ model = self.protocol_combobox.get_model()
+ iter = self.protocol_combobox.get_active_iter()
+ type_ = model[iter][1]
+ if type_ != 'jabber':
+ transport = self.protocol_jid_combobox.get_active_text().decode(
+ 'utf-8')
+ jid = jid.replace('@', '%') + '@' + transport
+
# check if jid is conform to RFC and stringprep it
try:
jid = helpers.parse_jid(jid)
@@ -493,6 +603,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
ErrorDialog(pritext, _('The user ID must not contain a resource.'))
return
+ nickname = self.nickname_entry.get_text().decode('utf-8') or ''
# get value of account combobox, if account was not specified
if not self.account:
model = self.account_combobox.get_model()
@@ -507,57 +618,81 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
_('This contact is already listed in your roster.'))
return
- message_buffer = self.xml.get_widget('message_textview').get_buffer()
- start_iter = message_buffer.get_start_iter()
- end_iter = message_buffer.get_end_iter()
- message = message_buffer.get_text(start_iter, end_iter).decode('utf-8')
+ if type_ == 'jabber':
+ message_buffer = self.message_textview.get_buffer()
+ start_iter = message_buffer.get_start_iter()
+ end_iter = message_buffer.get_end_iter()
+ message = message_buffer.get_text(start_iter, end_iter).decode('utf-8')
+ else:
+ message= ''
group = self.group_comboboxentry.child.get_text().decode('utf-8')
auto_auth = self.xml.get_widget('auto_authorize_checkbutton').get_active()
gajim.interface.roster.req_sub(self, jid, message, self.account,
group = group, pseudo = nickname, auto_auth = auto_auth)
self.window.destroy()
-
- def fill_jid(self):
- model = self.protocol_combobox.get_model()
- index = self.protocol_combobox.get_active()
- jid = self.uid_entry.get_text().decode('utf-8').strip()
- if index > 0: # it's not jabber but a transport
- jid = jid.replace('@', '%')
- agent = model[index][1].decode('utf-8')
- if agent:
- jid += '@' + agent
- self.jid_entry.set_text(jid)
def on_protocol_combobox_changed(self, widget):
- self.fill_jid()
+ model = widget.get_model()
+ iter = widget.get_active_iter()
+ type_ = model[iter][1]
+ model = self.protocol_jid_combobox.get_model()
+ model.clear()
+ if len(self.agents[type_]):
+ for jid_ in self.agents[type_]:
+ model.append([jid_])
+ self.protocol_jid_combobox.set_active(0)
+ self.protocol_jid_combobox.set_sensitive(True)
+ else:
+ self.protocol_jid_combobox.set_sensitive(False)
+ if type_ in self.uid_labels:
+ self.uid_label.set_text(self.uid_labels[type_])
+ else:
+ self.uid_label.set_text(_('User ID'))
+ if type_ == 'jabber':
+ self.message_scrolledwindow.show()
+ else:
+ self.message_scrolledwindow.hide()
+ if type_ in self.available_types:
+ self.register_hbox.set_no_show_all(False)
+ self.register_hbox.show_all()
+ self.connected_label.hide()
+ self.subscription_table.hide()
+ self.add_button.set_sensitive(False)
+ else:
+ self.register_hbox.hide()
+ if type_ != 'jabber':
+ jid = self.protocol_jid_combobox.get_active_text()
+ contact = gajim.contacts.get_first_contact_from_jid(self.account,
+ jid)
+ if contact.show in ('offline', 'error'):
+ self.subscription_table.hide()
+ self.connected_label.show()
+ self.add_button.set_sensitive(False)
+ return
+ self.subscription_table.set_no_show_all(False)
+ self.subscription_table.show_all()
+ self.connected_label.hide()
+ self.add_button.set_sensitive(True)
- def guess_agent(self):
- uid = self.uid_entry.get_text().decode('utf-8')
- model = self.protocol_combobox.get_model()
-
- #If login contains only numbers, it's probably an ICQ number
- if uid.isdigit():
- if 'ICQ' in self.agents:
- self.protocol_combobox.set_active(self.agents.index('ICQ'))
- return
+ def transport_signed_in(self, jid):
+ if self.protocol_jid_combobox.get_active_text() == jid:
+ self.register_hbox.hide()
+ self.connected_label.hide()
+ self.subscription_table.set_no_show_all(False)
+ self.subscription_table.show_all()
+ self.add_button.set_sensitive(True)
- def set_nickname(self):
- uid = self.uid_entry.get_text().decode('utf-8')
- nickname = self.nickname_entry.get_text().decode('utf-8')
- if nickname == self.old_uid_value:
- self.nickname_entry.set_text(uid.split('@')[0])
-
- def on_uid_entry_changed(self, widget):
- uid = self.uid_entry.get_text().decode('utf-8')
- self.guess_agent()
- self.set_nickname()
- self.fill_jid()
- self.old_uid_value = uid.split('@')[0]
+ def transport_signed_out(self, jid):
+ if self.protocol_jid_combobox.get_active_text() == jid:
+ self.subscription_table.hide()
+ self.connected_label.show()
+ self.add_button.set_sensitive(False)
class AboutDialog:
'''Class for about dialog'''
def __init__(self):
dlg = gtk.AboutDialog()
+ dlg.set_transient_for(gajim.interface.roster.window)
dlg.set_name('Gajim')
dlg.set_version(gajim.version)
s = u'Copyright © 2003-2006 Gajim Team'
@@ -565,30 +700,28 @@ class AboutDialog:
text = open('../COPYING').read()
dlg.set_license(text)
- #FIXME: do versions strings translatable after .10
- #FIXME: use %s then
- dlg.set_comments(_('A GTK+ jabber client') + '\nGTK+ Version: ' + \
- self.tuple2str(gtk.gtk_version) + '\nPyGTK Version: ' + \
- self.tuple2str(gtk.pygtk_version))
- dlg.set_website('http://www.gajim.org')
-
- #FIXME: do current devs a translatable string
- authors = [
- 'Current Developers:',
- 'Yann Le Boulanger ',
- 'Dimitur Kirov ',
- 'Travis Shirk ',
- '',
- _('Past Developers:'),
- 'Nikos Kouremenos ',
- 'Vincent Hanquez ',
- '',
- _('THANKS:'),
- ]
+ dlg.set_comments('%s\n%s %s\n%s %s'
+ % (_('A GTK+ jabber client'), \
+ _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \
+ _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version)))
+ dlg.set_website('http://www.gajim.org/')
+ authors = []
+ authors_file = open('../AUTHORS').read()
+ authors_file = authors_file.split('\n')
+ for author in authors_file:
+ if author == 'CURRENT DEVELOPERS:':
+ authors.append(_('Current Developers:'))
+ elif author == 'PAST DEVELOPERS:':
+ authors.append('\n' + _('Past Developers:'))
+ elif author != '': # Real author line
+ authors.append(author)
+
+ authors.append('\n' + _('THANKS:'))
+
text = open('../THANKS').read()
text_splitted = text.split('\n')
- text = '\n'.join(text_splitted[:-2]) # remove one english setence
+ text = '\n'.join(text_splitted[:-2]) # remove one english sentence
# and add it manually as translatable
text += '\n%s\n' % _('Last but not least, we would like to thank all '
'the package maintainers.')
@@ -728,6 +861,12 @@ class FileChooserDialog(gtk.FileChooserDialog):
def just_destroy(self, widget):
self.destroy()
+class BindPortError(HigDialog):
+ def __init__(self, port):
+ ErrorDialog(_('Unable to bind to port %s.') % port,
+ _('Maybe you have another running instance of Gajim. '
+ 'File Transfer will be canceled.'))
+
class ConfirmationDialog(HigDialog):
'''HIG compliant confirmation dialog.'''
def __init__(self, pritext, sectext='', on_response_ok = None,
@@ -860,11 +999,12 @@ ok_handler = None):
return response
class SubscriptionRequestWindow:
- def __init__(self, jid, text, account):
+ def __init__(self, jid, text, account, user_nick = None):
xml = gtkgui_helpers.get_glade('subscription_request_window.glade')
self.window = xml.get_widget('subscription_request_window')
self.jid = jid
self.account = account
+ self.user_nick = user_nick
if len(gajim.connections) >= 2:
prompt_text = _('Subscription request for account %s from %s')\
% (account, self.jid)
@@ -883,7 +1023,7 @@ class SubscriptionRequestWindow:
gajim.connections[self.account].send_authorization(self.jid)
self.window.destroy()
if self.jid not in gajim.contacts.get_jid_list(self.account):
- AddNewContactWindow(self.account, self.jid)
+ AddNewContactWindow(self.account, self.jid, self.user_nick)
def on_contact_info_button_clicked(self, widget):
'''ask vcard'''
@@ -905,8 +1045,18 @@ class SubscriptionRequestWindow:
self.window.destroy()
class JoinGroupchatWindow:
- def __init__(self, account, server = '', room = '', nick = ''):
+ def __init__(self, account, server = '', room = '', nick = '',
+ automatic = False):
+ '''automatic is a dict like {'invities': []}
+ If automatic is not empty, this means room must be automaticaly configured
+ and when done, invities must be automatically invited'''
+ if server and room:
+ jid = room + '@' + server
+ if jid in gajim.gc_connected[account] and gajim.gc_connected[account][jid]:
+ ErrorDialog(_('You are already in room %s') % jid)
+ raise RuntimeError, 'You are already in this room'
self.account = account
+ self.automatic = automatic
if nick == '':
nick = gajim.nicks[self.account]
if gajim.connections[account].connected < 2:
@@ -1007,10 +1157,12 @@ _('You can not join a group chat unless you are connected.'))
def on_join_button_clicked(self, widget):
'''When Join button is clicked'''
- nickname = self.xml.get_widget('nickname_entry').get_text().decode('utf-8')
+ nickname = self.xml.get_widget('nickname_entry').get_text().decode(
+ 'utf-8')
room = self.xml.get_widget('room_entry').get_text().decode('utf-8')
server = self.xml.get_widget('server_entry').get_text().decode('utf-8')
- password = self.xml.get_widget('password_entry').get_text().decode('utf-8')
+ password = self.xml.get_widget('password_entry').get_text().decode(
+ 'utf-8')
jid = '%s@%s' % (room, server)
try:
jid = helpers.parse_jid(jid)
@@ -1025,7 +1177,9 @@ _('You can not join a group chat unless you are connected.'))
if len(self.recently_groupchat) > 10:
self.recently_groupchat = self.recently_groupchat[0:10]
gajim.config.set('recently_groupchat', ' '.join(self.recently_groupchat))
-
+
+ if self.automatic:
+ gajim.automatic_rooms[self.account][jid] = self.automatic
gajim.interface.roster.join_gc_room(self.account, jid, nickname, password)
self.window.destroy()
@@ -1064,11 +1218,20 @@ class NewChatDialog(InputDialog):
if gajim.connections[self.account].connected <= 1:
#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
if self.completion_dict.has_key(jid):
jid = self.completion_dict[jid].jid
+ else:
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, e:
+ ErrorDialog(_('Invalid JID'), e[0])
+ return
+ except:
+ ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid)
+ return
gajim.interface.roster.new_chat_from_jid(self.account, jid)
class ChangePasswordDialog:
@@ -1261,12 +1424,15 @@ class SingleMessageWindow:
self.to_entry.set_text(to)
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send':
try:
- gtkspell.Spell(self.conversation_textview.tv)
- gtkspell.Spell(self.message_textview)
+ spell1 = gtkspell.Spell(self.conversation_textview.tv)
+ spell2 = gtkspell.Spell(self.message_textview)
+ lang = gajim.config.get('speller_language')
+ if lang:
+ spell1.set_language(lang)
+ spell2.set_language(lang)
except gobject.GError, msg:
- #FIXME: add a ui for this use spell.set_language()
ErrorDialog(unicode(msg), _('If that is not your language for which you want to highlight misspelled words, then please set your $LANG as appropriate. Eg. for French do export LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to make it global in /etc/profile.\n\nHighlighting misspelled words feature will not be used'))
gajim.config.set('use_speller', False)
@@ -1334,8 +1500,10 @@ class SingleMessageWindow:
def prepare_widgets_for(self, action):
if len(gajim.connections) > 1:
- #FIXME: for Received with should become 'in'
- title = _('Single Message with account %s') % self.account
+ if action == 'send':
+ title = _('Single Message using account %s') % self.account
+ else:
+ title = _('Single Message in account %s') % self.account
else:
title = _('Single Message')
@@ -1404,7 +1572,7 @@ class SingleMessageWindow:
if gajim.connections[self.account].connected <= 1:
# 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
to_whom_jid = self.to_entry.get_text().decode('utf-8')
if self.completion_dict.has_key(to_whom_jid):
@@ -1431,7 +1599,7 @@ class SingleMessageWindow:
def on_reply_button_clicked(self, widget):
# we create a new blank window to send and we preset RE: and to jid
self.subject = _('RE: %s') % self.subject
- self.message = _('%s wrote:\n' % self.from_whom) + self.message
+ self.message = _('%s wrote:\n') % self.from_whom + self.message
# add > at the begining of each line
self.message = self.message.replace('\n', '\n> ') + '\n\n'
self.window.destroy()
@@ -1498,8 +1666,10 @@ class XMLConsoleWindow:
def scroll_to_end(self, ):
parent = self.stanzas_log_textview.get_parent()
buffer = self.stanzas_log_textview.get_buffer()
- self.stanzas_log_textview.scroll_to_mark(buffer.get_mark('end'), 0, True,
- 0, 1)
+ end_mark = buffer.get_mark('end')
+ if not end_mark:
+ return False
+ self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1)
adjustment = parent.get_hadjustment()
adjustment.set_value(0)
return False
@@ -1526,7 +1696,7 @@ class XMLConsoleWindow:
if gajim.connections[self.account].connected <= 1:
#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')
@@ -1554,6 +1724,406 @@ class XMLConsoleWindow:
# it's expanded!!
self.input_textview.grab_focus()
+class PrivacyListWindow:
+ def __init__(self, account, privacy_list, list_type):
+ '''list_type can be 0 if list is created or 1 if it id edited'''
+ self.account = account
+ self.privacy_list = privacy_list
+
+ # Dicts and Default Values
+ self.active_rule = ''
+ self.global_rules = {}
+ self.list_of_groups = {}
+
+ # Default Edit Values
+ self.edit_rule_type = 'jid'
+ self.allow_deny = 'allow'
+
+ # Connect to glade
+ self.xml = gtkgui_helpers.get_glade('privacy_list_edit_window.glade')
+ self.window = self.xml.get_widget('privacy_list_edit_window')
+
+ # Add Widgets
+
+ for widget_to_add in ['title_hbox', 'privacy_lists_title_label',
+ 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox',
+ 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton',
+ 'list_of_rules_combobox', 'delete_open_buttons_hbox',
+ 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton',
+ 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton',
+ 'edit_type_jabberid_entry', 'edit_type_group_radiobutton',
+ 'edit_type_group_combobox', 'edit_type_subscription_radiobutton',
+ 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton',
+ 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton',
+ 'edit_view_status_checkbutton', 'edit_order_spinbutton',
+ 'new_rule_button', 'save_rule_button', 'privacy_list_refresh_button',
+ 'privacy_list_close_button', 'edit_send_status_checkbutton',
+ 'add_edit_vbox', 'privacy_list_active_checkbutton',
+ 'privacy_list_default_checkbutton']:
+ self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add)
+
+ # Send translations
+ self.privacy_lists_title_label.set_label(
+ _('Privacy List %s') % \
+ gtkgui_helpers.escape_for_pango_markup(self.privacy_list))
+
+ if len(gajim.connections) > 1:
+ title = _('Privacy List for %s') % self.account
+ else:
+ title = _('Privacy List')
+
+ self.delete_rule_button.set_sensitive(False)
+ self.open_rule_button.set_sensitive(False)
+ self.privacy_list_active_checkbutton.set_sensitive(False)
+ self.privacy_list_default_checkbutton.set_sensitive(False)
+
+ # Check if list is created (0) or edited (1)
+ if list_type == 1:
+ self.refresh_rules()
+
+ count = 0
+ for group in gajim.groups[self.account]:
+ self.list_of_groups[group] = count
+ count += 1
+ self.edit_type_group_combobox.append_text(group)
+ self.edit_type_group_combobox.set_active(0)
+
+ self.window.set_title(title)
+
+ self.add_edit_vbox.set_no_show_all(True)
+ self.window.show_all()
+ self.add_edit_vbox.hide()
+
+ self.xml.signal_autoconnect(self)
+
+ def on_privacy_list_edit_window_destroy(self, widget):
+ '''close window'''
+ if gajim.interface.instances[self.account].has_key('privacy_list_%s' % \
+ self.privacy_list):
+ del gajim.interface.instances[self.account]['privacy_list_%s' % \
+ self.privacy_list]
+
+ def check_active_default(self, a_d_dict):
+ if a_d_dict['active'] == self.privacy_list:
+ self.privacy_list_active_checkbutton.set_active(True)
+ else:
+ self.privacy_list_active_checkbutton.set_active(False)
+ if a_d_dict['default'] == self.privacy_list:
+ self.privacy_list_default_checkbutton.set_active(True)
+ else:
+ self.privacy_list_default_checkbutton.set_active(False)
+
+ def privacy_list_received(self, rules):
+ self.list_of_rules_combobox.get_model().clear()
+ self.global_rules = {}
+ for rule in rules:
+ if rule.has_key('type'):
+ text_item = 'Order: %s, action: %s, type: %s, value: %s' % \
+ (rule['order'], rule['action'], rule['type'],
+ rule['value'])
+ else:
+ text_item = 'Order: %s, action: %s' % (rule['order'],
+ rule['action'])
+ self.global_rules[text_item] = rule
+ self.list_of_rules_combobox.append_text(text_item)
+ if len(rules) == 0:
+ self.title_hbox.set_sensitive(False)
+ self.list_of_rules_combobox.set_sensitive(False)
+ self.delete_rule_button.set_sensitive(False)
+ self.open_rule_button.set_sensitive(False)
+ self.privacy_list_active_checkbutton.set_sensitive(False)
+ self.privacy_list_default_checkbutton.set_sensitive(False)
+ else:
+ self.list_of_rules_combobox.set_active(0)
+ self.title_hbox.set_sensitive(True)
+ self.list_of_rules_combobox.set_sensitive(True)
+ self.delete_rule_button.set_sensitive(True)
+ self.open_rule_button.set_sensitive(True)
+ self.privacy_list_active_checkbutton.set_sensitive(True)
+ self.privacy_list_default_checkbutton.set_sensitive(True)
+ self.reset_fields()
+ gajim.connections[self.account].get_active_default_lists()
+
+ def refresh_rules(self):
+ gajim.connections[self.account].get_privacy_list(self.privacy_list)
+
+ def on_delete_rule_button_clicked(self, widget):
+ tags = []
+ for rule in self.global_rules:
+ if rule != \
+ self.list_of_rules_combobox.get_active_text().decode('utf-8'):
+ tags.append(self.global_rules[rule])
+ gajim.connections[self.account].set_privacy_list(
+ self.privacy_list, tags)
+ self.privacy_list_received(tags)
+ self.add_edit_vbox.hide()
+
+ def on_open_rule_button_clicked(self, widget):
+ self.add_edit_rule_label.set_label(
+ _('Edit a rule'))
+ active_num = self.list_of_rules_combobox.get_active()
+ if active_num == -1:
+ self.active_rule = ''
+ else:
+ self.active_rule = \
+ self.list_of_rules_combobox.get_active_text().decode('utf-8')
+ if self.active_rule != '':
+ rule_info = self.global_rules[self.active_rule]
+ self.edit_order_spinbutton.set_value(int(rule_info['order']))
+ if rule_info.has_key('type'):
+ if rule_info['type'] == 'jid':
+ self.edit_type_jabberid_radiobutton.set_active(True)
+ self.edit_type_jabberid_entry.set_text(rule_info['value'])
+ elif rule_info['type'] == 'group':
+ self.edit_type_group_radiobutton.set_active(True)
+ if self.list_of_groups.has_key(rule_info['value']):
+ self.edit_type_group_combobox.set_active(
+ self.list_of_groups[rule_info['value']])
+ else:
+ self.edit_type_group_combobox.set_active(0)
+ elif rule_info['type'] == 'subscription':
+ self.edit_type_subscription_radiobutton.set_active(True)
+ sub_value = rule_info['value']
+ if sub_value == 'none':
+ self.edit_type_subscription_combobox.set_active(0)
+ elif sub_value == 'both':
+ self.edit_type_subscription_combobox.set_active(1)
+ elif sub_value == 'from':
+ self.edit_type_subscription_combobox.set_active(2)
+ elif sub_value == 'to':
+ self.edit_type_subscription_combobox.set_active(3)
+ else:
+ self.edit_type_select_all_radiobutton.set_active(True)
+ else:
+ self.edit_type_select_all_radiobutton.set_active(True)
+ self.edit_send_messages_checkbutton.set_active(False)
+ self.edit_queries_send_checkbutton.set_active(False)
+ self.edit_view_status_checkbutton.set_active(False)
+ self.edit_send_status_checkbutton.set_active(False)
+ for child in rule_info['child']:
+ if child == 'presence-out':
+ self.edit_send_status_checkbutton.set_active(True)
+ elif child == 'presence-in':
+ self.edit_view_status_checkbutton.set_active(True)
+ elif child == 'iq':
+ self.edit_queries_send_checkbutton.set_active(True)
+ elif child == 'message':
+ self.edit_send_messages_checkbutton.set_active(True)
+
+ if rule_info['action'] == 'allow':
+ self.edit_allow_radiobutton.set_active(True)
+ else:
+ self.edit_deny_radiobutton.set_active(True)
+ self.add_edit_vbox.show()
+
+ def on_privacy_list_active_checkbutton_toggled(self, widget):
+ if widget.get_active():
+ gajim.connections[self.account].set_active_list(self.privacy_list)
+ else:
+ gajim.connections[self.account].set_active_list(None)
+
+ def on_privacy_list_default_checkbutton_toggled(self, widget):
+ if widget.get_active():
+ gajim.connections[self.account].set_default_list(self.privacy_list)
+ else:
+ gajim.connections[self.account].set_default_list(None)
+
+ def on_new_rule_button_clicked(self, widget):
+ self.reset_fields()
+ self.add_edit_vbox.show()
+
+ def reset_fields(self):
+ self.edit_type_jabberid_entry.set_text('')
+ self.edit_allow_radiobutton.set_active(True)
+ self.edit_type_jabberid_radiobutton.set_active(True)
+ self.active_rule = ''
+ self.edit_send_messages_checkbutton.set_active(False)
+ self.edit_queries_send_checkbutton.set_active(False)
+ self.edit_view_status_checkbutton.set_active(False)
+ self.edit_send_status_checkbutton.set_active(False)
+ self.edit_order_spinbutton.set_value(1)
+ self.edit_type_group_combobox.set_active(0)
+ self.edit_type_subscription_combobox.set_active(0)
+ self.add_edit_rule_label.set_label(
+ _('Add a rule'))
+
+ def get_current_tags(self):
+ if self.edit_type_jabberid_radiobutton.get_active():
+ edit_type = 'jid'
+ edit_value = \
+ self.edit_type_jabberid_entry.get_text().decode('utf-8')
+ elif self.edit_type_group_radiobutton.get_active():
+ edit_type = 'group'
+ edit_value = \
+ self.edit_type_group_combobox.get_active_text().decode('utf-8')
+ elif self.edit_type_subscription_radiobutton.get_active():
+ edit_type = 'subscription'
+ subs = ['none', 'both', 'from', 'to']
+ edit_value = subs[self.edit_type_subscription_combobox.get_active()]
+ elif self.edit_type_select_all_radiobutton.get_active():
+ edit_type = ''
+ edit_value = ''
+ edit_order = str(self.edit_order_spinbutton.get_value_as_int())
+ if self.edit_allow_radiobutton.get_active():
+ edit_deny = 'allow'
+ else:
+ edit_deny = 'deny'
+ child = []
+ if self.edit_send_messages_checkbutton.get_active():
+ child.append('message')
+ if self.edit_queries_send_checkbutton.get_active():
+ child.append('iq')
+ if self.edit_send_status_checkbutton.get_active():
+ child.append('presence-out')
+ if self.edit_view_status_checkbutton.get_active():
+ child.append('presence-in')
+ if edit_type != '':
+ return {'order': edit_order, 'action': edit_deny,
+ 'type': edit_type, 'value': edit_value, 'child': child}
+ return {'order': edit_order, 'action': edit_deny, 'child': child}
+
+ def on_save_rule_button_clicked(self, widget):
+ tags=[]
+ current_tags = self.get_current_tags()
+ if self.active_rule == '':
+ tags.append(current_tags)
+
+ for rule in self.global_rules:
+ if rule != self.active_rule:
+ tags.append(self.global_rules[rule])
+ else:
+ tags.append(current_tags)
+
+ gajim.connections[self.account].set_privacy_list(self.privacy_list, tags)
+ self.privacy_list_received(tags)
+ self.add_edit_vbox.hide()
+
+ def on_list_of_rules_combobox_changed(self, widget):
+ self.add_edit_vbox.hide()
+
+ def on_edit_type_radiobutton_changed(self, widget, radiobutton):
+ active_bool = widget.get_active()
+ if active_bool:
+ self.edit_rule_type = radiobutton
+
+ def on_edit_allow_radiobutton_changed(self, widget, radiobutton):
+ active_bool = widget.get_active()
+ if active_bool:
+ self.allow_deny = radiobutton
+
+ def on_privacy_list_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_privacy_list_refresh_button_clicked(self, widget):
+ self.refresh_rules()
+ self.add_edit_vbox.hide()
+
+class PrivacyListsWindow:
+# To do: UTF-8 ???????
+ def __init__(self, account):
+ self.account = account
+
+ self.privacy_lists = []
+
+ self.privacy_lists_save = []
+
+ self.xml = gtkgui_helpers.get_glade('privacy_lists_first_window.glade')
+
+ self.window = self.xml.get_widget('privacy_lists_first_window')
+ for widget_to_add in ['list_of_privacy_lists_combobox',
+ 'delete_privacy_list_button', 'open_privacy_list_button',
+ 'new_privacy_list_button', 'new_privacy_list_entry', 'buttons_hbox',
+ 'privacy_lists_refresh_button', 'close_privacy_lists_window_button']:
+ self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add)
+
+ self.draw_privacy_lists_in_combobox()
+ self.privacy_lists_refresh()
+
+ self.enabled = True
+
+ if len(gajim.connections) > 1:
+ title = _('Privacy Lists for %s') % self.account
+ else:
+ title = _('Privacy Lists')
+
+ self.window.set_title(title)
+
+ self.window.show_all()
+
+ self.xml.signal_autoconnect(self)
+
+ def on_privacy_lists_first_window_destroy(self, widget):
+ '''close window'''
+ if gajim.interface.instances[self.account].has_key('privacy_lists'):
+ del gajim.interface.instances[self.account]['privacy_lists']
+
+ def draw_privacy_lists_in_combobox(self):
+ self.list_of_privacy_lists_combobox.set_active(-1)
+ self.list_of_privacy_lists_combobox.get_model().clear()
+ self.privacy_lists_save = self.privacy_lists
+ for add_item in self.privacy_lists:
+ self.list_of_privacy_lists_combobox.append_text(add_item)
+ if len(self.privacy_lists) == 0:
+ self.list_of_privacy_lists_combobox.set_sensitive(False)
+ self.buttons_hbox.set_sensitive(False)
+ elif len(self.privacy_lists) == 1:
+ self.list_of_privacy_lists_combobox.set_active(0)
+ self.list_of_privacy_lists_combobox.set_sensitive(False)
+ self.buttons_hbox.set_sensitive(True)
+ else:
+ self.list_of_privacy_lists_combobox.set_sensitive(True)
+ self.buttons_hbox.set_sensitive(True)
+ self.list_of_privacy_lists_combobox.set_active(0)
+ self.privacy_lists = []
+
+ def on_privacy_lists_refresh_button_clicked(self, widget):
+ self.privacy_lists_refresh()
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_delete_privacy_list_button_clicked(self, widget):
+ active_list = self.privacy_lists_save[
+ self.list_of_privacy_lists_combobox.get_active()]
+ gajim.connections[self.account].del_privacy_list(active_list)
+ self.privacy_lists_save.remove(active_list)
+ self.privacy_lists_received({'lists':self.privacy_lists_save})
+
+ def privacy_lists_received(self, lists):
+ if not lists:
+ return
+ for privacy_list in lists['lists']:
+ self.privacy_lists += [privacy_list]
+ self.draw_privacy_lists_in_combobox()
+
+ def privacy_lists_refresh(self):
+ gajim.connections[self.account].get_privacy_lists()
+
+ def on_new_privacy_list_button_clicked(self, widget):
+ name = self.new_privacy_list_entry.get_text().decode('utf-8')
+ if gajim.interface.instances[self.account].has_key(
+ 'privacy_list_%s' % name):
+ gajim.interface.instances[self.account]['privacy_list_%s' % name].\
+ window.present()
+ else:
+ gajim.interface.instances[self.account]['privacy_list_%s' % name] = \
+ PrivacyListWindow(self.account, name, 0)
+ self.new_privacy_list_entry.set_text('')
+
+ def on_privacy_lists_refresh_button_clicked(self, widget):
+ self.privacy_lists_refresh()
+
+ def on_open_privacy_list_button_clicked(self, widget):
+ name = self.privacy_lists_save[
+ self.list_of_privacy_lists_combobox.get_active()]
+ if gajim.interface.instances[self.account].has_key(
+ 'privacy_list_%s' % name):
+ gajim.interface.instances[self.account]['privacy_list_%s' % name].\
+ window.present()
+ else:
+ gajim.interface.instances[self.account]['privacy_list_%s' % name] = \
+ PrivacyListWindow(self.account, name, 1)
+
class InvitationReceivedDialog:
def __init__(self, account, room_jid, contact_jid, password = None, comment = None):
@@ -1561,7 +2131,7 @@ class InvitationReceivedDialog:
self.account = account
xml = gtkgui_helpers.get_glade('invitation_received_dialog.glade')
self.dialog = xml.get_widget('invitation_received_dialog')
-
+
#FIXME: use nickname instead of contact_jid
pritext = _('%(contact_jid)s has invited you to %(room_jid)s room') % {
'room_jid': room_jid, 'contact_jid': contact_jid }
@@ -1586,7 +2156,10 @@ class InvitationReceivedDialog:
def on_accept_button_clicked(self, widget):
self.dialog.destroy()
room, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid)
- JoinGroupchatWindow(self.account, server = server, room = room)
+ try:
+ JoinGroupchatWindow(self.account, server = server, room = room)
+ except RuntimeError:
+ pass
class ProgressDialog:
def __init__(self, title_text, during_text, messages_queue):
@@ -1659,6 +2232,8 @@ class ImageChooserDialog(FileChooserDialog):
def on_ok(widget, callback):
'''check if file exists and call callback'''
path_to_file = self.get_filename()
+ if not path_to_file:
+ return
path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
(path_to_file,))[0]
if os.path.exists(path_to_file):
@@ -1768,16 +2343,26 @@ class AddSpecialNotificationDialog:
print listen_sound_model[active_iter][0]
class AdvancedNotificationsWindow:
+ events_list = ['message_received', 'contact_connected',
+ 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight',
+ 'gc_msg', 'ft_request', 'ft_started', 'ft_finished']
+ recipient_types_list = ['contact', 'group', 'all']
+ config_options = ['event', 'recipient_type', 'recipients', 'status',
+ 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open',
+ 'run_command', 'command', 'systray', 'roster', 'urgency_hint']
def __init__(self):
self.xml = gtkgui_helpers.get_glade('advanced_notifications_window.glade')
self.window = self.xml.get_widget('advanced_notifications_window')
- self.xml.signal_autoconnect(self)
- self.conditions_treeview = self.xml.get_widget('conditions_treeview')
- self.recipient_type_combobox = self.xml.get_widget('recipient_type_combobox')
- self.recipient_list = self.xml.get_widget('recipient_list')
- self.list_expander = self.xml.get_widget('list_expander')
-
- self.status_hbox = self.xml.get_widget('status_hbox')
+ for w in ('conditions_treeview', 'config_vbox', 'event_combobox',
+ 'recipient_type_combobox', 'recipient_list_entry', 'delete_button',
+ 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
+ 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb',
+ 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb',
+ 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb',
+ 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button',
+ 'run_command_cb', 'command_entry', 'urgency_hint_cb'):
+ self.__dict__[w] = self.xml.get_widget(w)
+
# Contains status checkboxes
childs = self.status_hbox.get_children()
@@ -1785,130 +2370,438 @@ class AdvancedNotificationsWindow:
self.special_status_rb = childs[1]
self.online_cb = childs[2]
self.away_cb = childs[3]
- self.not_available_cb = childs[4]
- self.busy_cb = childs[5]
+ self.xa_cb = childs[4]
+ self.dnd_cb = childs[5]
self.invisible_cb = childs[6]
-
- self.use_sound_cb = self.xml.get_widget('use_sound_cb')
- self.disable_sound_cb = self.xml.get_widget('disable_sound_cb')
- self.use_popup_cb = self.xml.get_widget('use_popup_cb')
- self.disable_popup_cb = self.xml.get_widget('disable_popup_cb')
- self.use_auto_open_cb = self.xml.get_widget('use_auto_open_cb')
- self.disable_auto_open_cb = self.xml.get_widget('disable_auto_open_cb')
- self.use_systray_cb = self.xml.get_widget('use_systray_cb')
- self.disable_systray_cb = self.xml.get_widget('disable_systray_cb')
- self.use_roster_cb = self.xml.get_widget('use_roster_cb')
- self.disable_roster_cb = self.xml.get_widget('disable_roster_cb')
-
- self.tab_opened_cb = self.xml.get_widget('tab_opened_cb')
- self.not_tab_opened_cb = self.xml.get_widget('not_tab_opened_cb')
-
- model = gtk.ListStore(str)
+
+ model = gtk.ListStore(int, str)
+ model.set_sort_column_id(0, gtk.SORT_ASCENDING)
model.clear()
self.conditions_treeview.set_model(model)
+ ## means number
+ col = gtk.TreeViewColumn(_('#'))
+ self.conditions_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand = False)
+ col.set_attributes(renderer, text = 0)
+
col = gtk.TreeViewColumn(_('Condition'))
self.conditions_treeview.append_column(col)
renderer = gtk.CellRendererText()
col.pack_start(renderer, expand = True)
- col.set_attributes(renderer, text = 0)
-
- if (0==0): # No rule set yet
- self.list_expander.set_expanded(False)
-
- if (0==1): # We have existing rule(s)
- #temporary example
- model.append(("When Contact Connected for contact Asterix when I am Available",))
+ col.set_attributes(renderer, text = 1)
+
+ self.xml.signal_autoconnect(self)
+
+ # Fill conditions_treeview
+ num = 0
+ while gajim.config.get_per('notifications', str(num)):
+ iter = model.append((num, ''))
+ path = model.get_path(iter)
+ self.conditions_treeview.set_cursor(path)
+ self.active_num = num
+ self.initiate_rule_state()
+ self.set_treeview_string()
+ num += 1
- # TODO : add a "New rule" line
-
- self.window.show_all()
-
# No rule selected at init time
- self.initiate_new_rule_state()
-
- def initiate_new_rule_state(self):
- '''Set default value to all widgets'''
- # Deal with status line
- self.all_status_rb.set_active(True)
+ self.conditions_treeview.get_selection().unselect_all()
+ self.active_num = -1
+ self.config_vbox.set_sensitive(False)
+ self.delete_button.set_sensitive(False)
+ self.down_button.set_sensitive(False)
+ self.up_button.set_sensitive(False)
+ self.recipient_list_entry.set_no_show_all(True)
+ for st in ['online', 'away', 'xa', 'dnd', 'invisible']:
+ self.__dict__[st + '_cb'].set_no_show_all(True)
+
+ self.window.show_all()
+
+ def initiate_rule_state(self):
+ '''Set values for all widgets'''
+ if self.active_num < 0:
+ return
+ # event
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'event')
+ if value:
+ self.event_combobox.set_active(self.events_list.index(value))
+ else:
+ self.event_combobox.set_active(-1)
+ # recipient_type
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'recipient_type')
+ if value:
+ self.recipient_type_combobox.set_active(
+ self.recipient_types_list.index(value))
+ else:
+ self.recipient_type_combobox.set_active(-1)
+ # recipient
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'recipients')
+ if not value:
+ value = ''
+ self.recipient_list_entry.set_text(value)
+ # status
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'status')
+ if value == 'all':
+ self.all_status_rb.set_active(True)
+ else:
+ self.special_status_rb.set_active(True)
+ values = value.split()
+ for v in ['online', 'away', 'xa', 'dnd', 'invisible']:
+ if v in values:
+ self.__dict__[v + '_cb'].set_active(True)
+ else:
+ self.__dict__[v + '_cb'].set_active(False)
self.on_status_radiobutton_toggled(self.all_status_rb)
-
- self.recipient_type_combobox.set_active(0) # 'Contact(s)'
- self.not_tab_opened_cb.set_active(True)
+ # tab_opened
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'tab_opened')
self.tab_opened_cb.set_active(True)
+ self.not_tab_opened_cb.set_active(True)
+ if value == 'no':
+ self.tab_opened_cb.set_active(False)
+ elif value == 'yes':
+ self.not_tab_opened_cb.set_active(False)
+ # sound_file
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'sound_file')
+ self.sound_entry.set_text(value)
+ # sound, popup, auto_open, systray, roster
+ for option in ['sound', 'popup', 'auto_open', 'systray', 'roster']:
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ option)
+ if value == 'yes':
+ self.__dict__['use_' + option + '_cb'].set_active(True)
+ else:
+ self.__dict__['use_' + option + '_cb'].set_active(False)
+ if value == 'no':
+ self.__dict__['disable_' + option + '_cb'].set_active(True)
+ else:
+ self.__dict__['disable_' + option + '_cb'].set_active(False)
+ # run_command
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'run_command')
+ self.run_command_cb.set_active(value)
+ # command
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'command')
+ self.command_entry.set_text(value)
+ # urgency_hint
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'urgency_hint')
+ self.urgency_hint_cb.set_active(value)
+
+ def set_treeview_string(self):
+ (model, iter) = self.conditions_treeview.get_selection().get_selected()
+ if not iter:
+ return
+ event = self.event_combobox.get_active_text()
+ recipient_type = self.recipient_type_combobox.get_active_text()
+ recipient = ''
+ if recipient_type != 'everybody':
+ recipient = self.recipient_list_entry.get_text()
+ if self.all_status_rb.get_active():
+ status = ''
+ else:
+ status = _('when I am ')
+ for st in ['online', 'away', 'xa', 'dnd', 'invisible']:
+ if self.__dict__[st + '_cb'].get_active():
+ status += helpers.get_uf_show(st) + ' '
+ model[iter][1] = "When %s for %s %s %s" % (event, recipient_type,
+ recipient, status)
+
+ def on_conditions_treeview_cursor_changed(self, widget):
+ (model, iter) = widget.get_selection().get_selected()
+ if not iter:
+ self.active_num = -1
+ return
+ self.active_num = model[iter][0]
+ if self.active_num == 0:
+ self.up_button.set_sensitive(False)
+ else:
+ self.up_button.set_sensitive(True)
+ max = self.conditions_treeview.get_model().iter_n_children(None)
+ if self.active_num == max - 1:
+ self.down_button.set_sensitive(False)
+ else:
+ self.down_button.set_sensitive(True)
+ self.initiate_rule_state()
+ self.config_vbox.set_sensitive(True)
+ self.delete_button.set_sensitive(True)
+
+ def on_new_button_clicked(self, widget):
+ model = self.conditions_treeview.get_model()
+ num = self.conditions_treeview.get_model().iter_n_children(None)
+ gajim.config.add_per('notifications', str(num))
+ iter = model.append((num, ''))
+ path = model.get_path(iter)
+ self.conditions_treeview.set_cursor(path)
+ self.active_num = num
+ self.set_treeview_string()
+ self.config_vbox.set_sensitive(True)
+
+ def on_delete_button_clicked(self, widget):
+ (model, iter) = self.conditions_treeview.get_selection().get_selected()
+ if not iter:
+ return
+ # up all others
+ iter2 = model.iter_next(iter)
+ num = self.active_num
+ while iter2:
+ num = model[iter2][0]
+ model[iter2][0] = num - 1
+ for opt in self.config_options:
+ val = gajim.config.get_per('notifications', str(num), opt)
+ gajim.config.set_per('notifications', str(num - 1), opt, val)
+ iter2 = model.iter_next(iter2)
+ model.remove(iter)
+ gajim.config.del_per('notifications', str(num)) # delete latest
+ self.active_num = -1
+ self.config_vbox.set_sensitive(False)
+ self.delete_button.set_sensitive(False)
+ self.up_button.set_sensitive(False)
+ self.down_button.set_sensitive(False)
+
+ def on_up_button_clicked(self, widget):
+ (model, iter) = self.conditions_treeview.get_selection().\
+ get_selected()
+ if not iter:
+ return
+ for opt in self.config_options:
+ val = gajim.config.get_per('notifications', str(self.active_num), opt)
+ val2 = gajim.config.get_per('notifications', str(self.active_num - 1),
+ opt)
+ gajim.config.set_per('notifications', str(self.active_num), opt, val2)
+ gajim.config.set_per('notifications', str(self.active_num - 1), opt,
+ val)
+
+ model[iter][0] = self.active_num - 1
+ # get previous iter
+ path = model.get_path(iter)
+ iter = model.get_iter((path[0] - 1,))
+ model[iter][0] = self.active_num
+ self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
+
+ def on_down_button_clicked(self, widget):
+ (model, iter) = self.conditions_treeview.get_selection().get_selected()
+ if not iter:
+ return
+ for opt in self.config_options:
+ val = gajim.config.get_per('notifications', str(self.active_num), opt)
+ val2 = gajim.config.get_per('notifications', str(self.active_num + 1),
+ opt)
+ gajim.config.set_per('notifications', str(self.active_num), opt, val2)
+ gajim.config.set_per('notifications', str(self.active_num + 1), opt,
+ val)
+
+ model[iter][0] = self.active_num + 1
+ iter = model.iter_next(iter)
+ model[iter][0] = self.active_num
+ self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
+
+ def on_event_combobox_changed(self, widget):
+ if self.active_num < 0:
+ return
+ active = self.event_combobox.get_active()
+ if active == -1:
+ event = ''
+ else:
+ event = self.events_list[active]
+ gajim.config.set_per('notifications', str(self.active_num), 'event',
+ event)
+ self.set_treeview_string()
+
+ def on_recipient_type_combobox_changed(self, widget):
+ if self.active_num < 0:
+ return
+ recipient_type = self.recipient_types_list[self.recipient_type_combobox.\
+ get_active()]
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'recipient_type', recipient_type)
+ if recipient_type == 'all':
+ self.recipient_list_entry.hide()
+ else:
+ self.recipient_list_entry.show()
+ self.set_treeview_string()
+
+ def on_recipient_list_entry_changed(self, widget):
+ if self.active_num < 0:
+ return
+ recipients = widget.get_text().decode('utf-8')
+ #TODO: do some check
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'recipients', recipients)
+ self.set_treeview_string()
+
+ def set_status_config(self):
+ if self.active_num < 0:
+ return
+ status = ''
+ for st in ['online', 'away', 'xa', 'dnd', 'invisible']:
+ if self.__dict__[st + '_cb'].get_active():
+ status += st + ' '
+ if status:
+ status = status[:-1]
+ gajim.config.set_per('notifications', str(self.active_num), 'status',
+ status)
+ self.set_treeview_string()
def on_status_radiobutton_toggled(self, widget):
+ if self.active_num < 0:
+ return
if self.all_status_rb.get_active():
+ gajim.config.set_per('notifications', str(self.active_num), 'status',
+ 'all')
# 'All status' clicked
- self.online_cb.hide()
- self.away_cb.hide()
- self.not_available_cb.hide()
- self.busy_cb.hide()
- self.invisible_cb.hide()
-
- self.online_cb.set_active(False)
- self.away_cb.set_active(False)
- self.not_available_cb.set_active(False)
- self.busy_cb.set_active(False)
- self.invisible_cb.set_active(False)
-
+ for st in ['online', 'away', 'xa', 'dnd', 'invisible']:
+ self.__dict__[st + '_cb'].hide()
+
self.special_status_rb.show()
else:
+ self.set_status_config()
# 'special status' clicked
- self.online_cb.show()
- self.away_cb.show()
- self.not_available_cb.show()
- self.busy_cb.show()
- self.invisible_cb.show()
-
+ for st in ['online', 'away', 'xa', 'dnd', 'invisible']:
+ self.__dict__[st + '_cb'].show()
+
self.special_status_rb.hide()
- def on_recipient_type_combobox_changed(self, widget):
- if (self.recipient_type_combobox.get_active()==2 ):
- self.recipient_list.hide()
- else:
- self.recipient_list.show()
+ self.set_treeview_string()
+
+ def on_status_cb_toggled(self, widget):
+ if self.active_num < 0:
+ return
+ self.set_status_config()
# tab_opened OR (not xor) not_tab_opened must be active
def on_tab_opened_cb_toggled(self, widget):
- if not self.tab_opened_cb.get_active() and not self.not_tab_opened_cb.get_active():
+ if self.active_num < 0:
+ return
+ if self.tab_opened_cb.get_active():
+ if self.not_tab_opened_cb.get_active():
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'both')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'yes')
+ elif not self.not_tab_opened_cb.get_active():
self.not_tab_opened_cb.set_active(True)
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'no')
+
def on_not_tab_opened_cb_toggled(self, widget):
- if not self.tab_opened_cb.get_active() and not self.not_tab_opened_cb.get_active():
+ if self.active_num < 0:
+ return
+ if self.not_tab_opened_cb.get_active():
+ if self.tab_opened_cb.get_active():
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'both')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'no')
+ elif not self.tab_opened_cb.get_active():
self.tab_opened_cb.set_active(True)
-
- # 10 next functions : Forbid two incompatible actions to be checked together
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'yes')
+
+ def on_use_it_toggled(self, widget, oposite_widget, option):
+ if widget.get_active():
+ if oposite_widget.get_active():
+ oposite_widget.set_active(False)
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'yes')
+ elif oposite_widget.get_active():
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'no')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num), option, '')
+
+ def on_disable_it_toggled(self, widget, oposite_widget, option):
+ if widget.get_active():
+ if oposite_widget.get_active():
+ oposite_widget.set_active(False)
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'no')
+ elif oposite_widget.get_active():
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'yes')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num), option, '')
+
def on_use_sound_cb_toggled(self, widget):
- if self.use_sound_cb.get_active() and self.disable_sound_cb.get_active():
- self.disable_sound_cb.set_active(False)
+ self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound')
+ if widget.get_active():
+ self.sound_file_hbox.set_sensitive(True)
+ else:
+ self.sound_file_hbox.set_sensitive(False)
+
+ def on_browse_for_sounds_button_clicked(self, widget, data = None):
+ if self.active_num < 0:
+ return
+
+ def on_ok(widget, path_to_snd_file):
+ dialog.destroy()
+ if not path_to_snd_file:
+ path_to_snd_file = ''
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'sound_file', path_to_snd_file)
+ self.sound_entry.set_text(path_to_snd_file)
+
+ path_to_snd_file = self.sound_entry.get_text().decode('utf-8')
+ path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file)
+ dialog = SoundChooserDialog(path_to_snd_file, on_ok)
+
+ def on_play_button_clicked(self, widget):
+ helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8'))
+
def on_disable_sound_cb_toggled(self, widget):
- if self.use_sound_cb.get_active() and self.disable_sound_cb.get_active():
- self.use_sound_cb.set_active(False)
+ self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound')
+
+ def on_sound_entry_changed(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'sound_file', widget.get_text().decode('utf-8'))
+
def on_use_popup_cb_toggled(self, widget):
- if self.use_popup_cb.get_active() and self.disable_popup_cb.get_active():
- self.disable_popup_cb.set_active(False)
+ self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup')
+
def on_disable_popup_cb_toggled(self, widget):
- if self.use_popup_cb.get_active() and self.disable_popup_cb.get_active():
- self.use_popup_cb.set_active(False)
+ self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup')
+
def on_use_auto_open_cb_toggled(self, widget):
- if self.use_auto_open_cb.get_active() and\
- self.disable_auto_open_cb.get_active():
- self.disable_auto_open_cb.set_active(False)
+ self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open')
+
def on_disable_auto_open_cb_toggled(self, widget):
- if self.use_auto_open_cb.get_active() and\
- self.disable_auto_open_cb.get_active():
- self.use_auto_open_cb.set_active(False)
+ self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open')
+
+ def on_run_command_cb_toggled(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num), 'run_command',
+ widget.get_active())
+ if widget.get_active():
+ self.command_entry.set_sensitive(True)
+ else:
+ self.command_entry.set_sensitive(False)
+
+ def on_command_entry_changed(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num), 'command',
+ widget.get_text().decode('utf-8'))
+
def on_use_systray_cb_toggled(self, widget):
- if self.use_systray_cb.get_active() and self.disable_systray_cb.get_active():
- self.disable_systray_cb.set_active(False)
+ self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray')
+
def on_disable_systray_cb_toggled(self, widget):
- if self.use_systray_cb.get_active() and self.disable_systray_cb.get_active():
- self.use_systray_cb.set_active(False)
+ self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray')
+
def on_use_roster_cb_toggled(self, widget):
- if self.use_roster_cb.get_active() and self.disable_roster_cb.get_active():
- self.disable_roster_cb.set_active(False)
+ self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster')
+
def on_disable_roster_cb_toggled(self, widget):
- if self.use_roster_cb.get_active() and self.disable_roster_cb.get_active():
- self.use_roster_cb.set_active(False)
-
+ self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster')
+
+ def on_urgency_hint_cb_toggled(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'uregency_hint', widget.get_active())
+
def on_close_window(self, widget):
self.window.destroy()
diff --git a/src/disco.py b/src/disco.py
index 2730b68f2..f56f70a1c 100644
--- a/src/disco.py
+++ b/src/disco.py
@@ -51,7 +51,6 @@ import inspect
import weakref
import gobject
import gtk
-import gtk.glade
import pango
import dialogs
@@ -60,12 +59,6 @@ import gtkgui_helpers
from common import gajim
from common import xmpp
-from common import i18n
-
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain (APP, i18n.DIR)
-gtk.glade.textdomain (APP)
# Dictionary mapping category, type pairs to browser class, image pairs.
# This is a function, so we can call it after the classes are declared.
@@ -92,7 +85,7 @@ def _gen_agent_type_info():
('proxy', 'bytestreams'): (None, 'bytestreams.png'), # Socks5 FT proxy
# Transports
- ('conference', 'irc'): (False, 'irc.png'),
+ ('conference', 'irc'): (ToplevelAgentBrowser, 'irc.png'),
('_jid', 'irc'): (False, 'irc.png'),
('gateway', 'aim'): (False, 'aim.png'),
('_jid', 'aim'): (False, 'aim.png'),
@@ -140,7 +133,8 @@ class CacheDictionary:
def _expire_timeout(self, key):
'''The timeout has expired, remove the object.'''
- del self.cache[key]
+ if key in self.cache:
+ del self.cache[key]
return False
def _refresh_timeout(self, key):
@@ -278,7 +272,7 @@ class ServicesCache:
except KeyError:
continue
browser = info[0]
- if browser is not None:
+ if browser:
break
# Note: possible outcome here is browser=False
if browser is None:
@@ -449,7 +443,7 @@ _('Without a connection, you can not browse available services'))
# Address combobox
self.address_comboboxentry = None
- address_hbox = self.xml.get_widget('address_hbox')
+ address_table = self.xml.get_widget('address_table')
if address_entry:
self.address_comboboxentry = self.xml.get_widget(
'address_comboboxentry')
@@ -461,7 +455,6 @@ _('Without a connection, you can not browse available services'))
self.address_comboboxentry.set_text_column(0)
self.latest_addresses = gajim.config.get(
'latest_disco_addresses').split()
- jid = gajim.get_hostname_from_account(self.account)
if jid in self.latest_addresses:
self.latest_addresses.remove(jid)
self.latest_addresses.insert(0, jid)
@@ -472,8 +465,8 @@ _('Without a connection, you can not browse available services'))
self.address_comboboxentry.child.set_text(jid)
else:
# Don't show it at all if we didn't ask for it
- address_hbox.set_no_show_all(True)
- address_hbox.hide()
+ address_table.set_no_show_all(True)
+ address_table.hide()
self._initial_state()
self.xml.signal_autoconnect(self)
@@ -1205,7 +1198,10 @@ class ToplevelAgentBrowser(AgentBrowser):
else:
room = ''
if not gajim.interface.instances[self.account].has_key('join_gc'):
- dialogs.JoinGroupchatWindow(self.account, service, room)
+ try:
+ dialogs.JoinGroupchatWindow(self.account, service, room)
+ except RuntimeError:
+ pass
else:
gajim.interface.instances[self.account]['join_gc'].window.present()
self.window.destroy(chain = True)
@@ -1535,7 +1531,10 @@ class MucBrowser(AgentBrowser):
else:
room = model[iter][1].decode('utf-8')
if not gajim.interface.instances[self.account].has_key('join_gc'):
- dialogs.JoinGroupchatWindow(self.account, service, room)
+ try:
+ dialogs.JoinGroupchatWindow(self.account, service, room)
+ except RuntimeError:
+ pass
else:
gajim.interface.instances[self.account]['join_gc'].window.present()
self.window.destroy(chain = True)
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
index d4ef4860b..0e31e8a4e 100644
--- a/src/filetransfers_window.py
+++ b/src/filetransfers_window.py
@@ -18,7 +18,6 @@
##
import gtk
-import gtk.glade
import gobject
import pango
import os
@@ -30,12 +29,6 @@ import dialogs
from common import gajim
from common import helpers
-from common import i18n
-
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain (APP, i18n.DIR)
-gtk.glade.textdomain (APP)
C_IMAGE = 0
C_LABELS = 1
@@ -153,9 +146,8 @@ class FileTransfersWindow:
''' show a dialog saying that file (file_props) has been transferred'''
self.window.present()
self.window.window.focus()
-
def on_open(widget, file_props):
- self.dialog.destroy()
+ dialog.destroy()
if not file_props.has_key('file-name'):
return
(path, file) = os.path.split(file_props['file-name'])
@@ -192,17 +184,17 @@ class FileTransfersWindow:
sectext += recipient
if file_props['type'] == 'r':
sectext += '\n\t' +_('Saved in: %s') % file_path
- self.dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
+ dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
_('File transfer completed'), sectext)
if file_props['type'] == 'r':
button = gtk.Button(_('_Open Containing Folder'))
button.connect('clicked', on_open, file_props)
- self.dialog.action_area.pack_start(button)
- ok_button = self.dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
+ dialog.action_area.pack_start(button)
+ ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
def on_ok(widget):
- self.dialog.destroy()
+ dialog.destroy()
ok_button.connect('clicked', on_ok)
- self.dialog.show_all()
+ dialog.show_all()
def show_request_error(self, file_props):
''' show error dialog to the recipient saying that transfer
@@ -221,7 +213,7 @@ class FileTransfersWindow:
_('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all()
- def show_stopped(self, jid, file_props):
+ def show_stopped(self, jid, file_props, error_msg = ''):
self.window.present()
self.window.window.focus()
if file_props['type'] == 'r':
@@ -229,7 +221,9 @@ _('Connection with peer cannot be established.'))
else:
file_name = file_props['name']
sectext = '\t' + _('Filename: %s') % file_name
- sectext += '\n\t' + _('Sender: %s') % jid
+ sectext += '\n\t' + _('Recipient: %s') % jid
+ if error_msg:
+ sectext += '\n\t' + _('Error message: %s') % error_msg
dialogs.ErrorDialog(_('File transfer stopped by the contact of the other side'), \
sectext)
self.tree.get_selection().unselect_all()
@@ -237,7 +231,7 @@ _('Connection with peer cannot be established.'))
def show_file_send_request(self, account, contact):
def on_ok(widget):
file_dir = None
- files_path_list = self.dialog.get_filenames()
+ files_path_list = dialog.get_filenames()
files_path_list = gtkgui_helpers.decode_filechooser_file_paths(
files_path_list)
for file_path in files_path_list:
@@ -245,16 +239,16 @@ _('Connection with peer cannot be established.'))
file_dir = os.path.dirname(file_path)
if file_dir:
gajim.config.set('last_send_dir', file_dir)
- self.dialog.destroy()
+ dialog.destroy()
- self.dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
+ dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
gtk.RESPONSE_OK,
True, # select multiple true as we can select many files to send
gajim.config.get('last_send_dir'),
)
- btn = self.dialog.add_button(_('_Send'), gtk.RESPONSE_OK)
+ btn = dialog.add_button(_('_Send'), gtk.RESPONSE_OK)
btn.set_use_stock(True) # FIXME: add send icon to this button (JUMP_TO)
btn.connect('clicked', on_ok)
@@ -313,6 +307,12 @@ _('Connection with peer cannot be established.'))
file_path = gtkgui_helpers.decode_filechooser_file_paths(
(file_path,))[0]
if os.path.exists(file_path):
+ # 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.'))
+ return
stat = os.stat(file_path)
dl_size = stat.st_size
file_size = file_props['size']
@@ -327,6 +327,11 @@ _('Connection with peer cannot be established.'))
return
elif response == 100:
file_props['offset'] = dl_size
+ 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.'))
+ return
dialog2.destroy()
self._start_receive(file_path, account, contact, file_props)
@@ -442,12 +447,11 @@ _('Connection with peer cannot be established.'))
jid = gajim.get_jid_without_resource(other)
else: # It's a Contact instance
jid = other.jid
- if gajim.awaiting_events[account].has_key(jid):
- for event in gajim.awaiting_events[account][jid]:
- if event[0] in ('file-error', 'file-completed',
- 'file-request-error', 'file-send-error', 'file-stopped') and \
- event[1]['sid'] == file_props['sid']:
- gajim.interface.remove_event(account, jid, event)
+ for ev_type in ('file-error', 'file-completed', 'file-request-error',
+ 'file-send-error', 'file-stopped'):
+ for event in gajim.events.get_events(account, jid, [ev_type]):
+ if event.parameters[1]['sid'] == file_props['sid']:
+ gajim.events.remove_events(account, jid, event)
del(self.files_props[sid[0]][sid[1:]])
del(file_props)
@@ -832,9 +836,9 @@ _('Connection with peer cannot be established.'))
self.set_buttons_sensitive(path, True)
event_button = gtkgui_helpers.get_possible_button_event(event)
+ self.file_transfers_menu.show_all()
self.file_transfers_menu.popup(None, self.tree, None,
event_button, event.time)
- self.file_transfers_menu.show_all()
def on_transfers_list_key_press_event(self, widget, event):
'''when a key is pressed in the treeviews'''
diff --git a/src/gajim-remote.py b/src/gajim-remote.py
index 478990092..563cc121a 100755
--- a/src/gajim-remote.py
+++ b/src/gajim-remote.py
@@ -38,8 +38,6 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
from common import exceptions
from common import i18n
-_ = i18n._
-i18n.init()
try:
PREFERRED_ENCODING = locale.getpreferredencoding()
except:
@@ -68,7 +66,7 @@ BASENAME = 'gajim-remote'
class GajimRemote:
-
+
def __init__(self):
self.argv_len = len(sys.argv)
# define commands dict. Prototype :
@@ -81,7 +79,7 @@ class GajimRemote:
#
self.commands = {
'help':[
- _('shows a help on specific command'),
+ _('Shows a help on specific command'),
[
#User gets help for the command, specified by this parameter
(_('command'),
@@ -101,7 +99,7 @@ class GajimRemote:
[
(_('account'), _('show only contacts of the given account'), False)
]
-
+
],
'list_accounts': [
_('Prints a list of registered accounts'),
@@ -110,6 +108,7 @@ class GajimRemote:
'change_status': [
_('Changes the status of account or accounts'),
[
+#offline, online, chat, away, xa, dnd, invisible should not be translated
(_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True),
(_('message'), _('status message'), False),
(_('account'), _('change status of account "account". '
@@ -127,7 +126,7 @@ class GajimRemote:
]
],
'send_message':[
- _('Sends new message to a contact in the roster. Both OpenPGP key '
+ _('Sends new chat message to a contact in the roster. Both OpenPGP key '
'and account are optional. If you want to set only \'account\', '
'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
[
@@ -138,6 +137,20 @@ class GajimRemote:
(_('account'), _('if specified, the message will be sent '
'using this account'), False),
]
+ ],
+ 'send_single_message':[
+ _('Sends new single message to a contact in the roster. Both OpenPGP key '
+ 'and account are optional. If you want to set only \'account\', '
+ 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
+ [
+ ('jid', _('JID of the contact that will receive the message'), True),
+ (_('subject'), _('message subject'), True),
+ (_('message'), _('message contents'), True),
+ (_('pgp key'), _('if specified, the message will be encrypted '
+ 'using this public key'), False),
+ (_('account'), _('if specified, the message will be sent '
+ 'using this account'), False),
+ ]
],
'contact_info': [
_('Gets detailed info on a contact'),
@@ -188,7 +201,7 @@ class GajimRemote:
('jid', _('JID of the contact'), True),
(_('account'), _('if specified, contact is taken from the '
'contact list of this account'), False)
-
+
]
],
'add_contact': [
@@ -198,14 +211,14 @@ class GajimRemote:
(_('account'), _('Adds new contact to this account'), False)
]
],
-
+
'get_status': [
_('Returns current status (the global one unless account is specified)'),
[
(_('account'), _(''), False)
]
],
-
+
'get_status_message': [
_('Returns current status message(the global one unless account is specified)'),
[
@@ -218,11 +231,20 @@ class GajimRemote:
[ ]
],
'start_chat': [
- _('Open \'Start Chat\' dialog'),
+ _('Opens \'Start Chat\' dialog'),
[
(_('account'), _('Starts chat, using this account'), True)
]
],
+ 'send_xml': [
+ _('Sends custom XML'),
+ [
+ ('xml', _('XML to send'), True),
+ ('account', _('Account in which the xml will be sent; '
+ 'if not specified, xml will be sent to all accounts'),
+ False)
+ ]
+ ],
}
if self.argv_len < 2 or \
sys.argv[1] not in self.commands.keys(): # no args or bad args
@@ -234,14 +256,14 @@ class GajimRemote:
else:
print self.compose_help().encode(PREFERRED_ENCODING)
sys.exit(0)
-
+
self.init_connection()
self.check_arguments()
-
+
if self.command == 'contact_info':
if self.argv_len < 3:
send_error(_('Missing argument "contact_jid"'))
-
+
try:
res = self.call_remote_method()
except exceptions.ServiceNotAvailable:
@@ -249,14 +271,14 @@ class GajimRemote:
sys.exit(1)
else:
self.print_result(res)
-
+
def print_result(self, res):
''' Print retrieved result to the output '''
if res is not None:
- if self.command in ('open_chat', 'send_message', 'start_chat'):
- if self.command == 'send_message':
+ if self.command in ('open_chat', 'send_message', 'send_single_message', 'start_chat'):
+ if self.command in ('send_message', 'send_single_message'):
self.argv_len -= 2
-
+
if res is False:
if self.argv_len < 4:
send_error(_('\'%s\' is not in your roster.\n'
@@ -289,7 +311,7 @@ class GajimRemote:
print self.print_info(0, res, True)
elif res:
print unicode(res).encode(PREFERRED_ENCODING)
-
+
def init_connection(self):
''' create the onnection to the session dbus,
or exit if it is not possible '''
@@ -297,7 +319,7 @@ class GajimRemote:
self.sbus = dbus.SessionBus()
except:
raise exceptions.SessionBusNotPresent
-
+
if _version[1] >= 30:
obj = self.sbus.get_object(SERVICE, OBJ_PATH)
interface = dbus.Interface(obj, INTERFACE)
@@ -306,10 +328,10 @@ class GajimRemote:
interface = self.service.get_object(OBJ_PATH, INTERFACE)
else:
send_error(_('Unknown D-Bus version: %s') % _version[1])
-
+
# get the function asked
self.method = interface.__getattr__(self.command)
-
+
def make_arguments_row(self, args):
''' return arguments list. Mandatory arguments are enclosed with:
'<', '>', optional arguments - with '[', ']' '''
@@ -326,7 +348,7 @@ class GajimRemote:
else:
str += ']'
return str
-
+
def help_on_command(self, command):
''' return help message for a given command '''
if command in self.commands:
@@ -340,7 +362,7 @@ class GajimRemote:
str += ' ' + argument[0] + ' - ' + argument[1] + '\n'
return str
send_error(_('%s not found') % command)
-
+
def compose_help(self):
''' print usage, and list available commands '''
str = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME
@@ -361,7 +383,7 @@ class GajimRemote:
str += ']'
str += '\n'
return str
-
+
def print_info(self, level, prop_dict, encode_return = False):
''' return formated string from data structure '''
if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
@@ -410,7 +432,7 @@ class GajimRemote:
except:
pass
return ret_str
-
+
def check_arguments(self):
''' Make check if all necessary arguments are given '''
argv_len = self.argv_len - 2
@@ -420,7 +442,7 @@ class GajimRemote:
send_error(_('Argument "%s" is not specified. \n'
'Type "%s help %s" for more info') %
(args[argv_len][0], BASENAME, self.command))
-
+
def call_remote_method(self):
''' calls self.method with arguments from sys.argv[2:] '''
args = sys.argv[2:]
diff --git a/src/gajim.py b/src/gajim.py
index 703a089f5..a15c769cd 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -33,16 +33,22 @@ import sys
import os
import urllib
+from common import i18n
+
import message_control
from chat_control import ChatControlBase
from common import exceptions
-from common import i18n
from common.zeroconf import connection_zeroconf
-i18n.init()
-_ = i18n._
+if os.name == 'posix': # dl module is Unix Only
+ try: # rename the process name to gajim
+ import dl
+ libc = dl.open('/lib/libc.so.6')
+ libc.call('prctl', 15, 'gajim\0', 0, 0, 0)
+ except:
+ pass
try:
import gtk
@@ -73,6 +79,15 @@ except exceptions.PysqliteNotAvailable, e:
pritext = _('Gajim needs PySQLite2 to run')
sectext = str(e)
+if os.name == 'nt':
+ try:
+ import winsound # windows-only built-in module for playing wav
+ import win32api
+ import win32con
+ except:
+ 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'
+
if pritext:
dlg = gtk.MessageDialog(None,
gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
@@ -129,40 +144,60 @@ for o, a in opts:
elif o in ('-p', '--profile'): # gajim --profile name
profile = a
+pid_filename = os.path.expanduser('~/.gajim/gajim')
config_filename = os.path.expanduser('~/.gajim/config')
if os.name == 'nt':
try:
# Documents and Settings\[User Name]\Application Data\Gajim\logs
config_filename = os.environ['appdata'] + '/Gajim/config'
+ pid_filename = os.environ['appdata'] + '/Gajim/gajim'
except KeyError:
# win9x so ./config
config_filename = 'config'
+ pid_filename = 'gajim'
if profile:
config_filename += '.%s' % profile
+ pid_filename += '.%s' % profile
+
+pid_filename += '.pid'
+import dialogs
+if os.path.exists(pid_filename):
+ path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
+ pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
+ gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
+ pritext = _('Gajim is already running')
+ sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
+ dialog = dialogs.YesNoDialog(pritext, sectext)
+ if dialog.get_response() != gtk.RESPONSE_YES:
+ sys.exit(3)
+ if os.path.exists(pid_filename):
+ os.remove(pid_filename)
+ dialog.destroy()
+
+# Create .gajim dir
+pid_dir = os.path.dirname(pid_filename)
+if not os.path.exists(pid_dir):
+ check_paths.create_path(pid_dir)
+# Create pid file
+f = open(pid_filename, 'a')
+f.close()
+
+def on_exit():
+ # delete pid file on normal exit
+ if os.path.exists(pid_filename):
+ os.remove(pid_filename)
+
+import atexit
+atexit.register(on_exit)
parser = optparser.OptionsParser(config_filename)
import roster_window
import systray
-import dialogs
-import vcard
+import profile_window
import config
-class MigrateCommand(nslookup.IdleCommand):
- def __init__(self, on_result):
- nslookup.IdleCommand.__init__(self, on_result)
- self.commandtimeout = 10
-
- def _compose_command_args(self):
- return ['python', 'migrate_logs_to_dot9_db.py', 'dont_wait']
-
- def _return_result(self):
- print self.result
- if self.result_handler:
- self.result_handler(self.result)
- self.result_handler = None
-
class GlibIdleQueue(idlequeue.IdleQueue):
'''
Extends IdleQueue to use glib io_add_wath, instead of select/poll
@@ -239,7 +274,7 @@ class Interface:
on_response_no = (response, account, data[3], 'no'))
def handle_event_error_answer(self, account, array):
- #('ERROR_ANSWER', account, (id, jid_from. errmsg, errcode))
+ #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
id, jid_from, errmsg, errcode = array
if unicode(errcode) in ('403', '406') and id:
# show the error dialog
@@ -251,7 +286,7 @@ class Interface:
file_props = ft.files_props['s'][sid]
file_props['error'] = -4
self.handle_event_file_request_error(account,
- (jid_from, file_props))
+ (jid_from, file_props, errmsg))
conn = gajim.connections[account]
conn.disconnect_transfer(file_props)
return
@@ -275,6 +310,14 @@ class Interface:
gajim.con_types[account] = con_type
self.roster.draw_account(account)
+ def handle_event_connection_lost(self, account, array):
+ # ('CONNECTION_LOST', account, [title, text])
+ path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
+ 'connection_lost.png')
+ path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
+ notify.popup(_('Connection Failed'), account, account,
+ 'connection_failed', path, array[0], array[1])
+
def unblock_signed_in_notifications(self, account):
gajim.block_signed_in_notifications[account] = False
@@ -314,9 +357,9 @@ class Interface:
def edit_own_details(self, account):
jid = gajim.get_jid_from_account(account)
- if not self.instances[account]['infos'].has_key(jid):
- self.instances[account]['infos'][jid] = \
- vcard.VcardWindow(jid, account, True)
+ if not self.instances[account].has_key('profile'):
+ self.instances[account]['profile'] = \
+ profile_window.ProfileWindow(account)
gajim.connections[account].request_vcard(jid)
def handle_event_notify(self, account, array):
@@ -346,7 +389,7 @@ class Interface:
# Update contact
jid_list = gajim.contacts.get_jid_list(account)
- if ji in jid_list:
+ if ji in jid_list or jid == gajim.get_jid_from_account(account):
lcontact = gajim.contacts.get_contacts_from_jid(account, ji)
contact1 = None
resources = []
@@ -363,7 +406,20 @@ class Interface:
return
else:
contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
- if contact1.show in statuss:
+ if not contact1:
+ # presence of another resource of our jid
+ if resource == gajim.connections[account].server_resource:
+ return
+ contact1 = gajim.contacts.create_contact(jid = ji,
+ name = gajim.nicks[account], groups = [],
+ show = array[1], status = status_message, sub = 'both',
+ ask = 'none', priority = priority, keyID = keyID,
+ resource = resource)
+ old_show = 0
+ gajim.contacts.add_contact(account, contact1)
+ lcontact.append(contact1)
+ self.roster.add_self_contact(account)
+ elif contact1.show in statuss:
old_show = statuss.index(contact1.show)
if (resources != [''] and (len(lcontact) != 1 or
lcontact[0].show != 'offline')) and jid.find('@') > 0:
@@ -403,9 +459,21 @@ class Interface:
if ji in jid_list:
# Update existing iter
self.roster.draw_contact(ji, account)
- elif jid == gajim.get_jid_from_account(account):
- # It's another of our resources. We don't need to see that!
- return
+ # transport just signed in/out, don't show popup notifications
+ # for 30s
+ account_ji = account + '/' + ji
+ gajim.block_signed_in_notifications[account_ji] = True
+ gobject.timeout_add(30000, self.unblock_signed_in_notifications,
+ account_ji)
+ locations = (self.instances, self.instances[account])
+ for location in locations:
+ if location.has_key('add_contact'):
+ if old_show == 0 and new_show > 1:
+ location['add_contact'].transport_signed_in(jid)
+ break
+ elif old_show > 1 and new_show == 0:
+ location['add_contact'].transport_signed_out(jid)
+ break
elif ji in jid_list:
# It isn't an agent
# reset chatstate if needed:
@@ -416,20 +484,21 @@ class Interface:
gajim.connections[account].remove_transfers_for_contact(contact1)
self.roster.chg_contact_status(contact1, array[1], status_message,
account)
- # play sound
+ # Notifications
if old_show < 2 and new_show > 1:
notify.notify('contact_connected', jid, account, status_message)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactPresence',
(account, array))
-
+
elif old_show > 1 and new_show < 2:
notify.notify('contact_disconnected', jid, account, status_message)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
# FIXME: stop non active file transfers
elif new_show > 1: # Status change (not connected/disconnected or error (<1))
- notify.notify('status_change', jid, account, [new_show, status_message])
+ notify.notify('status_change', jid, account, [new_show,
+ status_message])
else:
# FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) doesn't follow the JEP
# remove in 2007
@@ -440,7 +509,7 @@ class Interface:
def handle_event_msg(self, account, array):
# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
- # chatstate))
+ # chatstate, msg_id, composing_jep, user_nick)) user_nick is JEP-0172
full_jid_with_resource = array[0]
jid = gajim.get_jid_without_resource(full_jid_with_resource)
@@ -448,6 +517,7 @@ class Interface:
message = array[1]
msg_type = array[4]
+ subject = array[5]
chatstate = array[6]
msg_id = array[7]
composing_jep = array[8]
@@ -513,10 +583,13 @@ class Interface:
not gajim.contacts.get_contact(account, jid) and not pm:
return
+ advanced_notif_num = notify.get_advanced_notification('message_received',
+ account, contact)
+
# Is it a first or next message received ?
first = False
- if not chat_control and not gajim.awaiting_events[account].has_key(
- jid_of_control):
+ if not chat_control and not gajim.events.get_events(account,
+ jid_of_control, ['chat']):
# It's a first message and not a Private Message
first = True
@@ -527,10 +600,14 @@ class Interface:
else:
# array: (jid, msg, time, encrypted, msg_type, subject)
self.roster.on_message(jid, message, array[2], account, array[3],
- msg_type, array[5], resource, msg_id)
+ msg_type, subject, resource, msg_id, array[9], advanced_notif_num)
nickname = gajim.get_name_from_jid(account, jid)
# Check and do wanted notifications
- notify.notify('new_message', jid, account, [msg_type, first, nickname, message])
+ msg = message
+ if subject:
+ msg = _('Subject: %s') % subject + '\n' + msg
+ notify.notify('new_message', jid, account, [msg_type, first, nickname,
+ msg], advanced_notif_num)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('NewMessage', (account, array))
@@ -583,8 +660,8 @@ class Interface:
helpers.play_sound('message_sent')
def handle_event_subscribe(self, account, array):
- #('SUBSCRIBE', account, (jid, text))
- dialogs.SubscriptionRequestWindow(array[0], array[1], account)
+ #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
+ dialogs.SubscriptionRequestWindow(array[0], array[1], account, array[2])
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Subscribe', (account, array))
@@ -664,8 +741,8 @@ class Interface:
config.ServiceRegistrationWindow(array[0], array[1], account,
array[2])
else:
- dialogs.ErrorDialog(_('Contact with "%s" cannot be established'\
-% array[0]), _('Check your connection or try again later.'))
+ dialogs.ErrorDialog(_('Contact with "%s" cannot be established')\
+% array[0], _('Check your connection or try again later.'))
def handle_event_agent_info_items(self, account, array):
#('AGENT_INFO_ITEMS', account, (agent, node, items))
@@ -705,8 +782,8 @@ class Interface:
nick = array['NICKNAME']
if nick:
gajim.nicks[account] = nick
- if self.instances[account]['infos'].has_key(array['jid']):
- win = self.instances[account]['infos'][array['jid']]
+ if self.instances[account].has_key('profile'):
+ win = self.instances[account]['profile']
win.set_values(array)
if account in self.show_vcard_when_connect:
self.show_vcard_when_connect.remove(account)
@@ -763,7 +840,7 @@ class Interface:
c = gajim.contacts.get_contact(account, array[0], array[1])
# c is a list when no resource is given. it probably means that contact
# is offline, so only on Contact instance
- if isinstance(c, list):
+ if isinstance(c, list) and len(c):
c = c[0]
if c: # c can be none if it's a gc contact
c.last_status_time = time.localtime(time.time() - array[2])
@@ -811,14 +888,13 @@ class Interface:
uf_show = helpers.get_uf_show(show)
ctrl.print_conversation(_('%s is now %s (%s)') % (nick, uf_show, status),
'status')
- ctrl.draw_banner()
ctrl.parent_win.redraw_tab(ctrl)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('GCPresence', (account, array))
def handle_event_gc_msg(self, account, array):
- # ('GC_MSG', account, (jid, msg, time))
+ # ('GC_MSG', account, (jid, msg, time, has_timestamp))
jids = array[0].split('/', 1)
room_jid = jids[0]
gc_control = self.msg_win_mgr.get_control(room_jid, account)
@@ -830,7 +906,7 @@ class Interface:
else:
# message from someone
nick = jids[1]
- gc_control.on_message(nick, array[1], array[2])
+ gc_control.on_message(nick, array[1], array[2], array[3])
if self.remote_ctrl:
self.remote_ctrl.raise_signal('GCMessage', (account, array))
@@ -851,10 +927,18 @@ class Interface:
def handle_event_gc_config(self, account, array):
#('GC_CONFIG', account, (jid, config)) config is a dict
- jid = array[0].split('/')[0]
- if not self.instances[account]['gc_config'].has_key(jid):
- self.instances[account]['gc_config'][jid] = \
- config.GroupchatConfigWindow(account, jid, array[1])
+ room_jid = array[0].split('/')[0]
+ if room_jid in gajim.automatic_rooms[account]:
+ # use default configuration
+ gajim.connections[account].send_gc_config(room_jid, array[1])
+ # invite contacts
+ if gajim.automatic_rooms[account][room_jid].has_key('invities'):
+ for jid in gajim.automatic_rooms[account][room_jid]['invities']:
+ gajim.connections[account].send_invite(room_jid, jid)
+ del gajim.automatic_rooms[account][room_jid]
+ elif not self.instances[account]['gc_config'].has_key(room_jid):
+ self.instances[account]['gc_config'][room_jid] = \
+ config.GroupchatConfigWindow(account, room_jid, array[1])
def handle_event_gc_affiliation(self, account, array):
#('GC_AFFILIATION', account, (room_jid, affiliation, list)) list is list
@@ -875,8 +959,7 @@ class Interface:
self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
array[3]))
- if gajim.config.get('notify_on_new_message') and \
- helpers.allow_showing_notification(account):
+ if helpers.allow_showing_notification(account, 'notify_on_new_message'):
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'gc_invitation.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
@@ -908,7 +991,7 @@ class Interface:
c = contacts[0]
self.roster.remove_contact(c, account)
gajim.contacts.remove_jid(account, jid)
- if gajim.awaiting_events[account].has_key(c.jid):
+ if gajim.events.get_events(account, c.jid):
keyID = ''
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split()
@@ -989,12 +1072,20 @@ class Interface:
def handle_event_gmail_notify(self, account, array):
jid = array[0]
gmail_new_messages = int(array[1])
+ gmail_messages_list = array[2]
if gajim.config.get('notify_on_new_gmail_email'):
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'single_msg_recv.png') #FIXME: find a better image
title = _('New E-mail on %(gmail_mail_address)s') % \
{'gmail_mail_address': jid}
text = i18n.ngettext('You have %d new E-mail message', 'You have %d new E-mail messages', gmail_new_messages, gmail_new_messages, gmail_new_messages)
+
+ if gajim.config.get('notify_on_new_gmail_email_extra'):
+ for gmessage in gmail_messages_list:
+ # each message has a 'From', 'Subject' and 'Snippet' field
+ text += _('\nFrom: %(from_address)s') % \
+ {'from_address': gmessage['From']}
+
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
notify.popup(_('New E-mail'), jid, account, 'gmail',
path_to_image = path, title = title, text = text)
@@ -1035,71 +1126,59 @@ class Interface:
path_to_bw_file = path_to_file + '_notif_size_bw.png'
bwbuf.save(path_to_bw_file, 'png')
- def add_event(self, account, jid, typ, args):
- '''add an event to the awaiting_events var'''
- # We add it to the awaiting_events queue
+ def add_event(self, account, jid, type_, args):
+ '''add an event to the gajim.events var'''
+ # We add it to the gajim.events queue
# Do we have a queue?
jid = gajim.get_jid_without_resource(jid)
- qs = gajim.awaiting_events[account]
- no_queue = False
- if not qs.has_key(jid):
- no_queue = True
- qs[jid] = []
- qs[jid].append((typ, args))
- self.roster.nb_unread += 1
+ no_queue = len(gajim.events.get_events(account, jid)) == 0
+ event_type = None
+ # type_ can be gc-invitation file-send-error file-error file-request-error
+ # file-request file-completed file-stopped
+ # event_type can be in advancedNotificationWindow.events_list
+ event_types = {'file-request': 'ft_request',
+ 'file-completed': 'ft_finished'}
+ if type_ in event_types:
+ event_type = event_types[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_, args,
+ 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
self.roster.draw_contact(jid, account)
- if self.systray_enabled:
- self.systray.add_jid(jid, account, typ)
- def redraw_roster_systray(self, account, jid, typ = None):
- self.roster.nb_unread -= 1
- self.roster.show_title()
- self.roster.draw_contact(jid, account)
- if self.systray_enabled:
- self.systray.remove_jid(jid, account, typ)
+ def remove_first_event(self, account, jid, type_ = None):
+ event = gajim.events.get_first_event(account, jid, type_)
+ self.remove_event(account, jid, event)
- def remove_first_event(self, account, jid, typ = None):
- qs = gajim.awaiting_events[account]
- event = gajim.get_first_event(account, jid, typ)
- qs[jid].remove(event)
- # Is it the last event?
- if not len(qs[jid]):
- del qs[jid]
+ def remove_event(self, account, jid, event):
+ if gajim.events.remove_events(account, jid, event):
+ # No such event found
+ return
+ # no other event?
+ if not len(gajim.events.get_events(account, jid)):
if not gajim.config.get('showoffline'):
contact = gajim.contacts.get_contact_with_highest_priority(account,
jid)
if contact:
self.roster.really_remove_contact(contact, account)
- self.redraw_roster_systray(account, jid, typ)
-
- def remove_event(self, account, jid, event):
- qs = gajim.awaiting_events[account]
- if not event in qs[jid]:
- return
- qs[jid].remove(event)
- # Is it the last event?
- if not len(qs[jid]):
- del qs[jid]
- if not gajim.config.get('showoffline'):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if contact:
- self.roster.really_remove_contact(contact, account)
- self.redraw_roster_systray(account, jid, event[0])
+ self.roster.show_title()
+ self.roster.draw_contact(jid, account)
def handle_event_file_request_error(self, account, array):
- jid = array[0]
- file_props = array[1]
+ # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
+ jid, file_props, errmsg = array
ft = self.instances['file_transfers']
ft.set_status(file_props['type'], file_props['sid'], 'stop')
errno = file_props['error']
if helpers.allow_popup_window(account):
if errno in (-4, -5):
- ft.show_stopped(jid, file_props)
+ ft.show_stopped(jid, file_props, errmsg)
else:
ft.show_request_error(file_props)
return
@@ -1114,7 +1193,7 @@ class Interface:
if helpers.allow_showing_notification(account):
# check if we should be notified
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png')
-
+
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
event_type = _('File Transfer Error')
notify.popup(event_type, jid, account, msg_type, path,
@@ -1137,7 +1216,8 @@ class Interface:
if helpers.allow_showing_notification(account):
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'ft_request.png')
- txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(account, jid)
+ txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
+ account, jid)
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
event_type = _('File Transfer Request')
notify.popup(event_type, jid, account, 'file-request',
@@ -1146,7 +1226,7 @@ class Interface:
def handle_event_file_progress(self, account, file_props):
self.instances['file_transfers'].set_progress(file_props['type'],
file_props['sid'], file_props['received-len'])
-
+
def handle_event_file_rcv_completed(self, account, file_props):
ft = self.instances['file_transfers']
if file_props['error'] == 0:
@@ -1172,13 +1252,14 @@ class Interface:
msg_type = ''
event_type = ''
- if file_props['error'] == 0 and gajim.config.get('notify_on_file_complete'):
+ if file_props['error'] == 0 and gajim.config.get(
+ 'notify_on_file_complete'):
msg_type = 'file-completed'
event_type = _('File Transfer Completed')
elif file_props['error'] == -1:
msg_type = 'file-stopped'
event_type = _('File Transfer Stopped')
-
+
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
@@ -1188,7 +1269,7 @@ class Interface:
if msg_type:
self.add_event(account, jid, msg_type, file_props)
-
+
if file_props is not None:
if file_props['type'] == 'r':
# get the name of the sender, as it is in the roster
@@ -1260,6 +1341,8 @@ class Interface:
def handle_event_signed_in(self, account, empty):
'''SIGNED_IN event is emitted when we sign in, so handle it'''
+ # block signed in notifications for 30 seconds
+ gajim.block_signed_in_notifications[account] = True
self.roster.actions_menu_needs_rebuild = True
if gajim.interface.sleeper.getState() != common.sleepy.STATE_UNKNOWN and \
gajim.connections[account].connected in (2, 3):
@@ -1289,6 +1372,31 @@ class Interface:
def handle_event_metacontacts(self, account, tags_list):
gajim.contacts.define_metacontacts(account, tags_list)
+ def handle_event_privacy_lists_received(self, account, data):
+ # ('PRIVACY_LISTS_RECEIVED', account, list)
+ if not self.instances.has_key(account):
+ return
+ if self.instances[account].has_key('privacy_lists'):
+ self.instances[account]['privacy_lists'].privacy_lists_received(data)
+
+ def handle_event_privacy_list_received(self, account, data):
+ # ('PRIVACY_LISTS_RECEIVED', account, (name, rules))
+ if not self.instances.has_key(account):
+ return
+ name = data[0]
+ rules = data[1]
+ if self.instances[account].has_key('privacy_list_%s' % name):
+ self.instances[account]['privacy_list_%s' % name].\
+ privacy_list_received(rules)
+
+ def handle_event_privacy_lists_active_default(self, account, data):
+ if not data:
+ return
+ # Send to all privacy_list_* windows as we can't know which one asked
+ for win in self.instances[account]:
+ if win.startswith('privacy_list_'):
+ self.instances[account][win].check_active_default(data)
+
def read_sleepy(self):
'''Check idle status and change that status if needed'''
if not self.sleeper.poll():
@@ -1342,12 +1450,12 @@ class Interface:
return False
def show_systray(self):
- self.systray.show_icon()
self.systray_enabled = True
+ self.systray.show_icon()
def hide_systray(self):
- self.systray.hide_icon()
self.systray_enabled = False
+ self.systray.hide_icon()
def image_is_ok(self, image):
if not os.path.exists(image):
@@ -1577,6 +1685,7 @@ class Interface:
'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,
@@ -1589,6 +1698,10 @@ class Interface:
'ASK_NEW_NICK': self.handle_event_ask_new_nick,
'SIGNED_IN': self.handle_event_signed_in,
'METACONTACTS': self.handle_event_metacontacts,
+ '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,
}
gajim.handlers = self.handlers
@@ -1608,14 +1721,14 @@ class Interface:
err_str)
sys.exit()
- def handle_event(self, account, jid, typ):
+ def handle_event(self, account, jid, type_):
w = None
fjid = jid
resource = gajim.get_resource_from_jid(jid)
jid = gajim.get_jid_without_resource(jid)
- if typ == message_control.TYPE_GC:
+ if type_ in ('printed_gc_msg', 'gc_msg'):
w = self.msg_win_mgr.get_window(jid, account)
- elif typ == message_control.TYPE_CHAT:
+ elif type_ in ('printed_chat', 'chat'):
if self.msg_win_mgr.has_window(fjid, account):
w = self.msg_win_mgr.get_window(fjid, account)
else:
@@ -1625,30 +1738,30 @@ class Interface:
self.roster.new_chat(contact, account, resource = resource)
w = self.msg_win_mgr.get_window(fjid, account)
gajim.last_message_time[account][jid] = 0 # long time ago
- elif typ == message_control.TYPE_PM:
+ elif type_ in ('printed_pm', 'pm'):
if self.msg_win_mgr.has_window(fjid, account):
w = self.msg_win_mgr.get_window(fjid, account)
else:
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,
- name = nick, show = show)
+ gc_contact = gajim.contacts.create_gc_contact(
+ room_jid = room_jid, name = nick, show = show)
c = gajim.contacts.contact_from_gc_contact(gc_contact)
self.roster.new_chat(c, account, private_chat = True)
w = self.msg_win_mgr.get_window(fjid, account)
- elif typ in ('normal', 'file-request', 'file-request-error',
- 'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
+ elif type_ in ('normal', 'file-request', 'file-request-error',
+ 'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
# Get the first single message event
- ev = gajim.get_first_event(account, jid, typ)
+ event = gajim.events.get_first_event(account, jid, type_)
# Open the window
- self.roster.open_event(account, jid, ev)
- elif typ == 'gmail':
+ self.roster.open_event(account, jid, event)
+ elif type_ == 'gmail':
if gajim.config.get_per('accounts', account, 'savepass'):
url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\
(urllib.quote(gajim.config.get_per('accounts', account, 'name')),
@@ -1656,12 +1769,12 @@ class Interface:
else:
url = ('http://mail.google.com/')
helpers.launch_browser_mailer('url', url)
- elif typ == 'gc-invitation':
- ev = gajim.get_first_event(account, jid, typ)
- data = ev[1]
+ elif type_ == 'gc-invitation':
+ event = gajim.events.get_first_event(account, jid, type_)
+ data = event.parameters
dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
data[1])
- self.remove_first_event(account, jid, typ)
+ gajim.events.remove_events(account, jid, event)
if w:
w.set_active_tab(fjid, account)
w.window.present()
@@ -1753,14 +1866,13 @@ class Interface:
self.instances = {'logs': {}}
for a in gajim.connections:
- self.instances[a] = {'infos': {}, 'disco': {}, 'chats': {},
- 'gc': {}, 'gc_config': {}}
+ self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}}
gajim.contacts.add_account(a)
gajim.groups[a] = {}
gajim.gc_connected[a] = {}
+ gajim.automatic_rooms[a] = {}
gajim.newly_added[a] = []
gajim.to_be_removed[a] = []
- gajim.awaiting_events[a] = {}
gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
gajim.block_signed_in_notifications[a] = True
gajim.sleeper_state[a] = 0
@@ -1814,12 +1926,34 @@ class Interface:
# get instances for windows/dialogs that will show_all()/hide()
self.instances['file_transfers'] = dialogs.FileTransfersWindow()
+ # get transports type from DB
+ gajim.transport_type = gajim.logger.get_transports_type()
+
+ # test is dictionnary is present for speller
+ if gajim.config.get('use_speller'):
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ tv = gtk.TextView()
+ try:
+ import gtkspell
+ spell = gtkspell.Spell(tv, lang)
+ except:
+ dialogs.ErrorDialog(
+ _('Dictionary for lang %s not available') % lang,
+ _('You have to install %s dictionary to use spellchecking, or '
+ 'choose another language by setting the speller_language option.'
+ ) % lang)
+ gajim.config.set('use_speller', False)
gobject.timeout_add(100, self.autoconnect)
gobject.timeout_add(200, self.process_connections)
gobject.timeout_add(500, self.read_sleepy)
if __name__ == '__main__':
- signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
+ def sigint_cb(num, stack):
+ sys.exit(5)
+ # ^C exits the application normally to delete pid file
+ signal.signal(signal.SIGINT, sigint_cb)
if os.name != 'nt':
# Session Management support
@@ -1847,33 +1981,7 @@ if __name__ == '__main__':
cli.set_restart_command(len(argv), argv)
gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
-
-
- # Migrate old logs if we have such olds logs
- from common import logger
- from migrate_logs_to_dot9_db import PATH_TO_LOGS_BASE_DIR
- LOG_DB_PATH = logger.LOG_DB_PATH
- if not os.path.exists(LOG_DB_PATH) and os.path.isdir(PATH_TO_LOGS_BASE_DIR):
- import Queue
- q = Queue.Queue(100)
- dialog = dialogs.ProgressDialog(_('Migrating Logs...'),
- _('Please wait while logs are being migrated...'), q)
- if os.name == 'nt' and gtk.pygtk_version > (2, 8, 0):
- idlequeue = idlequeue.SelectIdleQueue()
- else:
- idlequeue = GlibIdleQueue()
- def on_result(*arg):
- dialog.dialog.destroy()
- dialog.dialog = None
- gobject.source_remove(dialog.update_progressbar_timeout_id)
- gajim.logger.init_vars()
- check_paths.check_and_possibly_create_paths()
- Interface()
- m = MigrateCommand(on_result)
- m.set_idlequeue(idlequeue)
- m.start()
- else:
- check_paths.check_and_possibly_create_paths()
- Interface()
+
+ check_paths.check_and_possibly_create_paths()
+ Interface()
gtk.main()
-
diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py
index c57ed2665..cc9b1742e 100644
--- a/src/gajim_themes_window.py
+++ b/src/gajim_themes_window.py
@@ -25,23 +25,18 @@
##
import gtk
-import gtk.glade
import pango
import dialogs
import gtkgui_helpers
from common import gajim
-from common import i18n
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain (APP, i18n.DIR)
-gtk.glade.textdomain (APP)
class GajimThemesWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('gajim_themes_window.glade')
self.window = self.xml.get_widget('gajim_themes_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
self.options = ['account', 'group', 'contact', 'banner']
self.options_combobox = self.xml.get_widget('options_combobox')
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index bd7a40dc2..38e20e5bf 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -23,7 +23,6 @@
import os
import time
import gtk
-import gtk.glade
import pango
import gobject
import gtkgui_helpers
@@ -40,13 +39,6 @@ from common import helpers
from chat_control import ChatControl
from chat_control import ChatControlBase
from conversation_textview import ConversationTextview
-from common import i18n
-
-_ = i18n._
-Q_ = i18n.Q_
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
#(status_image, type, nick, shown_nick)
(
@@ -127,6 +119,13 @@ class PrivateChatControl(ChatControl):
return
ChatControl.send_message(self, message)
+
+ def update_ui(self):
+ if self.contact.show == 'offline':
+ self.got_disconnected()
+ else:
+ self.got_connected()
+ ChatControl.update_ui(self)
class GroupchatControl(ChatControlBase):
@@ -183,7 +182,7 @@ class GroupchatControl(ChatControlBase):
# alphanum sorted
self.muc_cmds = ['ban', 'chat', 'query', 'clear', 'close', 'compact',
'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick', 'part',
- 'say', 'topic']
+ 'names', 'say', 'topic']
# muc attention flag (when we are mentioned in a muc)
# if True, the room has mentioned us
self.attention_flag = False
@@ -197,10 +196,6 @@ class GroupchatControl(ChatControlBase):
self.tooltip = tooltips.GCTooltip()
- self.allow_focus_out_line = True
- # holds the iter's offset which points to the end of --- line
- self.focus_out_end_iter_offset = None
-
# connect the menuitems to their respective functions
xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
@@ -292,12 +287,9 @@ class GroupchatControl(ChatControlBase):
column.set_visible(False)
self.list_treeview.set_expander_column(column)
- id = self.msg_textview.connect('populate_popup',
- self.on_msg_textview_populate_popup)
- self.handlers[id] = self.msg_textview
# set an empty subject to show the room_jid
self.set_subject('')
- self.got_disconnected() #init some variables
+ self.got_disconnected() # init some variables
self.update_ui()
self.conv_textview.tv.grab_focus()
@@ -306,20 +298,18 @@ class GroupchatControl(ChatControlBase):
def on_msg_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend Clear
and the ability to insert a nick'''
+ ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
item = gtk.SeparatorMenuItem()
menu.prepend(item)
- item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
- menu.prepend(item)
- id = item.connect('activate', self.msg_textview.clear)
- self.handlers[id] = item
item = gtk.MenuItem(_('Insert Nickname'))
menu.prepend(item)
submenu = gtk.Menu()
item.set_submenu(submenu)
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- item = gtk.MenuItem(nick)
+ for nick in sorted(gajim.contacts.get_nick_list(self.account,
+ self.room_jid)):
+ item = gtk.MenuItem(nick, use_underline = False)
submenu.append(item)
id = item.connect('activate', self.append_nick_in_msg_textview, nick)
self.handlers[id] = item
@@ -333,7 +323,7 @@ class GroupchatControl(ChatControlBase):
def _on_window_focus_in_event(self, widget, event):
'''When window gets focus'''
if self.parent_win.get_active_jid() == self.room_jid:
- self.allow_focus_out_line = True
+ self.conv_textview.allow_focus_out_line = True
def on_treeview_size_allocate(self, widget, allocation):
'''The MUC treeview has resized. Move the hpaned in all tabs to match'''
@@ -440,21 +430,21 @@ class GroupchatControl(ChatControlBase):
childs[3].set_sensitive(False)
return menu
- def on_message(self, nick, msg, tim):
+ def on_message(self, nick, msg, tim, has_timestamp = False):
if not nick:
# message from server
self.print_conversation(msg, tim = tim)
else:
# message from someone
- self.print_conversation(msg, nick, tim)
+ if has_timestamp:
+ self.print_old_conversation(msg, nick, tim)
+ else:
+ self.print_conversation(msg, nick, tim)
def on_private_message(self, nick, msg, tim):
# Do we have a queue?
fjid = self.room_jid + '/' + nick
- qs = gajim.awaiting_events[self.account]
- no_queue = True
- if qs.has_key(fjid):
- no_queue = False
+ no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
# We print if window is opened
pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
@@ -462,9 +452,9 @@ class GroupchatControl(ChatControlBase):
pm_control.print_conversation(msg, tim = tim)
return
- if no_queue:
- qs[fjid] = []
- qs[fjid].append(('chat', (msg, '', 'incoming', tim, False, '')))
+ event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
+ False, '', None))
+ gajim.events.add_event(self.account, fjid, event)
autopopup = gajim.config.get('autopopup')
autopopupaway = gajim.config.get('autopopupaway')
@@ -479,8 +469,6 @@ class GroupchatControl(ChatControlBase):
self.room_jid, icon_name = 'message')
image = state_images['message']
model[iter][C_IMG] = image
- if gajim.interface.systray_enabled:
- gajim.interface.systray.add_jid(fjid, self.account, 'pm')
self.parent_win.show_title()
else:
self._start_private_message(nick)
@@ -511,6 +499,24 @@ class GroupchatControl(ChatControlBase):
fin = True
return None
+ gc_count_nicknames_colors = 0
+ gc_custom_colors = {}
+
+ def print_old_conversation(self, text, contact, tim = None):
+ if isinstance(text, str):
+ text = unicode(text, 'utf-8')
+ if contact == self.nick: # it's us
+ kind = 'outgoing'
+ else:
+ kind = 'incoming'
+ if gajim.config.get('restored_messages_small'):
+ small_attr = ['small']
+ else:
+ small_attr = []
+ ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
+ small_attr, small_attr + ['restored_message'],
+ small_attr + ['restored_message'])
+
def print_conversation(self, text, contact = '', tim = None):
'''Print a line in the conversation:
if contact is set: it's a message from someone or an info message (contact
@@ -536,6 +542,19 @@ class GroupchatControl(ChatControlBase):
if kind == 'incoming': # it's a message NOT from us
# highlighting and sounds
(highlight, sound) = self.highlighting_for_message(text, tim)
+ if self.gc_custom_colors.has_key(contact):
+ other_tags_for_name.append('gc_nickname_color_' + \
+ str(self.gc_custom_colors[contact]))
+ else:
+ self.gc_count_nicknames_colors += 1
+ number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
+ split(':'))
+ if self.gc_count_nicknames_colors == number_of_colors:
+ self.gc_count_nicknames_colors = 0
+ self.gc_custom_colors[contact] = \
+ self.gc_count_nicknames_colors
+ other_tags_for_name.append('gc_nickname_color_' + \
+ str(self.gc_count_nicknames_colors))
if highlight:
# muc-specific chatstate
self.parent_win.redraw_tab(self, 'attention')
@@ -545,12 +564,23 @@ class GroupchatControl(ChatControlBase):
helpers.play_sound('muc_message_received')
elif sound == 'highlight':
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]))
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)
+ def get_nb_unread(self):
+ nb = len(gajim.events.get_events(self.account, self.room_jid,
+ ['printed_gc_msg']))
+ 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']))
+ return nb
+
def highlighting_for_message(self, text, tim):
'''Returns a 2-Tuple. The first says whether or not to highlight the
text, the second, what sound to play.'''
@@ -587,64 +617,7 @@ class GroupchatControl(ChatControlBase):
# we have full focus (we are reading it!)
return
- if not self.allow_focus_out_line:
- # if room did not receive focus-in from the last time we added
- # --- line then do not readd
- return
-
- print_focus_out_line = False
- buffer = self.conv_textview.tv.get_buffer()
-
- if self.focus_out_end_iter_offset is None:
- # this happens only first time we focus out on this room
- print_focus_out_line = True
-
- else:
- if self.focus_out_end_iter_offset != buffer.get_end_iter().get_offset():
- # this means after last-focus something was printed
- # (else end_iter's offset is the same as before)
- # only then print ---- line (eg. we avoid printing many following
- # ---- lines)
- print_focus_out_line = True
-
- if print_focus_out_line and buffer.get_char_count() > 0:
- buffer.begin_user_action()
-
- # remove previous focus out line if such focus out line exists
- if self.focus_out_end_iter_offset is not None:
- end_iter_for_previous_line = buffer.get_iter_at_offset(
- self.focus_out_end_iter_offset)
- begin_iter_for_previous_line = end_iter_for_previous_line.copy()
- begin_iter_for_previous_line.backward_chars(2) # img_char+1 (the '\n')
-
- # remove focus out line
- buffer.delete(begin_iter_for_previous_line,
- end_iter_for_previous_line)
-
- # add the new focus out line
- # FIXME: Why is this loaded from disk everytime
- path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
- focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
- end_iter = buffer.get_end_iter()
- buffer.insert(end_iter, '\n')
- buffer.insert_pixbuf(end_iter, focus_out_line_pixbuf)
-
- end_iter = buffer.get_end_iter()
- before_img_iter = end_iter.copy()
- before_img_iter.backward_char() # one char back (an image also takes one char)
- buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
- #FIXME: remove this workaround when bug is fixed
- # c http://bugzilla.gnome.org/show_bug.cgi?id=318569
-
- self.allow_focus_out_line = False
-
- # update the iter we hold to make comparison the next time
- self.focus_out_end_iter_offset = buffer.get_end_iter().get_offset()
-
- buffer.end_user_action()
-
- # scroll to the end (via idle in case the scrollbar has appeared)
- gobject.idle_add(self.conv_textview.scroll_to_end)
+ self.conv_textview.show_focus_out_line()
def needs_visual_notification(self, text):
'''checks text to see whether any of the words in (muc_highlight_words
@@ -740,7 +713,7 @@ class GroupchatControl(ChatControlBase):
model = self.list_treeview.get_model()
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
state_images = gajim.interface.roster.jabber_state_images['16']
- if gajim.awaiting_events[self.account].has_key(self.room_jid + '/' + nick):
+ if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
image = state_images['message']
else:
image = state_images[gc_contact.show]
@@ -823,9 +796,29 @@ class GroupchatControl(ChatControlBase):
# after that, but that doesn't hurt
self.add_contact_to_roster(new_nick, show, role, affiliation,
status, jid)
+ # keep nickname color
+ if nick in self.gc_custom_colors:
+ self.gc_custom_colors[new_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)
+ new_path = os.path.join(gajim.VCARD_PATH, puny_jid, 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)
+ for old_file in files:
+ if os.path.exists(old_file):
+ os.rename(old_file, files[old_file])
self.print_conversation(s, 'info')
- if not gajim.awaiting_events[self.account].has_key(self.room_jid + '/' + nick):
+ if len(gajim.events.get_events(self.account,
+ self.room_jid + '/' + nick)) == 0:
self.remove_contact(nick)
else:
c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
@@ -870,15 +863,18 @@ class GroupchatControl(ChatControlBase):
break
if print_status is None:
print_status = gajim.config.get('print_status_in_muc')
+ nick_jid = nick
+ if jid:
+ nick_jid += ' (%s)' % jid
if show == 'offline' and print_status in ('all', 'in_and_out'):
- st = _('%s has left') % nick
+ st = _('%s has left') % nick_jid
if reason:
st += ' [%s]' % reason
else:
if newly_created and print_status in ('all', 'in_and_out'):
- st = _('%s has joined the room') % nick
+ st = _('%s has joined the room') % nick_jid
elif print_status == 'all':
- st = _('%s is now %s') % (nick, helpers.get_uf_show(show))
+ st = _('%s is now %s') % (nick_jid, helpers.get_uf_show(show))
if st:
if status:
st += ' (' + status + ')'
@@ -974,7 +970,7 @@ class GroupchatControl(ChatControlBase):
if command == 'nick':
# example: /nick foo
- if len(message_array):
+ if len(message_array) and message_array[0] != self.nick:
nick = message_array[0]
gajim.connections[self.account].change_gc_nick(self.room_jid, nick)
self.clear(self.msg_textview)
@@ -985,14 +981,19 @@ class GroupchatControl(ChatControlBase):
# Open a chat window to the specified nick
# example: /query foo
if len(message_array):
- nick = message_array.pop(0)
- nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
- if nick in nicks:
- self.on_send_pm(nick = nick)
- self.clear(self.msg_textview)
+ nick0 = message_array.pop(0)
+ if nick0[-1] == ' ':
+ nick1 = nick0[:-1]
else:
- self.print_conversation(_('Nickname not found: %s') % nick,
- 'info')
+ nick1 = nick0
+ nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+ for nick in [nick0, nick1]:
+ if nick in nicks:
+ self.on_send_pm(nick = nick)
+ self.clear(self.msg_textview)
+ return True
+ self.print_conversation(_('Nickname not found: %s') % \
+ nick0, 'info')
else:
self.get_command_help(command)
return True
@@ -1002,7 +1003,8 @@ class GroupchatControl(ChatControlBase):
if len(message_array):
message_array = message_array[0].split()
nick = message_array.pop(0)
- room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+ room_nicks = gajim.contacts.get_nick_list(self.account,
+ self.room_jid)
if nick in room_nicks:
privmsg = ' '.join(message_array)
self.on_send_pm(nick=nick, msg=privmsg)
@@ -1122,6 +1124,21 @@ class GroupchatControl(ChatControlBase):
else:
self.get_command_help(command)
return True
+ elif command == 'names':
+ # print the list of participants
+ nicklist=''
+ i=0
+ for contact in self.iter_contact_rows():
+ nicklist += '[ %-12.12s ] ' % (contact[C_NICK].decode('utf-8'))
+ i=i+1
+ if i == 3:
+ i=0
+ self.print_conversation(nicklist, 'info')
+ nicklist=''
+ if nicklist:
+ self.print_conversation(nicklist, 'info')
+ self.clear(self.msg_textview)
+ return True
elif command == 'help':
if len(message_array):
subcommand = message_array.pop(0)
@@ -1205,6 +1222,10 @@ class GroupchatControl(ChatControlBase):
s = _('Usage: /%s , changes your nickname in current room.')\
% command
self.print_conversation(s, 'info')
+ elif command == 'names':
+ s = _('Usage: /%s , display the names of room occupants.')\
+ % command
+ self.print_conversation(s, 'info')
elif command == 'topic':
self.print_conversation(_('Usage: /%s [topic], displays or updates the'
' current room topic.') % command, 'info')
@@ -1287,9 +1308,8 @@ class GroupchatControl(ChatControlBase):
nb = 0
for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
fjid = self.room_jid + '/' + nick
- if gajim.awaiting_events[self.account].has_key(fjid):
- # gc can only have messages as event
- nb += len(gajim.awaiting_events[self.account][fjid])
+ nb += len(gajim.events.get_events(self.account, fjid))
+ # gc can only have messages as event
return nb
def _on_change_subject_menuitem_activate(self, widget):
@@ -1559,8 +1579,8 @@ class GroupchatControl(ChatControlBase):
# show the popup now!
menu = xml.get_widget('gc_occupants_menu')
- menu.popup(None, None, None, event.button, event.time)
menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
def _start_private_message(self, nick):
gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py
index 45dc13ec0..875da8ee4 100644
--- a/src/gtkexcepthook.py
+++ b/src/gtkexcepthook.py
@@ -27,6 +27,7 @@
##
import sys
+import os
import traceback
import threading
@@ -36,9 +37,7 @@ import dialogs
from cStringIO import StringIO
from common import helpers
-from common import i18n
-_ = i18n._
_exception_in_progress = threading.Lock()
def _info(type, value, tb):
@@ -102,7 +101,8 @@ def _info(type, value, tb):
_exception_in_progress.release()
-if not sys.stderr.isatty(): # gdb/kdm etc if we use startx this is not True
+# gdb/kdm etc if we use startx this is not True
+if os.name == 'nt' or not sys.stderr.isatty():
#FIXME: maybe always show dialog?
_excepthook_save = sys.excepthook
sys.excepthook = _info
diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py
index e6657b04d..fb6e3eaa8 100644
--- a/src/gtkgui_helpers.py
+++ b/src/gtkgui_helpers.py
@@ -19,12 +19,14 @@
import xml.sax.saxutils
import gtk
+import gtk.glade
import gobject
import pango
import os
import sys
import vcard
+import dialogs
HAS_PYWIN32 = True
@@ -37,11 +39,12 @@ if os.name == 'nt':
HAS_PYWIN32 = False
from common import i18n
-i18n.init()
-_ = i18n._
from common import gajim
from common import helpers
+gtk.glade.bindtextdomain(i18n.APP, i18n.DIR)
+gtk.glade.textdomain(i18n.APP)
+
screen_w = gtk.gdk.screen_width()
screen_h = gtk.gdk.screen_height()
@@ -123,8 +126,8 @@ def get_default_font():
# in try because daemon may not be there
client = gconf.client_get_default()
- return helpers.ensure_unicode_string(
- client.get_string('/desktop/gnome/interface/font_name'))
+ return client.get_string('/desktop/gnome/interface/font_name'
+ ).decode('utf-8')
except:
pass
@@ -144,8 +147,7 @@ def get_default_font():
for line in file(xfce_config_file):
if line.find('name="Gtk/FontName"') != -1:
start = line.find('value="') + 7
- return helpers.ensure_unicode_string(
- line[start:line.find('"', start)])
+ return line[start:line.find('"', start)].decode('utf-8')
except:
#we talk about file
print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file
@@ -160,7 +162,7 @@ def get_default_font():
font_name = values[0]
font_size = values[1]
font_string = '%s %s' % (font_name, font_size) # Verdana 9
- return helpers.ensure_unicode_string(font_string)
+ return font_string.decode('utf-8')
except:
#we talk about file
print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file
@@ -403,7 +405,7 @@ def possibly_move_window_in_current_desktop(window):
if current_virtual_desktop_no != window_virtual_desktop:
# we are in another VD that the window was
# so show it in current VD
- window.show()
+ window.present()
def file_is_locked(path_to_file):
'''returns True if file is locked (WINDOWS ONLY)'''
@@ -455,7 +457,7 @@ def _get_fade_color(treeview, selected, focused):
def get_scaled_pixbuf(pixbuf, kind):
'''returns scaled pixbuf, keeping ratio etc or None
- kind is either "chat" or "roster" or "notification" or "tooltip"'''
+ kind is either "chat", "roster", "notification", "tooltip", "vcard"'''
# resize to a width / height for the avatar not to have distortion
# (keep aspect ratio)
@@ -666,3 +668,70 @@ 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_ok(widget):
+ def on_ok2(widget, file_path, pixbuf):
+ pixbuf.save(file_path, 'jpeg')
+ dialog2.destroy()
+ dialog.destroy()
+
+ file_path = dialog.get_filename()
+ file_path = decode_filechooser_file_paths((file_path,))[0]
+ if os.path.exists(file_path):
+ dialog2 = dialogs.FTOverwriteConfirmationDialog(
+ _('This file already exists'), _('What do you want to do?'),
+ False)
+ dialog2.set_transient_for(dialog)
+ dialog2.set_destroy_with_parent(True)
+ response = dialog2.get_response()
+ if response < 0:
+ return
+
+ # Get pixbuf
+ pixbuf = None
+ is_fake = False
+ if account and gajim.contacts.is_pm_from_jid(account, jid):
+ is_fake = True
+ pixbuf = get_avatar_pixbuf_from_cache(jid, is_fake)
+ ext = file_path.split('.')[-1]
+ type_ = ''
+ if not ext:
+ # Silently save as Jpeg image
+ file_path += '.jpeg'
+ type_ = 'jpeg'
+ elif ext == 'jpg':
+ type_ = 'jpeg'
+ else:
+ type_ = ext
+
+ # Save image
+ try:
+ pixbuf.save(file_path, type_)
+ except:
+ #XXX Check for permissions
+ os.remove(file_path)
+ new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
+ dialog2 = dialogs.ConfirmationDialog(_('Extension not supported'),
+ _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?') % {'type': type_, 'new_filename': new_file_path},
+ on_response_ok = (on_ok2, new_file_path, pixbuf))
+ else:
+ dialog.destroy()
+
+ def on_cancel(widget):
+ 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)
+
+ dialog.set_current_name(default_name)
+ dialog.connect('delete-event', lambda widget, event:
+ on_cancel(widget))
diff --git a/src/history_manager.py b/src/history_manager.py
index 8ddc0f29e..21d74491f 100755
--- a/src/history_manager.py
+++ b/src/history_manager.py
@@ -24,21 +24,17 @@ import sys
import os
import signal
import gtk
-import gtk.glade
import time
import locale
+from common import i18n
import exceptions
import dialogs
import gtkgui_helpers
from common.logger import LOG_DB_PATH, constants
from common import gajim
-from common import i18n
from common import helpers
-_ = i18n._
-gtk.glade.bindtextdomain(i18n.APP, i18n.DIR)
-gtk.glade.textdomain(i18n.APP)
# time, message, subject
(
@@ -468,8 +464,8 @@ class HistoryManager:
except ValueError:
pass
- file_.write(_('%(who)s on %(time)s said: %(message)s\n' % {'who': who,
- 'time': time_, 'message': message}))
+ file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {'who': who,
+ 'time': time_, 'message': message})
def _delete_jid_logs(self, liststore, list_of_paths):
paths_len = len(list_of_paths)
diff --git a/src/history_window.py b/src/history_window.py
index 4214b734c..fba1684ff 100644
--- a/src/history_window.py
+++ b/src/history_window.py
@@ -24,7 +24,6 @@
##
import gtk
-import gtk.glade
import gobject
import time
import calendar
@@ -34,17 +33,11 @@ import conversation_textview
from common import gajim
from common import helpers
-from common import i18n
from common.logger import Constants
constants = Constants()
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
-
# contact_name, date, message, time
(
C_CONTACT_NAME,
@@ -120,7 +113,7 @@ class HistoryWindow:
# select and show logs for last date we have logs with contact
# and if we don't have logs at all, default to today
- result = gajim.logger.get_last_date_that_has_logs(self.jid)
+ result = gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
if result is None:
date = time.localtime()
else:
@@ -157,7 +150,7 @@ class HistoryWindow:
asks for days in this month if they have logs it bolds them (marks them)'''
weekday, days_in_this_month = calendar.monthrange(year, month)
log_days = gajim.logger.get_days_with_logs(self.jid, year,
- month, days_in_this_month)
+ month, days_in_this_month, self.account)
for day in log_days:
widget.mark_day(day)
yield True
@@ -197,7 +190,8 @@ class HistoryWindow:
'''adds all the lines for given date in textbuffer'''
self.history_buffer.set_text('') # clear the buffer first
self.last_time_printout = 0
- lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day)
+
+ lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
# lines holds list with tupples that have:
# contact_name, time, kind, show, message
for line in lines:
@@ -325,7 +319,8 @@ class HistoryWindow:
if text == '':
return
# contact_name, time, kind, show, message, subject
- results = gajim.logger.get_search_results_for_query(self.jid, text)
+ results = gajim.logger.get_search_results_for_query(
+ self.jid, text, self.account)
#FIXME:
# add "subject: | message: " in message column if kind is single
# also do we need show at all? (we do not search on subject)
diff --git a/src/message_control.py b/src/message_control.py
index df19f570f..1c1b0aa50 100644
--- a/src/message_control.py
+++ b/src/message_control.py
@@ -11,8 +11,6 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
-import gtk
-import gtk.glade
import gtkgui_helpers
from common import gajim
@@ -22,12 +20,6 @@ TYPE_CHAT = 'chat'
TYPE_GC = 'gc'
TYPE_PM = 'pm'
-####################
-# FIXME: Can't this stuff happen once?
-from common import i18n
-_ = i18n._
-APP = i18n.APP
-
####################
class MessageControl:
@@ -47,7 +39,6 @@ class MessageControl:
self.account = account
self.hide_chat_buttons_always = False
self.hide_chat_buttons_current = False
- self.nb_unread = 0
self.resource = resource
gajim.last_message_time[self.account][self.get_full_jid()] = 0
@@ -125,16 +116,15 @@ class MessageControl:
pass
def get_specific_unread(self):
- n = 0
- if gajim.awaiting_events[self.account].has_key(self.contact.jid):
- n = len(gajim.awaiting_events[self.account][self.contact.jid])
- return n
+ return len(gajim.events.get_events(self.account, self.contact.jid))
def send_message(self, message, keyID = '', type = 'chat',
- chatstate = None, msg_id = None, composing_jep = None, resource = None):
+ chatstate = None, msg_id = None, composing_jep = None, resource = None,
+ user_nick = None):
'''Send the given message to the active tab'''
jid = self.contact.jid
# Send and update history
gajim.connections[self.account].send_message(jid, message, keyID,
- type = type, chatstate = chatstate, msg_id = msg_id,
- composing_jep = composing_jep, resource = self.resource)
+ type = type, chatstate = chatstate, msg_id = msg_id,
+ composing_jep = composing_jep, resource = self.resource,
+ user_nick = user_nick)
diff --git a/src/message_textview.py b/src/message_textview.py
index 179827880..cdf21ab54 100644
--- a/src/message_textview.py
+++ b/src/message_textview.py
@@ -44,12 +44,14 @@ class MessageTextView(gtk.TextView):
self.set_accepts_tab(True)
self.set_editable(True)
self.set_cursor_visible(True)
- self.set_wrap_mode(gtk.WRAP_WORD)
+ self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
self.set_left_margin(2)
self.set_right_margin(2)
self.set_pixels_above_lines(2)
self.set_pixels_below_lines(2)
+ self.lang = None # Lang used for spell checking
+
def destroy(self):
import gc
gobject.idle_add(lambda:gc.collect())
diff --git a/src/message_window.py b/src/message_window.py
index be1585a24..f190956c8 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -22,7 +22,7 @@
##
import gtk
-import gtk.glade
+import gobject
import common
import gtkgui_helpers
@@ -31,12 +31,6 @@ from chat_control import ChatControlBase
from common import gajim
-####################
-# FIXME: Can't this stuff happen once?
-from common import i18n
-_ = i18n._
-APP = i18n.APP
-
####################
class MessageWindow:
@@ -156,8 +150,17 @@ class MessageWindow:
fjid = control.get_full_jid()
self._controls[control.account][fjid] = control
- if self.get_num_controls() > 1:
+ if self.get_num_controls() == 2:
+ # is first conversation_textview scrolled down ?
+ scrolled = False
+ first_widget = self.notebook.get_nth_page(0)
+ ctrl = self._widget_to_control(first_widget)
+ conv_textview = ctrl.conv_textview
+ if conv_textview.at_the_end():
+ scrolled = True
self.notebook.set_show_tabs(True)
+ if scrolled:
+ gobject.idle_add(conv_textview.scroll_to_end_iter)
self.alignment.set_property('top-padding', 2)
# Add notebook page and connect up to the tab's close button
@@ -221,7 +224,7 @@ class MessageWindow:
gajim.config.get('notify_on_all_muc_messages') and not \
ctrl.attention_flag:
continue
- unread += ctrl.nb_unread
+ unread += ctrl.get_nb_unread()
unread_str = ''
if unread > 1:
@@ -277,9 +280,8 @@ class MessageWindow:
ctrl.shutdown()
# Update external state
- if gajim.interface.systray_enabled:
- gajim.interface.systray.remove_jid(ctrl.get_full_jid(), ctrl.account,
- ctrl.type_id)
+ gajim.events.remove_events(ctrl.account, ctrl.get_full_jid,
+ types = ['printed_msg', 'chat', 'gc_msg'])
del gajim.last_message_time[ctrl.account][ctrl.get_full_jid()]
self.disconnect_tab_dnd(ctrl.widget)
@@ -416,6 +418,8 @@ class MessageWindow:
ind = self.notebook.get_current_page()
current = ind
found = False
+ first_composing_ind = -1 # id of first composing ctrl to switch to
+ # if no others controls have awaiting events
# loop until finding an unread tab or having done a complete cycle
while True:
if forward == True: # look for the first unread tab on the right
@@ -426,15 +430,22 @@ class MessageWindow:
ind = ind - 1
if ind < 0:
ind = self.notebook.get_n_pages() - 1
- if ind == current:
- break # a complete cycle without finding an unread tab
ctrl = self.get_control(ind, None)
- if ctrl.nb_unread > 0:
+ if ctrl.get_nb_unread() > 0:
found = True
break # found
+ elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact
+ contact = ctrl.contact
+ if first_composing_ind == -1 and contact.chatstate == 'composing':
+ # If no composing contact found yet, check if this one is composing
+ first_composing_ind = ind
+ if ind == current:
+ break # a complete cycle without finding an unread tab
if found:
self.notebook.set_current_page(ind)
- else: # not found
+ elif first_composing_ind != -1:
+ self.notebook.set_current_page(first_composing_ind)
+ else: # not found and nobody composing
if forward: # CTRL + TAB
if current < (self.notebook.get_n_pages() - 1):
self.notebook.next_page()
@@ -641,13 +652,13 @@ class MessageWindowMgr:
if not gajim.config.get('saveposition'):
return
- if self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_ALWAYS):
+ if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
size = (gajim.config.get('msgwin-width'),
gajim.config.get('msgwin-height'))
elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
size = (gajim.config.get_per('accounts', acct, 'msgwin-width'),
gajim.config.get_per('accounts', acct, 'msgwin-height'))
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
+ elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE):
if type == message_control.TYPE_PM:
type = message_control.TYPE_CHAT
opt_width = type + '-msgwin-width'
@@ -705,6 +716,7 @@ class MessageWindowMgr:
win_type = type
win_role = type
elif self.mode == self.ONE_MSG_WINDOW_NEVER:
+ win_type = type
win_role = contact.jid
win = None
@@ -792,6 +804,10 @@ class MessageWindowMgr:
pos_y_key = type + '-msgwin-y-position'
size_width_key = type + '-msgwin-width'
size_height_key = type + '-msgwin-height'
+ elif self.mode == self.ONE_MSG_WINDOW_NEVER:
+ type = msg_win.type
+ size_width_key = type + '-msgwin-width'
+ size_height_key = type + '-msgwin-height'
if acct:
gajim.config.set_per('accounts', acct, size_width_key, width)
diff --git a/src/migrate_logs_to_dot9_db.py b/src/migrate_logs_to_dot9_db.py
deleted file mode 100755
index 7bb402822..000000000
--- a/src/migrate_logs_to_dot9_db.py
+++ /dev/null
@@ -1,271 +0,0 @@
-#!/bin/sh
-''':'
-exec python -OOt "$0" ${1+"$@"}
-' '''
-## Contributors for this file:
-## - Yann Le Boulanger
-## - Nikos Kouremenos
-##
-## Copyright (C) 2003-2004 Yann Le Boulanger
-## Vincent Hanquez
-## Copyright (C) 2005 Yann Le Boulanger
-## Vincent Hanquez
-## Nikos Kouremenos
-## Dimitur Kirov
-## Travis Shirk
-## Norman Rasmussen
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 2 only.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-
-import os
-import sre
-import sys
-import time
-import signal
-from common import logger
-from common import i18n
-
-_ = i18n._
-try:
- PREFERRED_ENCODING = sys.getpreferredencoding()
-except:
- PREFERRED_ENCODING = 'utf-8'
-from common.helpers import from_one_line, decode_string
-
-signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
-
-from pysqlite2 import dbapi2 as sqlite
-
-if os.name == 'nt':
- try:
- PATH_TO_LOGS_BASE_DIR = os.path.join(os.environ['appdata'], 'Gajim', 'Logs')
- PATH_TO_DB = os.path.join(os.environ['appdata'], 'Gajim', 'logs.db') # database is called logs.db
- except KeyError:
- # win9x
- PATH_TO_LOGS_BASE_DIR = '../src/Logs'
- PATH_TO_DB = '../src/logs.db'
-else:
- PATH_TO_LOGS_BASE_DIR = os.path.expanduser('~/.gajim/logs')
- PATH_TO_DB = os.path.expanduser('~/.gajim/logs.db') # database is called logs.db
-
-
-
-class Migration:
- def __init__(self):
- self.constants = logger.Constants()
- self.DONE = False
- self.PROCESSING = False
-
- if os.path.exists(PATH_TO_DB):
- print '%s already exists. Exiting..' % PATH_TO_DB
- sys.exit()
-
- self.jids_already_in = [] # jid we already put in DB
-
- def get_jid(self, dirname, filename):
- # jids.jid text column will be JID if TC-related, room_jid if GC-related,
- # ROOM_JID/nick if pm-related. Here I get names from filenames
- if dirname.endswith('logs') or dirname.endswith('Logs'):
- # we have file (not dir) in logs base dir, so it's TC
- jid = filename # file is JID
- else:
- # we are in a room folder (so it can be either pm or message in room)
- if filename == os.path.basename(dirname): # room/room
- jid = dirname # filename is ROOM_JID
- else: #room/nick it's pm
- jid = dirname + '/' + filename
-
- if jid.startswith('/'):
- p = len(PATH_TO_LOGS_BASE_DIR)
- jid = jid[p+1:]
- jid = jid.lower()
- return jid
-
- def decode_jid(self, string):
- '''try to decode (to make it Unicode instance) given jid'''
- string = decode_string(string)
- if isinstance(string, str):
- return None # decode failed
- return string
-
- def visit(self, arg, dirname, filenames):
- s = _('Visiting %s') % dirname
- if self.queue:
- self.queue.put(s)
- else:
- print s
- for filename in filenames:
- # Don't take this file into account, this is dup info
- # notifications are also in contact log file
- if filename in ('notify.log', 'README'):
- continue
- path_to_text_file = os.path.join(dirname, filename)
- if os.path.isdir(path_to_text_file):
- continue
-
- jid = self.get_jid(dirname, filename)
-
- jid = self.decode_jid(jid)
- if not jid:
- continue
-
- if filename == os.path.basename(dirname): # gajim@conf/gajim@conf then gajim@conf is type room
- jid_type = self.constants.JID_ROOM_TYPE
- #Type of log
- typ = 'room'
- else:
- jid_type = self.constants.JID_NORMAL_TYPE
- #Type of log
- typ = _('normal')
- s = _('Processing %s of type %s') % (jid, typ)
- if self.queue:
- self.queue.put(s.encode(PREFERRED_ENCODING))
- else:
- print s.encode(PREFERRED_ENCODING)
-
- JID_ID = None
- f = open(path_to_text_file, 'r')
- lines = f.readlines()
- for line in lines:
- line = from_one_line(line)
- splitted_line = line.split(':')
- if len(splitted_line) > 2:
- # type in logs is one of
- # 'gc', 'gcstatus', 'recv', 'sent' and if nothing of those
- # it is status
- # new db has:
- # status, gcstatus, gc_msg, (we only recv those 3),
- # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
- # to meet all our needs
- # here I convert
- # gc ==> gc_msg, gcstatus ==> gcstatus, recv ==> chat_msg_recv
- # sent ==> chat_msg_sent, status ==> status
- typ = splitted_line[1] # line[1] has type of logged message
- message_data = splitted_line[2:] # line[2:] has message data
- # line[0] is date,
- # some lines can be fucked up, just drop them
- try:
- tim = int(float(splitted_line[0]))
- except:
- continue
-
- contact_name = None
- show = None
- if typ == 'gc':
- contact_name = message_data[0]
- message = ':'.join(message_data[1:])
- kind = self.constants.KIND_GC_MSG
- elif typ == 'gcstatus':
- contact_name = message_data[0]
- show = message_data[1]
- message = ':'.join(message_data[2:]) # status msg
- kind = self.constants.KIND_GCSTATUS
- elif typ == 'recv':
- message = ':'.join(message_data[0:])
- kind = self.constants.KIND_CHAT_MSG_RECV
- elif typ == 'sent':
- message = ':'.join(message_data[0:])
- kind = self.constants.KIND_CHAT_MSG_SENT
- else: # status
- kind = self.constants.KIND_STATUS
- show = message_data[0]
- message = ':'.join(message_data[1:]) # status msg
-
- message = message[:-1] # remove last \n
- if not message:
- continue
-
- # jid is already in the DB, don't create a new row, just get his jid_id
- if not JID_ID:
- if jid in self.jids_already_in:
- self.cur.execute('SELECT jid_id FROM jids WHERE jid = "%s"' % jid)
- JID_ID = self.cur.fetchone()[0]
- else:
- self.jids_already_in.append(jid)
- self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)',
- (jid, jid_type))
- self.con.commit()
- JID_ID = self.cur.lastrowid
-
- sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message) '\
- 'VALUES (?, ?, ?, ?, ?, ?)'
-
- values = (JID_ID, contact_name, tim, kind, show, message)
- self.cur.execute(sql, values)
- self.con.commit()
-
- def migrate(self, queue = None):
- self.queue = queue
- self.con = sqlite.connect(PATH_TO_DB)
- os.chmod(PATH_TO_DB, 0600) # rw only for us
- self.cur = self.con.cursor()
- # create the tables
- # kind can be
- # status, gcstatus, gc_msg, (we only recv for those 3),
- # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
- # to meet all our needs
- # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code
- self.cur.executescript(
- '''
- CREATE TABLE jids(
- jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE unread_messages(
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER
- );
-
- CREATE TABLE logs(
- log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- contact_name TEXT,
- time INTEGER,
- kind INTEGER,
- show INTEGER,
- message TEXT,
- subject TEXT
- );
- '''
- )
-
- self.con.commit()
-
- self.PROCESSING = True
- os.path.walk(PATH_TO_LOGS_BASE_DIR, self.visit, None)
- s = '''
-
-We do not use plain-text files anymore, because they do not meet our needs.
-Those files here are logs for Gajim up until 0.8.2
-We now use an sqlite database called logs.db found in %s
-You can now safely remove your %s folder
-Thank you''' % (os.path.dirname(PATH_TO_LOGS_BASE_DIR), PATH_TO_LOGS_BASE_DIR)
- f = open(os.path.join(PATH_TO_LOGS_BASE_DIR, 'README'), 'w')
- f.write(s)
- f.close()
- if queue:
- queue.put(s)
- self.DONE = True
-
-if __name__ == '__main__':
- # magic argumen 'dont_wait' tells us that script is run from Gajim
- if len(sys.argv) < 2 or sys.argv[1] != 'dont_wait':
- print 'IMPORTNANT: PLEASE READ http://trac.gajim.org/wiki/MigrateLogToDot9DB'
- print 'Migration will start in 40 seconds unless you press Ctrl+C'
- time.sleep(40) # give the user time to act
- print
- print 'Starting Logs Migration'
- print '======================='
- print 'Please do NOT run Gajim until this script is over'
- m = Migration()
- m.migrate()
diff --git a/src/notify.py b/src/notify.py
index 201392899..908ff25d9 100644
--- a/src/notify.py
+++ b/src/notify.py
@@ -23,10 +23,7 @@ import dialogs
import gtkgui_helpers
from common import gajim
-from common import i18n
from common import helpers
-i18n.init()
-_ = i18n._
import dbus_support
if dbus_support.supported:
@@ -35,29 +32,112 @@ if dbus_support.supported:
import dbus.glib
import dbus.service
-def notify(event, jid, account, parameters):
+def get_show_in_roster(event, account, contact):
+ '''Return True if this event must be shown in roster, else False'''
+ num = get_advanced_notification(event, account, contact)
+ if num != None:
+ if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
+ return True
+ if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
+ return False
+ if event == 'message_received':
+ chat_control = helpers.get_chat_control(account, contact)
+ if not chat_control:
+ return True
+ elif event == 'ft_request':
+ return True
+ return False
+
+def get_show_in_systray(event, account, contact):
+ '''Return True if this event must be shown in roster, else False'''
+ num = get_advanced_notification(event, account, contact)
+ if num != None:
+ if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
+ return True
+ if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
+ return False
+ if event in ('message_received', 'ft_request', 'gc_msg_highlight',
+ 'ft_request'):
+ return True
+ return False
+
+def get_advanced_notification(event, account, contact):
+ '''Returns the number of the first advanced notification or None'''
+ num = 0
+ notif = gajim.config.get_per('notifications', str(num))
+ while notif:
+ recipient_ok = False
+ status_ok = False
+ tab_opened_ok = False
+ # test event
+ if gajim.config.get_per('notifications', str(num), 'event') == event:
+ # test recipient
+ recipient_type = gajim.config.get_per('notifications', str(num),
+ 'recipient_type')
+ recipients = gajim.config.get_per('notifications', str(num),
+ 'recipients').split()
+ if recipient_type == 'all':
+ recipient_ok = True
+ elif recipient_type == 'contact' and contact.jid in recipients:
+ recipient_ok = True
+ elif recipient_type == 'group':
+ for group in contact.groups:
+ if group in contact.groups:
+ recipient_ok = True
+ break
+ if recipient_ok:
+ # test status
+ our_status = gajim.SHOW_LIST[gajim.connections[account].connected]
+ status = gajim.config.get_per('notifications', str(num), 'status')
+ if status == 'all' or our_status in status.split():
+ status_ok = True
+ if status_ok:
+ # test window_opened
+ tab_opened = gajim.config.get_per('notifications', str(num),
+ 'tab_opened')
+ if tab_opened == 'both':
+ tab_opened_ok = True
+ else:
+ chat_control = helper.get_chat_control(account, contact)
+ 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
+
+ num += 1
+ notif = gajim.config.get_per('notifications', str(num))
+
+def notify(event, jid, account, parameters, advanced_notif_num = None):
'''Check what type of notifications we want, depending on basic configuration
of notifications and advanced one and do these notifications'''
# First, find what notifications we want
do_popup = False
do_sound = False
+ do_cmd = False
if (event == 'status_change'):
new_show = parameters[0]
status_message = parameters[1]
# Default : No popup for status change
elif (event == 'contact_connected'):
status_message = parameters
- if gajim.config.get('notify_on_signin') and \
- not gajim.block_signed_in_notifications[account]\
- and helpers.allow_showing_notification(account):
+ j = gajim.get_jid_without_resource(jid)
+ server = gajim.get_server_from_jid(j)
+ account_server = account + '/' + server
+ block_transport = False
+ if account_server in gajim.block_signed_in_notifications and \
+ 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:
do_popup = True
if gajim.config.get_per('soundevents', 'contact_connected',
- 'enabled') and not gajim.block_signed_in_notifications[account]:
+ 'enabled') and not gajim.block_signed_in_notifications[account] and \
+ not block_transport:
do_sound = True
elif (event == 'contact_disconnected'):
status_message = parameters
- if gajim.config.get('notify_on_signout') \
- and helpers.allow_showing_notification(account):
+ if helpers.allow_showing_notification(account, 'notify_on_signout'):
do_popup = True
if gajim.config.get_per('soundevents', 'contact_disconnected',
'enabled'):
@@ -67,17 +147,21 @@ def notify(event, jid, account, parameters):
first = parameters[1]
nickname = parameters[2]
message = parameters[3]
- if gajim.config.get('notify_on_new_message') and \
- helpers.allow_showing_notification(account) and first:
+ if helpers.allow_showing_notification(account, 'notify_on_new_message',
+ advanced_notif_num, first):
do_popup = True
- if first and gajim.config.get_per('soundevents', 'first_message_received',
- 'enabled'):
+ if first and helpers.allow_sound_notification('first_message_received',
+ advanced_notif_num):
do_sound = True
- elif not first and gajim.config.get_per('soundevents', 'next_message_received',
- 'enabled'):
+ elif not first and helpers.allow_sound_notification(
+ 'next_message_received', advanced_notif_num):
do_sound = True
else:
print '*Event not implemeted yet*'
+
+ if advanced_notif_num != None and gajim.config.get_per('notifications',
+ str(advanced_notif_num), 'run_command'):
+ do_cmd = True
# Do the wanted notifications
if (do_popup):
@@ -138,8 +222,7 @@ def notify(event, jid, account, parameters):
text = message
elif message_type == 'pm': # private message
event_type = _('New Private Message')
- room_name, t = gajim.get_room_name_and_server_from_room_jid(
- jid)
+ room_name, t = gajim.get_room_name_and_server_from_room_jid(jid)
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'priv_msg_recv.png')
title = _('New Private Message from room %s') % room_name
@@ -151,20 +234,40 @@ def notify(event, jid, account, parameters):
'chat_msg_recv.png')
title = _('New Message from %(nickname)s') % \
{'nickname': nickname}
- text = message
+ text = message
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
popup(event_type, jid, account, message_type,
path_to_image = path, title = title, text = text)
if (do_sound):
+ snd_file = None
+ snd_event = None # If not snd_file, play the event
if (event == 'new_message'):
- if first:
- helpers.play_sound('first_message_received')
+ if advanced_notif_num != 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')
+ elif advanced_notif_num != None and gajim.config.get_per(
+ 'notifications', str(advanced_notif_num), 'sound') == 'no':
+ pass # do not set snd_event
+ elif first:
+ snd_event = 'first_message_received'
else:
- helpers.play_sound('next_message_received')
- elif (event == 'contact_connected' or event == 'contact_disconnected'):
- helpers.play_sound(event)
-
+ snd_event = 'next_message_received'
+ elif event in ('contact_connected', 'contact_disconnected'):
+ snd_event = event
+ if snd_file:
+ helpers.play_sound_file(snd_file)
+ if snd_event:
+ helpers.play_sound(snd_event)
+
+ if do_cmd:
+ command = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'command')
+ try:
+ helpers.exec_command(command)
+ except:
+ pass
def popup(event_type, jid, account, msg_type = '', path_to_image = None,
title = None, text = None):
@@ -279,6 +382,8 @@ class DesktopNotification:
ntype = 'im.invitation'
elif event_type == _('Contact Changed Status'):
ntype = 'presence.status'
+ elif event_type == _('Connection Failed'):
+ ntype = 'connection.failed'
else:
# default failsafe values
self.path_to_image = os.path.abspath(
diff --git a/src/profile_window.py b/src/profile_window.py
new file mode 100644
index 000000000..41bbd8de3
--- /dev/null
+++ b/src/profile_window.py
@@ -0,0 +1,283 @@
+## profile_window.py
+##
+## Copyright (C) 2003-2006 Yann Le Boulanger
+## Copyright (C) 2005-2006 Nikos Kouremenos
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+import gtk
+import gobject
+import base64
+import mimetypes
+import os
+import time
+import locale
+
+import gtkgui_helpers
+import dialogs
+
+from common import helpers
+from common import gajim
+from common.i18n import Q_
+
+def get_avatar_pixbuf_encoded_mime(photo):
+ '''return the pixbuf of the image
+ photo is a dictionary containing PHOTO information'''
+ if not isinstance(photo, dict):
+ return None, None, None
+ img_decoded = None
+ avatar_encoded = None
+ avatar_mime_type = None
+ if photo.has_key('BINVAL'):
+ img_encoded = photo['BINVAL']
+ avatar_encoded = img_encoded
+ try:
+ img_decoded = base64.decodestring(img_encoded)
+ except:
+ pass
+ if img_decoded:
+ if photo.has_key('TYPE'):
+ avatar_mime_type = photo['TYPE']
+ pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
+ else:
+ pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
+ img_decoded, want_type=True)
+ else:
+ pixbuf = None
+ return pixbuf, avatar_encoded, avatar_mime_type
+
+class ProfileWindow:
+ '''Class for our information window'''
+
+ def __init__(self, account):
+ self.xml = gtkgui_helpers.get_glade('profile_window.glade')
+ self.window = self.xml.get_widget('profile_window')
+
+ self.account = account
+ self.jid = gajim.get_jid_from_account(account)
+
+ self.avatar_mime_type = None
+ self.avatar_encoded = None
+
+ self.xml.signal_autoconnect(self)
+ self.window.show_all()
+
+ def on_profile_window_destroy(self, widget):
+ del gajim.interface.instances[self.account]['profile']
+
+ def on_profile_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def on_clear_button_clicked(self, widget):
+ # empty the image
+ self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person',
+ gtk.ICON_SIZE_DIALOG)
+ self.avatar_encoded = None
+ self.avatar_mime_type = None
+
+ def on_set_avatar_button_clicked(self, widget):
+ f = None
+ def on_ok(widget, path_to_file):
+ filesize = os.path.getsize(path_to_file) # in bytes
+ #FIXME: use messages for invalid file for 0.11
+ invalid_file = False
+ msg = ''
+ if os.path.isfile(path_to_file):
+ stat = os.stat(path_to_file)
+ if stat[6] == 0:
+ invalid_file = True
+ else:
+ invalid_file = True
+ if not invalid_file and filesize > 16384: # 16 kb
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
+ # get the image at 'notification size'
+ # and use that user did not specify in ACE crazy size
+ scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
+ 'tooltip')
+ except gobject.GError, msg: # unknown format
+ # msg should be string, not object instance
+ msg = str(msg)
+ invalid_file = True
+ if invalid_file:
+ if True: # keep identation
+ dialogs.ErrorDialog(_('Could not load image'), msg)
+ return
+ if filesize > 16384:
+ if scaled_pixbuf:
+ path_to_file = os.path.join(gajim.TMP,
+ 'avatar_scaled.png')
+ scaled_pixbuf.save(path_to_file, 'png')
+ self.dialog.destroy()
+
+ fd = open(path_to_file, 'rb')
+ data = fd.read()
+ pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
+ # rescale it
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
+ image = self.xml.get_widget('PHOTO_image')
+ image.set_from_pixbuf(pixbuf)
+ self.avatar_encoded = base64.encodestring(data)
+ # returns None if unknown type
+ self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
+
+ self.dialog = dialogs.ImageChooserDialog(on_response_ok = on_ok)
+
+ def on_PHOTO_button_press_event(self, widget, event):
+ '''If right-clicked, show popup'''
+ if event.button == 3 and self.avatar_encoded: # right click
+ menu = gtk.Menu()
+ 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, None, nick + '.jpeg')
+ menu.append(menuitem)
+ # show clear
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+ menuitem.connect('activate', self.on_clear_button_clicked)
+ menu.append(menuitem)
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+ elif event.button == 1: # left click
+ self.on_set_avatar_button_clicked(widget)
+
+ def set_value(self, entry_name, value):
+ try:
+ self.xml.get_widget(entry_name).set_text(value)
+ except AttributeError:
+ pass
+
+ def set_values(self, vcard):
+ if not 'PHOTO' in vcard:
+ # set default image
+ image = self.xml.get_widget('PHOTO_image')
+ image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
+ for i in vcard.keys():
+ if i == 'PHOTO':
+ pixbuf, self.avatar_encoded, self.avatar_mime_type = \
+ get_avatar_pixbuf_encoded_mime(vcard[i])
+ image = self.xml.get_widget('PHOTO_image')
+ if not pixbuf:
+ image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
+ continue
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
+ image.set_from_pixbuf(pixbuf)
+ continue
+ if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
+ for entry in vcard[i]:
+ add_on = '_HOME'
+ if 'WORK' in entry:
+ add_on = '_WORK'
+ for j in entry.keys():
+ self.set_value(i + add_on + '_' + j + '_entry', entry[j])
+ if isinstance(vcard[i], dict):
+ for j in vcard[i].keys():
+ self.set_value(i + '_' + j + '_entry', vcard[i][j])
+ else:
+ if i == 'DESC':
+ self.xml.get_widget('DESC_textview').get_buffer().set_text(
+ vcard[i], 0)
+ else:
+ self.set_value(i + '_entry', vcard[i])
+
+ def add_to_vcard(self, vcard, entry, txt):
+ '''Add an information to the vCard dictionary'''
+ entries = entry.split('_')
+ loc = vcard
+ if len(entries) == 3: # We need to use lists
+ if not loc.has_key(entries[0]):
+ loc[entries[0]] = []
+ found = False
+ for e in loc[entries[0]]:
+ if entries[1] in e:
+ found = True
+ break
+ if found:
+ e[entries[2]] = txt
+ else:
+ loc[entries[0]].append({entries[1]: '', entries[2]: txt})
+ return vcard
+ while len(entries) > 1:
+ if not loc.has_key(entries[0]):
+ loc[entries[0]] = {}
+ loc = loc[entries[0]]
+ del entries[0]
+ loc[entries[0]] = txt
+ return vcard
+
+ def make_vcard(self):
+ '''make the vCard dictionary'''
+ entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
+ 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
+ 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
+ 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
+ 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
+ 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
+ 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
+ vcard = {}
+ for e in entries:
+ txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8')
+ if txt != '':
+ vcard = self.add_to_vcard(vcard, e, txt)
+
+ # DESC textview
+ buff = self.xml.get_widget('DESC_textview').get_buffer()
+ start_iter = buff.get_start_iter()
+ end_iter = buff.get_end_iter()
+ txt = buff.get_text(start_iter, end_iter, 0)
+ if txt != '':
+ vcard['DESC'] = txt.decode('utf-8')
+
+ # Avatar
+ if self.avatar_encoded:
+ vcard['PHOTO'] = {'BINVAL': self.avatar_encoded}
+ if self.avatar_mime_type:
+ vcard['PHOTO']['TYPE'] = self.avatar_mime_type
+ return vcard
+
+ def on_publish_button_clicked(self, widget):
+ if gajim.connections[self.account].connected < 2:
+ dialogs.ErrorDialog(_('You are not connected to the server'),
+ _('Without a connection you can not publish your contact '
+ 'information.'))
+ return
+ vcard = self.make_vcard()
+ nick = ''
+ if vcard.has_key('NICKNAME'):
+ nick = vcard['NICKNAME']
+ if nick == '':
+ nick = gajim.config.get_per('accounts', self.account, 'name')
+ gajim.nicks[self.account] = nick
+ gajim.connections[self.account].send_vcard(vcard)
+
+ def on_retrieve_button_clicked(self, widget):
+ entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
+ 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
+ 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
+ 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
+ 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR',
+ 'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE',
+ 'ADR_WORK_CTRY']
+ if gajim.connections[self.account].connected > 1:
+ # clear all entries
+ for e in entries:
+ self.xml.get_widget(e + '_entry').set_text('')
+ self.xml.get_widget('DESC_textview').get_buffer().set_text('')
+ self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person',
+ gtk.ICON_SIZE_DIALOG)
+ gajim.connections[self.account].request_vcard(self.jid)
+ else:
+ dialogs.ErrorDialog(_('You are not connected to the server'),
+ _('Without a connection, you can not get your contact information.'))
diff --git a/src/remote_control.py b/src/remote_control.py
index 52b36acb9..05156cf84 100644
--- a/src/remote_control.py
+++ b/src/remote_control.py
@@ -31,9 +31,7 @@ import os
from common import gajim
from common import helpers
from time import time
-from common import i18n
from dialogs import AddNewContactWindow, NewChatDialog
-_ = i18n._
import dbus_support
if dbus_support.supported:
@@ -56,7 +54,7 @@ ident = lambda e: e
if dbus_support.version[1] >= 43:
# in most cases it is a utf-8 string
DBUS_STRING = dbus.String
-
+
# general type (for use in dicts,
# where all values should have the same type)
DBUS_VARIANT = dbus.Variant
@@ -69,7 +67,7 @@ if dbus_support.version[1] >= 43:
DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
# empty type
DBUS_NONE = lambda : dbus.Variant(0)
-
+
else: # 33, 35, 36
DBUS_DICT_SV = lambda : {}
DBUS_DICT_SS = lambda : {}
@@ -124,7 +122,7 @@ class Remote:
def __init__(self):
self.signal_object = None
session_bus = dbus_support.session_bus.SessionBus()
-
+
if dbus_support.version[1] >= 41:
service = dbus.service.BusName(SERVICE, bus=session_bus)
self.signal_object = SignalObject(service)
@@ -141,7 +139,7 @@ class Remote:
class SignalObject(DbusPrototype):
''' Local object definition for /org/gajim/dbus/RemoteObject. This doc must
not be visible, because the clients can access only the remote object. '''
-
+
def __init__(self, service):
self.first_show = True
self.vcard_account = None
@@ -161,6 +159,7 @@ class SignalObject(DbusPrototype):
self.change_status,
self.open_chat,
self.send_message,
+ self.send_single_message,
self.contact_info,
self.send_file,
self.prefs_list,
@@ -172,6 +171,7 @@ class SignalObject(DbusPrototype):
self.get_status,
self.get_status_message,
self.start_chat,
+ self.send_xml,
])
def raise_signal(self, signal, arg):
@@ -181,7 +181,7 @@ class SignalObject(DbusPrototype):
i = message.get_iter(True)
i.append(arg)
self._connection.send(message)
-
+
def get_status(self, *args):
'''get_status(account = None)
returns status (show to be exact) which is the global one
@@ -194,7 +194,7 @@ class SignalObject(DbusPrototype):
# return show for the given account
index = gajim.connections[account].connected
return DBUS_STRING(STATUS_LIST[index])
-
+
def get_status_message(self, *args):
'''get_status(account = None)
returns status which is the global one
@@ -207,7 +207,7 @@ class SignalObject(DbusPrototype):
# return show for the given account
status = gajim.connections[account].status
return DBUS_STRING(status)
-
+
def get_account_and_contact(self, account, jid):
''' get the account (if not given) and contact instance from jid'''
@@ -233,7 +233,7 @@ class SignalObject(DbusPrototype):
break
if not contact:
contact = jid
-
+
return connected_account, contact
def send_file(self, *args):
@@ -241,34 +241,50 @@ class SignalObject(DbusPrototype):
send file, located at 'file_path' to 'jid', using account
(optional) 'account' '''
file_path, jid, account = self._get_real_arguments(args, 3)
-
+ jid = self._get_real_jid(jid, account)
connected_account, contact = self.get_account_and_contact(account, jid)
if connected_account:
+ if file_path[:7] == 'file://':
+ file_path=file_path[7:]
if os.path.isfile(file_path): # is it file?
gajim.interface.instances['file_transfers'].send_file(
connected_account, contact, file_path)
return True
return False
- def send_message(self, *args):
- ''' send_message(jid, message, keyID=None, account=None)
- send 'message' to 'jid', using account (optional) 'account'.
- if keyID is specified, encrypt the message with the pgp key '''
- jid, message, keyID, account = self._get_real_arguments(args, 4)
+ def _send_message(self, jid, message, keyID, account, type = 'chat', subject = None):
+ ''' can be called from send_chat_message (default when send_message)
+ or send_single_message'''
if not jid or not message:
return None # or raise error
if not keyID:
keyID = ''
-
+
connected_account, contact = self.get_account_and_contact(account, jid)
-
+
if connected_account:
connection = gajim.connections[connected_account]
- res = connection.send_message(jid, message, keyID)
+ res = connection.send_message(jid, message, keyID, type, subject)
return True
return False
+ def send_chat_message(self, *args):
+ ''' send_message(jid, message, keyID=None, account=None)
+ send chat 'message' to 'jid', using account (optional) 'account'.
+ if keyID is specified, encrypt the message with the pgp key '''
+ jid, message, keyID, account = self._get_real_arguments(args, 4)
+ jid = self._get_real_jid(jid, account)
+ return self._send_message(jid, message, keyID, account)
+
+ def send_single_message(self, *args):
+ ''' send_single_message(jid, subject, message, keyID=None, account=None)
+ send single 'message' to 'jid', using account (optional) 'account'.
+ if keyID is specified, encrypt the message with the pgp key '''
+ jid, subject, message, keyID, account = self._get_real_arguments(args, 5)
+ jid = self._get_real_jid(jid, account)
+ return self._send_message(jid, message, keyID, account, type, subject)
+
def open_chat(self, *args):
''' start_chat(jid, account=None) -> shows the tabbed window for new
message to 'jid', using account(optional) 'account' '''
@@ -276,9 +292,8 @@ class SignalObject(DbusPrototype):
if not jid:
# FIXME: raise exception for missing argument (dbus0.35+)
return None
- if jid.startswith('xmpp:'):
- jid = jid[5:] # len('xmpp:') = 5
-
+ jid = self._get_real_jid(jid, account)
+
if account:
accounts = [account]
else:
@@ -303,11 +318,11 @@ class SignalObject(DbusPrototype):
connected_account = acct
elif first_connected_acct is None:
first_connected_acct = acct
-
+
# if jid is not a conntact, open-chat with first connected account
if connected_account is None and first_connected_acct:
connected_account = first_connected_acct
-
+
if connected_account:
gajim.interface.roster.new_chat_from_jid(connected_account, jid)
# preserve the 'steal focus preservation'
@@ -316,7 +331,7 @@ class SignalObject(DbusPrototype):
win.window.focus()
return True
return False
-
+
def change_status(self, *args, **keywords):
''' change_status(status, message, account). account is optional -
if not specified status is changed for all accounts. '''
@@ -331,16 +346,17 @@ class SignalObject(DbusPrototype):
else:
# account not specified, so change the status of all accounts
for acc in gajim.contacts.get_accounts():
+ if not gajim.config.get_per('accounts', acc, 'sync_with_global_status'):
+ continue
gobject.idle_add(gajim.interface.roster.send_status, acc,
status, message)
return None
-
+
def show_next_unread(self, *args):
''' Show the window(s) with next waiting messages in tabbed/group chats. '''
- #FIXME: when systray is disabled this method does nothing.
- if len(gajim.interface.systray.jids) != 0:
+ if gajim.events.get_nb_events():
gajim.interface.systray.handle_first_event()
-
+
def contact_info(self, *args):
''' get vcard info for a contact. Return cached value of the vcard.
'''
@@ -350,14 +366,15 @@ class SignalObject(DbusPrototype):
if not jid:
# FIXME: raise exception for missing argument (0.3+)
return None
-
+ jid = self._get_real_jid(jid, account)
+
cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
if cached_vcard:
return get_dbus_struct(cached_vcard)
-
+
# return empty dict
return DBUS_DICT_SV()
-
+
def list_accounts(self, *args):
''' list register accounts '''
result = gajim.contacts.get_accounts()
@@ -367,7 +384,7 @@ class SignalObject(DbusPrototype):
result_array.append(DBUS_STRING(account))
return result_array
return None
-
+
def account_info(self, *args):
''' show info on account: resource, jid, nick, prio, message '''
[for_account] = self._get_real_arguments(args, 1)
@@ -386,7 +403,7 @@ class SignalObject(DbusPrototype):
result['resource'] = DBUS_STRING(unicode(gajim.config.get_per('accounts',
account.name, 'resource')))
return result
-
+
def list_contacts(self, *args):
''' list all contacts in the roster. If the first argument is specified,
then return the contacts for the specified account '''
@@ -410,7 +427,7 @@ class SignalObject(DbusPrototype):
if result == []:
return None
return result
-
+
def toggle_roster_appearance(self, *args):
''' shows/hides the roster window '''
win = gajim.interface.roster.window
@@ -437,14 +454,14 @@ class SignalObject(DbusPrototype):
prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
gajim.config.foreach(get_prefs)
return prefs_dict
-
+
def prefs_store(self, *args):
try:
gajim.interface.save_config()
except Exception, e:
return False
return True
-
+
def prefs_del(self, *args):
[key] = self._get_real_arguments(args, 1)
if not key:
@@ -457,7 +474,7 @@ class SignalObject(DbusPrototype):
else:
gajim.config.del_per(key_path[0], key_path[1], key_path[2])
return True
-
+
def prefs_put(self, *args):
[key] = self._get_real_arguments(args, 1)
if not key:
@@ -470,7 +487,7 @@ class SignalObject(DbusPrototype):
subname, value = key_path[2].split('=', 1)
gajim.config.set_per(key_path[0], key_path[1], subname, value)
return True
-
+
def add_contact(self, *args):
[jid, account] = self._get_real_arguments(args, 2)
if account:
@@ -485,11 +502,12 @@ class SignalObject(DbusPrototype):
# if account is not given, show account combobox
AddNewContactWindow(account = None, jid = jid)
return True
-
+
def remove_contact(self, *args):
[jid, account] = self._get_real_arguments(args, 2)
+ jid = self._get_real_jid(jid, account)
accounts = gajim.contacts.get_accounts()
-
+
# if there is only one account in roster, take it as default
if account:
accounts = [account]
@@ -503,7 +521,7 @@ class SignalObject(DbusPrototype):
gajim.contacts.remove_jid(account, jid)
contact_exists = True
return contact_exists
-
+
def _is_first(self):
if self.first_show:
self.first_show = False
@@ -523,7 +541,35 @@ class SignalObject(DbusPrototype):
args.extend([None] * (desired_length - len(args)))
args = args[:desired_length]
return args
-
+
+ def _get_real_jid(self, jid, account = None):
+ '''get the real jid from the given one: removes xmpp: or get jid from nick
+ if account is specified, search only in this account
+ '''
+ if account:
+ accounts = [account]
+ else:
+ accounts = gajim.connections.keys()
+ if jid.startswith('xmpp:'):
+ return jid[5:] # len('xmpp:') = 5
+ nick_in_roster = None # Is jid a nick ?
+ for account in accounts:
+ # Does jid exists in roster of one account ?
+ if gajim.contacts.get_contacts_from_jid(account, jid):
+ return jid
+ if not nick_in_roster:
+ # look in all contact if one has jid as nick
+ for jid_ in gajim.contacts.get_jid_list(account):
+ c = gajim.contacts.get_contacts_from_jid(account, jid_)
+ if c[0].name == jid:
+ nick_in_roster = jid_
+ break
+ if nick_in_roster:
+ # We have not found jid in roster, but we found is as a nick
+ return nick_in_roster
+ # We have not found it as jid nor as nick, probably a not in roster jid
+ return jid
+
def _contacts_as_dbus_structure(self, contacts):
''' get info from list of Contact objects and create dbus dict '''
if not contacts:
@@ -552,7 +598,7 @@ class SignalObject(DbusPrototype):
return contact_dict
def get_unread_msgs_number(self, *args):
- return str(gajim.interface.roster.nb_unread)
+ return str(gajim.events.get_nb_events)
def start_chat(self, *args):
[account] = self._get_real_arguments(args, 1)
@@ -562,13 +608,21 @@ class SignalObject(DbusPrototype):
NewChatDialog(account)
return True
+ def send_xml(self, *args):
+ xml, account = self._get_real_arguments(args, 2)
+ if account:
+ gajim.connections[account[0]].send_stanza(xml)
+ else:
+ for acc in gajim.contacts.get_accounts():
+ gajim.connections[acc].send_stanza(xml)
+
if dbus_support.version[1] >= 30 and dbus_support.version[1] <= 40:
method = dbus.method
signal = dbus.signal
elif dbus_support.version[1] >= 41:
method = dbus.service.method
signal = dbus.service.signal
-
+
# prevent using decorators, because they are not supported
# on python < 2.4
# FIXME: use decorators when python2.3 (and dbus 0.23) is OOOOOOLD
@@ -579,7 +633,8 @@ class SignalObject(DbusPrototype):
change_status = method(INTERFACE)(change_status)
open_chat = method(INTERFACE)(open_chat)
contact_info = method(INTERFACE)(contact_info)
- send_message = method(INTERFACE)(send_message)
+ send_message = method(INTERFACE)(send_chat_message)
+ send_single_message = method(INTERFACE)(send_single_message)
send_file = method(INTERFACE)(send_file)
prefs_list = method(INTERFACE)(prefs_list)
prefs_put = method(INTERFACE)(prefs_put)
@@ -592,3 +647,4 @@ class SignalObject(DbusPrototype):
account_info = method(INTERFACE)(account_info)
get_unread_msgs_number = method(INTERFACE)(get_unread_msgs_number)
start_chat = method(INTERFACE)(start_chat)
+ send_xml = method(INTERFACE)(send_xml)
diff --git a/src/roster_window.py b/src/roster_window.py
index 9aa53b9e4..b7f46ba33 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -15,10 +15,10 @@
##
import gtk
-import gtk.glade
import gobject
import os
import time
+import urllib
import common.sleepy
import history_window
@@ -30,20 +30,15 @@ import gtkgui_helpers
import cell_renderer_image
import tooltips
import message_control
+import notify
from common import gajim
from common import helpers
-from common import i18n
from message_window import MessageWindowMgr
from chat_control import ChatControl
from groupchat_control import GroupchatControl
from groupchat_control import PrivateChatControl
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
-
#(icon, name, type, jid, account, editable, second pixbuf)
(
C_IMG, # image to show state (online, new message etc)
@@ -69,7 +64,7 @@ class RosterWindow:
if self.regroup:
return account_iter
while account_iter:
- account_name = model[account_iter][C_NAME].decode('utf-8')
+ account_name = model[account_iter][C_ACCOUNT].decode('utf-8')
if name == account_name:
break
account_iter = model.iter_next(account_iter)
@@ -89,6 +84,12 @@ class RosterWindow:
return group_iter
def get_contact_iter(self, jid, account):
+ if jid == gajim.get_jid_from_account(account):
+ iter = self.get_self_contact_iter(account)
+ if iter:
+ return [iter]
+ else:
+ return []
model = self.tree.get_model()
acct = self.get_account_iter(account)
found = []
@@ -190,6 +191,13 @@ class RosterWindow:
model[iter][C_SECPIXBUF] = tls_pixbuf
else:
model[iter][C_SECPIXBUF] = None
+ path = model.get_path(iter)
+ if self.regroup:
+ account = _('Merged accounts')
+ if not self.tree.row_expanded(path) and model.iter_has_child(iter):
+ model[iter][C_NAME] = '[%s]' % account
+ else:
+ model[iter][C_NAME] = account
def remove_newly_added(self, jid, account):
if jid in gajim.newly_added[account]:
@@ -210,6 +218,9 @@ class RosterWindow:
# If contact already in roster, do not add it
if len(self.get_contact_iter(jid, account)):
return
+ if jid == gajim.get_jid_from_account(account):
+ self.add_self_contact(account)
+ return
if gajim.jid_is_transport(contact.jid):
contact.groups = [_('Transports')]
@@ -244,25 +255,24 @@ class RosterWindow:
if family:
for data in family:
_account = data['account']
- #XXX When we support metacontacts from different servers, make
- # sure that loop from #1953 is fixed and remove next 2 lines!
- if _account != account:
+ # Metacontacts over different accounts only in merged mode
+ if _account != account and not self.regroup:
continue
_jid = data['jid']
if self.get_contact_iter(_jid, _account):
shown_family.append(data)
- if _jid == jid:
+ if _jid == jid and _account == account:
our_data = data
shown_family.append(our_data)
big_brother_data = gajim.contacts.get_metacontacts_big_brother(
shown_family)
big_brother_jid = big_brother_data['jid']
big_brother_account = big_brother_data['account']
- if big_brother_jid != jid:
+ if big_brother_jid != jid or big_brother_account != account:
# We are adding a child contact
if contact.show in ('offline', 'error') and \
- not showOffline and not gajim.awaiting_events[account].has_key(jid):
+ not showOffline and len(gajim.events.get_events(account, jid)) == 0:
return
parent_iters = self.get_contact_iter(big_brother_jid,
big_brother_account)
@@ -280,7 +290,8 @@ class RosterWindow:
if (contact.show in ('offline', 'error') or hide) and \
not showOffline and (not _('Transports') in contact.groups or \
gajim.connections[account].connected < 2) and \
- not gajim.awaiting_events[account].has_key(jid):
+ len(gajim.events.get_events(account, jid)) == 0 and \
+ not _('Not in Roster') in contact.groups:
return
# Remove brother contacts that are already in roster to add them
@@ -331,6 +342,38 @@ class RosterWindow:
data['jid'])
self.add_contact_to_roster(data['jid'], data['account'])
+ def get_self_contact_iter(self, account):
+ model = self.tree.get_model()
+ iterAcct = self.get_account_iter(account)
+ iter = model.iter_children(iterAcct)
+ if not iter:
+ return None
+ if model[iter][C_TYPE] == 'self_contact':
+ return iter
+ return None
+
+ def add_self_contact(self, account):
+ jid = gajim.get_jid_from_account(account)
+ if self.get_self_contact_iter(account):
+ self.draw_contact(jid, account)
+ self.draw_avatar(jid, account)
+ return
+
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ if not contact:
+ return
+ showOffline = gajim.config.get('showoffline')
+ if (contact.show in ('offline', 'error')) and not showOffline and \
+ len(gajim.events.get_events(account, jid)) == 0:
+ return
+
+ model = self.tree.get_model()
+ iterAcct = self.get_account_iter(account)
+ model.append(iterAcct, (None, gajim.nicks[account], 'self_contact', jid,
+ account, False, None))
+ self.draw_contact(jid, account)
+ self.draw_avatar(jid, account)
+
def add_transport_to_roster(self, account, transport):
c = gajim.contacts.create_contact(jid = transport, name = transport,
groups = [_('Transports')], show = 'offline', status = 'offline',
@@ -339,6 +382,9 @@ class RosterWindow:
gajim.interface.roster.add_contact_to_roster(transport, account)
def really_remove_contact(self, contact, account):
+ if not gajim.interface.instances.has_key(account):
+ # Account has been deleted during the timeout that called us
+ return
if contact.jid in gajim.newly_added[account]:
return
if contact.jid.find('@') < 1 and gajim.connections[account].connected > 1:
@@ -359,7 +405,7 @@ class RosterWindow:
if (contact.show in ('offline', 'error') or hide) and \
not showOffline and (not _('Transports') in contact.groups or \
gajim.connections[account].connected < 2) and \
- not gajim.awaiting_events[account].has_key(contact.jid):
+ len(gajim.events.get_events(account, contact.jid, ['chat'])) == 0:
self.remove_contact(contact, account)
else:
self.draw_contact(contact.jid, account)
@@ -416,8 +462,9 @@ class RosterWindow:
transport: transport iconset doesn't contain all icons, so we fall back
to jabber one'''
transport = gajim.get_transport_name_from_jid(jid)
- if transport and icon_name in \
- self.transports_state_images[size][transport]:
+ if transport and self.transports_state_images.has_key(size) and \
+ self.transports_state_images[size].has_key(transport) and icon_name in \
+ self.transports_state_images[size][transport]:
return self.transports_state_images[size][transport]
return self.jabber_state_images[size]
@@ -468,7 +515,7 @@ class RosterWindow:
iter = iters[0] # choose the icon with the first iter
icon_name = helpers.get_icon_name_to_show(contact, account)
- # look if anotherresource has awaiting events
+ # look if another resource has awaiting events
for c in contact_instances:
c_icon_name = helpers.get_icon_name_to_show(c, account)
if c_icon_name == 'message':
@@ -481,15 +528,18 @@ class RosterWindow:
if icon_name in ('error', 'offline'):
# get the icon from the first child as they are sorted by show
child_jid = model[child_iter][C_JID].decode('utf-8')
+ child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
child_contact = gajim.contacts.get_contact_with_highest_priority(
- account, child_jid)
- child_icon_name = helpers.get_icon_name_to_show(child_contact, account)
+ child_account, child_jid)
+ child_icon_name = helpers.get_icon_name_to_show(child_contact,
+ child_account)
if child_icon_name not in ('error', 'not in roster'):
icon_name = child_icon_name
while child_iter:
# a child has awaiting messages ?
child_jid = model[child_iter][C_JID].decode('utf-8')
- if gajim.awaiting_events[account].has_key(child_jid):
+ child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
+ if len(gajim.events.get_events(child_account, child_jid)):
icon_name = 'message'
break
child_iter = model.iter_next(child_iter)
@@ -521,7 +571,8 @@ class RosterWindow:
# parent is not a contact
return
parent_jid = model[parent_iter][C_JID].decode('utf-8')
- self.draw_contact(parent_jid, account)
+ parent_account = model[parent_iter][C_ACCOUNT].decode('utf-8')
+ self.draw_contact(parent_jid, parent_account)
def draw_avatar(self, jid, account):
'''draw the avatar'''
@@ -588,6 +639,13 @@ class RosterWindow:
gajim.interface.instances[account]['xml_console'] = \
dialogs.XMLConsoleWindow(account)
+ def on_privacy_lists_menuitem_activate(self, widget, account):
+ if gajim.interface.instances[account].has_key('privacy_lists'):
+ gajim.interface.instances[account]['privacy_lists'].window.present()
+ else:
+ gajim.interface.instances[account]['privacy_lists'] = \
+ dialogs.PrivacyListsWindow(account)
+
def on_set_motd_menuitem_activate(self, widget, account):
server = gajim.config.get_per('accounts', account, 'hostname')
server += '/announce/motd'
@@ -606,14 +664,11 @@ class RosterWindow:
def on_history_manager_menuitem_activate(self, widget):
if os.name == 'nt':
if os.path.exists('history_manager.exe'): # user is running stable
- os.startfile('history_manager.exe')
+ helpers.exec_command('history_manager.exe')
else: # user is running svn
- try:
- os.startfile('history_manager.py')
- except: # user doesn't have pywin32, too bad for him
- pass
+ helpers.exec_command('python history_manager.py')
else: # Unix user
- os.system('python history_manager.py &')
+ helpers.exec_command('python history_manager.py &')
def get_and_connect_advanced_menuitem_menu(self, account):
'''adds FOR ACCOUNT options'''
@@ -623,6 +678,7 @@ class RosterWindow:
send_single_message_menuitem = xml.get_widget(
'send_single_message_menuitem')
xml_console_menuitem = xml.get_widget('xml_console_menuitem')
+ privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem')
administrator_menuitem = xml.get_widget('administrator_menuitem')
send_server_message_menuitem = xml.get_widget(
'send_server_message_menuitem')
@@ -636,6 +692,9 @@ class RosterWindow:
xml_console_menuitem.connect('activate',
self.on_xml_console_menuitem_activate, account)
+ privacy_lists_menuitem.connect('activate',
+ self.on_privacy_lists_menuitem_activate, account)
+
send_server_message_menuitem.connect('activate',
self.on_send_server_message_menuitem_activate, account)
@@ -716,8 +775,7 @@ class RosterWindow:
add_sub_menu = gtk.Menu()
disco_sub_menu = gtk.Menu()
new_chat_sub_menu = gtk.Menu()
- profile_avatar_sub_menu = gtk.Menu()
-
+
for account in gajim.connections:
if gajim.connections[account].connected <= 1:
# if offline or connecting
@@ -741,16 +799,12 @@ class RosterWindow:
add_item = gtk.MenuItem(_('to %s account') % account, False)
add_sub_menu.append(add_item)
add_item.connect('activate', self.on_add_new_contact, account)
- add_new_contact_menuitem.set_submenu(add_sub_menu)
- add_sub_menu.show_all()
# disco
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)
- service_disco_menuitem.set_submenu(disco_sub_menu)
- disco_sub_menu.show_all()
# new chat
new_chat_item = gtk.MenuItem(_('using account %s') % account,
@@ -758,17 +812,13 @@ class RosterWindow:
new_chat_sub_menu.append(new_chat_item)
new_chat_item.connect('activate',
self.on_new_chat_menuitem_activate, account)
- new_chat_menuitem.set_submenu(new_chat_sub_menu)
- new_chat_sub_menu.show_all()
-
- # profile, avatar
- profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
- False)
- profile_avatar_sub_menu.append(profile_avatar_item)
- profile_avatar_item.connect('activate',
- self.on_profile_avatar_menuitem_activate, account)
- profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
- profile_avatar_sub_menu.show_all()
+
+ add_new_contact_menuitem.set_submenu(add_sub_menu)
+ add_sub_menu.show_all()
+ service_disco_menuitem.set_submenu(disco_sub_menu)
+ disco_sub_menu.show_all()
+ new_chat_menuitem.set_submenu(new_chat_sub_menu)
+ new_chat_sub_menu.show_all()
elif connected_accounts == 1: # user has only one account
for account in gajim.connections:
@@ -796,12 +846,6 @@ class RosterWindow:
gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
self.have_new_chat_accel = True
- # 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)
-
break # No other account connected
if connected_accounts == 0:
@@ -810,17 +854,45 @@ class RosterWindow:
join_gc_menuitem.set_sensitive(False)
add_new_contact_menuitem.set_sensitive(False)
service_disco_menuitem.set_sensitive(False)
- profile_avatar_menuitem.set_sensitive(False)
else: # we have one or more connected accounts
new_chat_menuitem.set_sensitive(True)
join_gc_menuitem.set_sensitive(True)
add_new_contact_menuitem.set_sensitive(True)
service_disco_menuitem.set_sensitive(True)
- profile_avatar_menuitem.set_sensitive(True)
# show the 'manage gc bookmarks' item
newitem = gtk.SeparatorMenuItem() # separator
gc_sub_menu.append(newitem)
+ connected_accounts_with_vcard = []
+ for account in gajim.connections:
+ if gajim.connections[account].connected > 1 and \
+ gajim.connections[account].vcard_supported:
+ connected_accounts_with_vcard.append(account)
+ if len(connected_accounts_with_vcard) > 1:
+ # 2 or more accounts? make submenus
+ profile_avatar_sub_menu = gtk.Menu()
+ for account in connected_accounts_with_vcard:
+ # profile, avatar
+ profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
+ False)
+ profile_avatar_sub_menu.append(profile_avatar_item)
+ profile_avatar_item.connect('activate',
+ 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
+ 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)
+
+ if len(connected_accounts_with_vcard) == 0:
+ profile_avatar_menuitem.set_sensitive(False)
+ else:
+ profile_avatar_menuitem.set_sensitive(True)
+
newitem = gtk.ImageMenuItem(_('Manage Bookmarks...'))
img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
gtk.ICON_SIZE_MENU)
@@ -922,6 +994,7 @@ class RosterWindow:
'''adds contacts of group to roster treeview'''
for jid in gajim.contacts.get_jid_list(account):
self.add_contact_to_roster(jid, account)
+ self.draw_account(account)
def fire_up_unread_messages_events(self, account):
'''reads from db the unread messages, and fire them up'''
@@ -995,7 +1068,7 @@ class RosterWindow:
contact.show = show
contact.status = status
if show in ('offline', 'error') and \
- not gajim.awaiting_events[account].has_key(contact.jid):
+ len(gajim.events.get_events(account, contact.jid)) == 0:
if len(contact_instances) > 1:
# if multiple resources
gajim.contacts.remove_contact(account, contact)
@@ -1068,7 +1141,7 @@ class RosterWindow:
except:
self.tooltip.hide_tooltip()
return
- if model[iter][C_TYPE] == 'contact':
+ if model[iter][C_TYPE] in ('contact', 'self_contact'):
# we're on a contact entry in the roster
account = model[iter][C_ACCOUNT].decode('utf-8')
jid = model[iter][C_JID].decode('utf-8')
@@ -1130,26 +1203,41 @@ class RosterWindow:
'''When we want to modify the agent registration'''
gajim.connections[account].request_register_agent_info(contact.jid)
- def on_remove_agent(self, widget, contact, account):
- '''When an agent is requested to log in or off'''
- if gajim.config.get_per('accounts', account, 'hostname') == contact.jid:
- # We remove the server contact
- # remove it from treeview
- gajim.connections[account].unsubscribe(contact.jid)
- self.remove_contact(contact, account)
- gajim.contacts.remove_contact(account, contact)
- return
+ def on_remove_agent(self, widget, list_):
+ '''When an agent is requested to log in or off. list_ is a list of
+ (contact, account) tuple'''
+ for (contact, account) in list_:
+ if gajim.config.get_per('accounts', account, 'hostname') == \
+ contact.jid:
+ # We remove the server contact
+ # remove it from treeview
+ gajim.connections[account].unsubscribe(contact.jid)
+ self.remove_contact(contact, account)
+ gajim.contacts.remove_contact(account, contact)
+ return
- def remove(widget, contact, account):
+ def remove(widget, list_):
self.dialog.destroy()
- full_jid = contact.get_full_jid()
- gajim.connections[account].unsubscribe_agent(full_jid)
- # remove transport from treeview
- self.remove_contact(contact, account)
- gajim.contacts.remove_jid(account, contact.jid)
- gajim.contacts.remove_contact(account, contact)
+ for (contact, account) in list_:
+ full_jid = contact.get_full_jid()
+ gajim.connections[account].unsubscribe_agent(full_jid)
+ # remove transport from treeview
+ self.remove_contact(contact, account)
+ gajim.contacts.remove_jid(account, contact.jid)
+ gajim.contacts.remove_contact(account, contact)
- self.dialog = dialogs.ConfirmationDialog(_('Transport "%s" will be removed') % contact.jid, _('You will no longer be able to send and receive messages to contacts from this transport.'), on_response_ok = (remove, contact, account))
+ if len(list_) == 1:
+ pritext = _('Transport "%s" will be removed') % contact.jid
+ sectext = _('You will no longer be able to send and receive messages to contacts from 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
+ self.dialog = dialogs.ConfirmationDialog(pritext, sectext,
+ on_response_ok = (remove, list_))
def on_rename(self, widget, iter, path):
# this function is called either by F2 or by Rename menuitem
@@ -1158,7 +1246,7 @@ class RosterWindow:
# focus-in callback checks on this var and if is NOT None
# it redraws the selected contact resulting in stopping our rename
# procedure. So set this to None to stop that
- self._last_selected_contact = None
+ self._last_selected_contact = []
model = self.tree.get_model()
row_type = model[iter][C_TYPE]
@@ -1211,8 +1299,8 @@ class RosterWindow:
keys_str += jid + ' ' + keys[jid] + ' '
gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str)
- def on_edit_groups(self, widget, contact, account):
- dlg = dialogs.EditGroupsDialog(contact, account)
+ def on_edit_groups(self, widget, list_):
+ dlg = dialogs.EditGroupsDialog(list_)
dlg.run()
def on_history(self, widget, contact, account):
@@ -1228,7 +1316,10 @@ class RosterWindow:
if contact is None:
dialogs.SingleMessageWindow(account, action = 'send')
else:
- dialogs.SingleMessageWindow(account, contact.jid, 'send')
+ jid = contact.jid
+ if contact.jid == gajim.get_jid_from_account(account):
+ jid += '/' + contact.resource
+ dialogs.SingleMessageWindow(account, jid, 'send')
def on_send_file_menuitem_activate(self, widget, account, contact):
gajim.interface.instances['file_transfers'].show_file_send_request(
@@ -1243,6 +1334,7 @@ class RosterWindow:
jid = model[iter][C_JID].decode('utf-8')
path = model.get_path(iter)
account = model[iter][C_ACCOUNT].decode('utf-8')
+ our_jid = jid == gajim.get_jid_from_account(account)
contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
if not contact:
return
@@ -1254,6 +1346,7 @@ class RosterWindow:
start_chat_menuitem = xml.get_widget('start_chat_menuitem')
send_single_message_menuitem = xml.get_widget(
'send_single_message_menuitem')
+ invite_menuitem = xml.get_widget('invite_menuitem')
rename_menuitem = xml.get_widget('rename_menuitem')
edit_groups_menuitem = xml.get_widget('edit_groups_menuitem')
# separator has with send file, assign_openpgp_key_menuitem, etc..
@@ -1266,20 +1359,24 @@ class RosterWindow:
add_special_notification_menuitem.hide()
add_special_notification_menuitem.set_no_show_all(True)
-
- # add a special img for rename menuitem
- path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
- 'kbd_input.png')
- img = gtk.Image()
- img.set_from_file(path_to_kbd_input_img)
- rename_menuitem.set_image(img)
- # skip a separator
+ if not our_jid:
+ # add a special img for rename menuitem
+ path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
+ 'kbd_input.png')
+ img = gtk.Image()
+ img.set_from_file(path_to_kbd_input_img)
+ rename_menuitem.set_image(img)
+
+ above_subscription_separator = xml.get_widget(
+ 'above_subscription_separator')
subscription_menuitem = xml.get_widget('subscription_menuitem')
send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem =\
subscription_menuitem.get_submenu().get_children()
add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem')
- remove_from_roster_menuitem = xml.get_widget('remove_from_roster_menuitem')
+ remove_from_roster_menuitem = xml.get_widget(
+ 'remove_from_roster_menuitem')
+
# skip a separator
information_menuitem = xml.get_widget('information_menuitem')
history_menuitem = xml.get_widget('history_menuitem')
@@ -1317,9 +1414,39 @@ class RosterWindow:
send_single_message_menuitem.connect('activate',
self.on_send_single_message_menuitem_activate, account, contact)
+
+ submenu = gtk.Menu()
+ invite_menuitem.set_submenu(submenu)
+ menuitem = gtk.ImageMenuItem(_('_New room'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ menuitem.set_image(icon)
+ menuitem.connect('activate', self.on_invite_to_new_room, [(contact,
+ account)])
+ contact_transport = gajim.get_transport_name_from_jid(contact.jid)
+ t = contact_transport or 'jabber' # transform None in 'jabber'
+ if not gajim.connections[account].muc_jid.has_key(t):
+ menuitem.set_sensitive(False)
+ submenu.append(menuitem)
+ rooms = [] # a list of (room_jid, account) tuple
+ for gc_control in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC):
+ acct = gc_control.account
+ room_jid = gc_control.room_jid
+ if gajim.gc_connected[acct].has_key(room_jid) and \
+ gajim.gc_connected[acct][room_jid] and \
+ contact_transport == gajim.get_transport_name_from_jid(room_jid):
+ rooms.append((room_jid, acct))
+ if len(rooms):
+ item = gtk.SeparatorMenuItem() # separator
+ submenu.append(item)
+ for (room_jid, acct) in rooms:
+ menuitem = gtk.MenuItem(room_jid.split('@')[0])
+ menuitem.connect('activate', self.on_invite_to_room,
+ [(contact, account)], room_jid, acct)
+ submenu.append(menuitem)
rename_menuitem.connect('activate', self.on_rename, iter, path)
remove_from_roster_menuitem.connect('activate', self.on_req_usub,
- contact, account)
+ [(contact, account)])
information_menuitem.connect('activate', self.on_info, contact,
account)
history_menuitem.connect('activate', self.on_history, contact,
@@ -1331,8 +1458,8 @@ class RosterWindow:
assign_openpgp_key_menuitem.set_no_show_all(False)
add_to_roster_menuitem.hide()
add_to_roster_menuitem.set_no_show_all(True)
- edit_groups_menuitem.connect('activate', self.on_edit_groups, contact,
- account)
+ edit_groups_menuitem.connect('activate', self.on_edit_groups, [(
+ contact,account)])
if gajim.config.get('usegpg'):
assign_openpgp_key_menuitem.connect('activate',
@@ -1370,6 +1497,14 @@ class RosterWindow:
add_to_roster_menuitem.connect('activate',
self.on_add_to_roster, contact, account)
+ # Remove many items when it's self contact row
+ if our_jid:
+ for menuitem in (rename_menuitem, edit_groups_menuitem,
+ above_subscription_separator, subscription_menuitem,
+ remove_from_roster_menuitem):
+ menuitem.set_no_show_all(True)
+ menuitem.hide()
+
# Unsensitive many items when account is offline
if gajim.connections[account].connected < 2:
for widget in [start_chat_menuitem, send_single_message_menuitem,
@@ -1378,16 +1513,132 @@ class RosterWindow:
remove_from_roster_menuitem]:
widget.set_sensitive(False)
- #FIXME: create menu for sub contacts
-
event_button = gtkgui_helpers.get_possible_button_event(event)
roster_contact_context_menu.attach_to_widget(self.tree, None)
roster_contact_context_menu.connect('selection-done',
gtkgui_helpers.destroy_widget)
+ roster_contact_context_menu.show_all()
roster_contact_context_menu.popup(None, None, None, event_button,
event.time)
- roster_contact_context_menu.show_all()
+
+ def on_invite_to_new_room(self, widget, list_):
+ account_list = []
+ jid_list = []
+ for (contact, account) in list_:
+ if contact.jid not in jid_list:
+ jid_list.append(contact.jid)
+ if account not in account_list:
+ account_list.append(account)
+ # transform None in 'jabber'
+ type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
+ for account in account_list:
+ if gajim.connections[account].muc_jid[type_]:
+ # create the room on this muc server
+ if gajim.interface.instances[account].has_key('join_gc'):
+ gajim.interface.instances[account]['join_gc'].window.destroy()
+ try:
+ gajim.interface.instances[account]['join_gc'] = \
+ dialogs.JoinGroupchatWindow(account,
+ server = gajim.connections[account].muc_jid[type_],
+ automatic = {'invities': jid_list})
+ except RuntimeError:
+ continue
+ break
+
+ def on_invite_to_room(self, widget, list_, room_jid, account):
+ for (contact, acct) in list_:
+ gajim.connections[account].send_invite(room_jid, contact.jid)
+
+
+ def make_multiple_contact_menu(self, event, iters):
+ '''Make group's popup menu'''
+ model = self.tree.get_model()
+ list_ = [] # list of (jid, account) tuples
+ one_account_offline = False
+ connected_accounts = []
+ contacts_transport = -1
+ # -1 is at start, False when not from the same, None when jabber
+ for iter in iters:
+ jid = model[iter][C_JID].decode('utf-8')
+ account = model[iter][C_ACCOUNT].decode('utf-8')
+ if gajim.connections[account].connected < 2:
+ one_account_offline = True
+ elif not account in connected_accounts:
+ connected_accounts.append(account)
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ transport = gajim.get_transport_name_from_jid(contact.jid)
+ if contacts_transport == -1:
+ contacts_transport = transport
+ if contacts_transport != transport:
+ contacts_transport = False
+ list_.append((contact, account))
+
+ menu = gtk.Menu()
+
+ remove_item = gtk.ImageMenuItem(_('_Remove from Roster'))
+ 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_req_usub, list_)
+
+ invite_item = gtk.ImageMenuItem(_('In_vite to'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_MENU)
+ invite_item.set_image(icon)
+ if contacts_transport == False:
+ # they are not all from the same transport
+ invite_item.set_sensitive(False)
+ else:
+
+ sub_menu = gtk.Menu()
+ menuitem = gtk.ImageMenuItem(_('_New room'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ menuitem.set_image(icon)
+ menuitem.connect('activate', self.on_invite_to_new_room, list_)
+ muc_jid = {}
+ c_t = contacts_transport or 'jabber' # transform None in 'jabber'
+ for account in connected_accounts:
+ for t in gajim.connections[account].muc_jid:
+ muc_jid[t] = gajim.connections[account].muc_jid[t]
+ if not muc_jid.has_key(c_t):
+ menuitem.set_sensitive(False)
+ sub_menu.append(menuitem)
+ rooms = [] # a list of (room_jid, account) tuple
+ for gc_control in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC):
+ account = gc_control.account
+ room_jid = gc_control.room_jid
+ if gajim.gc_connected[account].has_key(room_jid) and \
+ gajim.gc_connected[account][room_jid] and \
+ contacts_transport == gajim.get_transport_name_from_jid(room_jid):
+ rooms.append((room_jid, account))
+ if len(rooms):
+ item = gtk.SeparatorMenuItem() # separator
+ sub_menu.append(item)
+ for (room_jid, account) in rooms:
+ menuitem = gtk.MenuItem(room_jid.split('@')[0])
+ menuitem.connect('activate', self.on_invite_to_room, list_,
+ room_jid, account)
+ sub_menu.append(menuitem)
+
+ invite_item.set_submenu(sub_menu)
+ menu.append(invite_item)
+
+ edit_groups_item = gtk.MenuItem(_('Edit _Groups'))
+ menu.append(edit_groups_item)
+ edit_groups_item.connect('activate', self.on_edit_groups, list_)
+
+ # unsensitive if one account is not connected
+ if one_account_offline:
+ remove_item.set_sensitive(False)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
def make_group_menu(self, event, iter):
'''Make group's popup menu'''
@@ -1418,8 +1669,8 @@ class RosterWindow:
menu.attach_to_widget(self.tree, None)
menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.popup(None, None, None, event_button, event.time)
menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
def make_transport_menu(self, event, iter):
'''Make transport's popup menu'''
@@ -1476,7 +1727,7 @@ class RosterWindow:
icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
item.set_image(icon)
menu.append(item)
- item.connect('activate', self.on_remove_agent, contact, account)
+ item.connect('activate', self.on_remove_agent, [(contact, account)])
if not is_connected:
item.set_sensitive(False)
@@ -1484,8 +1735,8 @@ class RosterWindow:
menu.attach_to_widget(self.tree, None)
menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.popup(None, None, None, event_button, event.time)
menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
def on_edit_account(self, widget, account):
if gajim.interface.instances[account].has_key('account_modification'):
@@ -1495,6 +1746,15 @@ class RosterWindow:
gajim.interface.instances[account]['account_modification'] = \
config.AccountModificationWindow(account)
+ def on_open_gmail_inbox(self, widget, account):
+ if gajim.config.get_per('accounts', account, 'savepass'):
+ url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\
+ (urllib.quote(gajim.config.get_per('accounts', account, 'name')),
+ urllib.quote(gajim.config.get_per('accounts', account, 'password')))
+ else:
+ url = ('http://mail.google.com/')
+ helpers.launch_browser_mailer('url', url)
+
def on_change_status_message_activate(self, widget, account):
show = gajim.SHOW_LIST[gajim.connections[account].connected]
dlg = dialogs.ChangeStatusMessageDialog(show)
@@ -1513,14 +1773,14 @@ class RosterWindow:
xml = gtkgui_helpers.get_glade('account_context_menu.glade')
account_context_menu = xml.get_widget('account_context_menu')
- childs = account_context_menu.get_children()
- status_menuitem = childs[0]
- join_group_chat_menuitem = childs[1]
- new_message_menuitem = childs[2]
- add_contact_menuitem = childs[3]
- service_discovery_menuitem = childs[4]
- edit_account_menuitem = childs[5]
+ status_menuitem = xml.get_widget('status_menuitem')
+ join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
+ open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
+ new_message_menuitem = xml.get_widget('new_message_menuitem')
+ add_contact_menuitem = xml.get_widget('add_contact_menuitem')
+ service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
+ edit_account_menuitem = xml.get_widget('edit_account_menuitem')
sub_menu = gtk.Menu()
status_menuitem.set_submenu(sub_menu)
@@ -1552,6 +1812,13 @@ class RosterWindow:
sub_menu.append(item)
item.connect('activate', self.change_status, account, 'offline')
+ if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
+ open_gmail_inbox_menuitem.set_no_show_all(True)
+ open_gmail_inbox_menuitem.hide()
+ else:
+ open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
+ account)
+
edit_account_menuitem.connect('activate', self.on_edit_account, account)
add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
service_discovery_menuitem.connect('activate',
@@ -1602,11 +1869,11 @@ class RosterWindow:
menu.attach_to_widget(self.tree, None)
menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.popup(None, self.tree, None, event_button, event.time)
menu.show_all()
+ menu.popup(None, self.tree, None, event_button, event.time)
def on_add_to_roster(self, widget, contact, account):
- dialogs.AddNewContactWindow(account, contact.jid)
+ dialogs.AddNewContactWindow(account, contact.jid, contact.name)
def authorize(self, widget, jid, account):
'''Authorize a contact (by re-sending auth menuitem)'''
@@ -1622,7 +1889,7 @@ class RosterWindow:
else:
group = []
gajim.connections[account].request_subscription(jid, txt, pseudo, group,
- auto_auth)
+ auto_auth, gajim.nicks[account])
contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
if not contact:
keyID = ''
@@ -1664,57 +1931,74 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
self.tree.get_selection().unselect_all()
elif event.keyval == gtk.keysyms.F2:
treeselection = self.tree.get_selection()
- model, iter = treeselection.get_selected()
- if not iter:
+ model, list_of_paths = treeselection.get_selected_rows()
+ if len(list_of_paths) != 1:
return
- type = model[iter][C_TYPE]
+ path = list_of_paths[0]
+ type = model[path][C_TYPE]
if type in ('contact', 'group', 'agent'):
- path = model.get_path(iter)
- self.on_rename(widget, iter, path)
+ if not model[path][C_EDITABLE]:
+ # we are NOT already renaming it
+ iter = model.get_iter(path)
+ self.on_rename(widget, iter, path)
elif event.keyval == gtk.keysyms.Delete:
treeselection = self.tree.get_selection()
- model, iter = treeselection.get_selected()
- if not iter:
+ model, list_of_paths = treeselection.get_selected_rows()
+ if not len(list_of_paths):
return
- jid = model[iter][C_JID].decode('utf-8')
- account = model[iter][C_ACCOUNT].decode('utf-8')
- type = model[iter][C_TYPE]
- if type in ('account', 'group'):
+ type = model[list_of_paths[0]][C_TYPE]
+ list_ = []
+ for path in list_of_paths:
+ if model[path][C_TYPE] != type:
+ 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)
+ list_.append((contact, account))
+ if type in ('account', 'group', 'self_contact'):
return
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
if type == 'contact':
- self.on_req_usub(widget, contact, account)
+ self.on_req_usub(widget, list_)
elif type == 'agent':
- self.on_remove_agent(widget, contact, account)
+ self.on_remove_agent(widget, list_)
- def show_appropriate_context_menu(self, event, iter):
+ def show_appropriate_context_menu(self, event, iters):
+ # iters must be all of the same type
model = self.tree.get_model()
- type = model[iter][C_TYPE]
- if type == 'group':
- self.make_group_menu(event, iter)
- elif type == 'agent':
- self.make_transport_menu(event, iter)
- elif type == 'contact':
- self.make_contact_menu(event, iter)
- elif type == 'account':
- self.make_account_menu(event, iter)
+ type = model[iters[0]][C_TYPE]
+ for iter in iters[1:]:
+ if model[iter][C_TYPE] != type:
+ return
+ if type == 'group' and len(iters) == 1:
+ self.make_group_menu(event, iters[0])
+ elif type == 'agent' and len(iters) == 1:
+ self.make_transport_menu(event, iters[0])
+ elif type in ('contact', 'self_contact') and len(iters) == 1:
+ self.make_contact_menu(event, iters[0])
+ elif type == 'contact':
+ self.make_multiple_contact_menu(event, iters)
+ elif type == 'account' and len(iters) == 1:
+ self.make_account_menu(event, iters[0])
def show_treeview_menu(self, event):
try:
- store, iter = self.tree.get_selection().get_selected()
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
except TypeError:
self.tree.get_selection().unselect_all()
return
- if not iter:
+ if not len(list_of_paths):
# no row is selected
return
- model = self.tree.get_model()
- path = model.get_path(iter)
- self.tree.get_selection().select_path(path)
-
- self.show_appropriate_context_menu(event, iter)
+ if len(list_of_paths) > 1:
+ iters = []
+ for path in list_of_paths:
+ iters.append(model.get_iter(path))
+ else:
+ path = list_of_paths[0]
+ iters = [model.get_iter(path)]
+ self.show_appropriate_context_menu(event, iters)
return True
@@ -1729,21 +2013,30 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return False
if event.button == 3: # Right click
- self.tree.get_selection().select_path(path)
- model = self.tree.get_model()
- iter = model.get_iter(path)
- self.show_appropriate_context_menu(event, iter)
- return True
+ try:
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ except TypeError:
+ list_of_paths = []
+ pass
+ if path not in list_of_paths:
+ self.tree.get_selection().unselect_all()
+ self.tree.get_selection().select_path(path)
+ return self.show_treeview_menu(event)
elif event.button == 2: # Middle click
- self.tree.get_selection().select_path(path)
- model = self.tree.get_model()
- iter = model.get_iter(path)
- type = model[iter][C_TYPE]
- if type in ('agent', 'contact'):
+ try:
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ except TypeError:
+ list_of_paths = []
+ pass
+ if list_of_paths != [path]:
+ self.tree.get_selection().unselect_all()
+ self.tree.get_selection().select_path(path)
+ type = model[path][C_TYPE]
+ if type in ('agent', 'contact', 'self_contact'):
self.on_roster_treeview_row_activated(widget, path)
elif type == 'account':
- account = model[iter][C_ACCOUNT].decode('utf-8')
+ account = model[path][C_ACCOUNT].decode('utf-8')
if account != 'all':
show = gajim.connections[account].connected
if show > 1: # We are connected
@@ -1766,51 +2059,90 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
elif event.button == 1: # Left click
model = self.tree.get_model()
- iter = model.get_iter(path)
- type = model[iter][C_TYPE]
- if type in ('group', 'contact'):
- if x < 27: # first cell in 1st column (the arrow SINGLE clicked)
+ type = model[path][C_TYPE]
+ if type == 'group' and x < 27:
+ # first cell in 1st column (the arrow SINGLE clicked)
+ if (self.tree.row_expanded(path)):
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+
+ elif type == 'contact' and x < 27:
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ jid = model[path][C_JID].decode('utf-8')
+ # first cell in 1st column (the arrow SINGLE clicked)
+ iters = self.get_contact_iter(jid, account)
+ for iter in iters:
+ path = model.get_path(iter)
if (self.tree.row_expanded(path)):
self.tree.collapse_row(path)
else:
self.tree.expand_row(path, False)
- def on_req_usub(self, widget, contact, account):
- '''Remove a contact'''
- def on_ok(widget, contact, account):
+ def on_req_usub(self, widget, list_):
+ '''Remove a contact. list_ is a list of (contact, account) tuples'''
+ def on_ok(widget, list_):
self.dialog.destroy()
remove_auth = True
- if contact.sub != 'to' and self.dialog.is_checked():
- remove_auth = False
- gajim.connections[account].unsubscribe(contact.jid, remove_auth)
- for c in gajim.contacts.get_contact(account, contact.jid):
- self.remove_contact(c, account)
- gajim.contacts.remove_jid(account, c.jid)
- if not remove_auth and contact.sub == 'both':
- contact.name = ''
- contact.groups = []
- contact.sub = 'from'
- gajim.contacts.add_contact(account, contact)
- self.add_contact_to_roster(contact.jid, account)
- elif gajim.interface.msg_win_mgr.has_window(contact.jid, account) or \
- gajim.awaiting_events[account].has_key(contact.jid):
- c = gajim.contacts.create_contact(jid = contact.jid,
- name = '', groups = [_('Not in Roster')],
- show = 'not in roster', status = '', ask = 'none',
- keyID = contact.keyID)
- gajim.contacts.add_contact(account, c)
- self.add_contact_to_roster(contact.jid, account)
- pritext = _('Contact "%s" will be removed from your roster') % \
- contact.get_shown_name()
- if contact.sub == 'to':
- self.dialog = dialogs.ConfirmationDialog(pritext,
- _('By removing this contact you also remove authorization resulting in him or her always seeing you as offline.'),
- on_response_ok = (on_ok, contact, account))
+ if len(list_) == 1:
+ contact = list_[0][0]
+ if contact.sub != 'to' and self.dialog.is_checked():
+ remove_auth = False
+ for (contact, account) in list_:
+ gajim.connections[account].unsubscribe(contact.jid, remove_auth)
+ for c in gajim.contacts.get_contact(account, contact.jid):
+ self.remove_contact(c, account)
+ gajim.contacts.remove_jid(account, c.jid)
+ need_readd = False
+ if not remove_auth and contact.sub == 'both':
+ contact.name = ''
+ contact.groups = []
+ contact.sub = 'from'
+ gajim.contacts.add_contact(account, contact)
+ self.add_contact_to_roster(contact.jid, account)
+ elif len(gajim.events.get_events(account, contact.jid)):
+ need_readd = True
+ elif gajim.interface.msg_win_mgr.has_window(contact.jid, account):
+ if _('Not in Roster') in contact.groups:
+ # Close chat window
+ msg_win = gajim.interface.msg_win_mgr.get_window(contact.jid,
+ account)
+ ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid,
+ account)
+ msg_win.remove_tab(ctrl)
+ else:
+ need_readd = True
+ if need_readd:
+ c = gajim.contacts.create_contact(jid = contact.jid,
+ name = '', groups = [_('Not in Roster')],
+ show = 'not in roster', status = '', ask = 'none',
+ keyID = contact.keyID)
+ gajim.contacts.add_contact(account, c)
+ self.add_contact_to_roster(contact.jid, account)
+ if len(list_) == 1:
+ contact = list_[0][0]
+ account = list_[0][1]
+ pritext = _('Contact "%s" will be removed from your roster') % \
+ contact.get_shown_name()
+ if contact.sub == 'to':
+ self.dialog = dialogs.ConfirmationDialog(pritext,
+ _('By removing this contact you also remove authorization resulting in him or her always seeing you as offline.'),
+ on_response_ok = (on_ok, list_))
+ else:
+ self.dialog = dialogs.ConfirmationDialogCheck(pritext,
+ _('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:
- self.dialog = dialogs.ConfirmationDialogCheck(pritext,
- _('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, contact, account))
+ # 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() + ','
+ sectext = _('By removing these contacts:%s\nyou also remove authorization resulting in them always seeing you as offline.') % jids
+ self.dialog = dialogs.ConfirmationDialog(pritext, sectext,
+ on_response_ok = (on_ok, list_))
+
def forget_gpg_passphrase(self, keyid):
if self.gpg_passphrase.has_key(keyid):
@@ -1825,7 +2157,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if gajim.interface.systray_enabled:
gajim.interface.systray.change_status('connecting')
- def send_status(self, account, status, txt, sync = False, auto = False):
+ def send_status(self, account, status, txt, auto = False):
model = self.tree.get_model()
accountIter = self.get_account_iter(account)
if status != 'offline':
@@ -1912,9 +2244,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
gajim.sleeper_state[account] = 'online'
else:
gajim.sleeper_state[account] = 'off'
- gajim.connections[account].change_status(status, txt, sync, auto)
+ gajim.connections[account].change_status(status, txt, auto)
def get_status_message(self, show):
+ if show in gajim.config.get_per('defaultstatusmsg'):
+ if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
+ return gajim.config.get_per('defaultstatusmsg', show, 'message')
if (show == 'online' and not gajim.config.get('ask_online_status')) or \
(show == 'offline' and not gajim.config.get('ask_offline_status')) or \
show == 'invisible':
@@ -2046,6 +2381,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if accountIter:
model[accountIter][0] = self.jabber_state_images['16'][status]
if status == 'offline':
+ if self.quit_on_next_offline > -1:
+ self.quit_on_next_offline -= 1
+ if self.quit_on_next_offline < 1:
+ self.quit_gtkgui_interface()
if accountIter:
model[accountIter][C_SECPIXBUF] = None
if gajim.con_types.has_key(account):
@@ -2081,14 +2420,16 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
mw.new_tab(chat_control)
- if gajim.awaiting_events[account].has_key(fjid):
+ if len(gajim.events.get_events(account, fjid)):
# We call this here to avoid race conditions with widget validation
chat_control.read_queue()
def new_chat_from_jid(self, account, jid):
jid = gajim.get_jid_without_resource(jid)
contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ no_contact = False
if not contact:
+ no_contact = True
keyID = ''
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split()
@@ -2106,6 +2447,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
mw = gajim.interface.msg_win_mgr.get_window(contact.jid, account)
mw.set_active_tab(jid, account)
mw.window.present()
+ # For JEP-0172
+ if no_contact:
+ mc = mw.get_control(jid, account)
+ mc.user_nick = gajim.nicks[account]
def new_room(self, room_jid, nick, account):
# Get target window, create a control, and associate it with the window
@@ -2118,7 +2463,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
mw.new_tab(gc_control)
def on_message(self, jid, msg, tim, account, encrypted = False,
- msg_type = '', subject = None, resource = '', msg_id = None):
+ msg_type = '', subject = None, resource = '', msg_id = None,
+ user_nick = '', advanced_notif_num = None):
'''when we receive a message'''
contact = None
# if chat window will be for specific resource
@@ -2127,21 +2473,26 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if resource:
fjid = jid + '/' + resource
contact = gajim.contacts.get_contact(account, jid, resource)
- # Default to highest prio
highest_contact = gajim.contacts.get_contact_with_highest_priority(
account, jid)
if not contact:
+ # Default to highest prio
fjid = jid
resource_for_chat = None
contact = highest_contact
if not contact:
+ # contact is not in roster
keyID = ''
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split()
if jid in attached_keys:
keyID = attached_keys[attached_keys.index(jid) + 1]
+ if user_nick:
+ nick = user_nick
+ else:
+ nick = jid.split('@')[0]
contact = gajim.contacts.create_contact(jid = jid,
- name = jid.split('@')[0], groups = [_('Not in Roster')],
+ name = nick, groups = [_('Not in Roster')],
show = 'not in roster', status = '', ask = 'none',
keyID = keyID, resource = resource)
gajim.contacts.add_contact(account, contact)
@@ -2152,28 +2503,22 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
path = self.tree.get_model().get_path(iters[0])
else:
path = None
- autopopup = gajim.config.get('autopopup')
- autopopupaway = gajim.config.get('autopopupaway')
# Look for a chat control that has the given resource
ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
if not ctrl:
# if not, if message comes from highest prio, get control or open one
# without resource
- if highest_contact and contact.resource == highest_contact.resource:
+ if highest_contact and contact.resource == highest_contact.resource \
+ and not jid == gajim.get_jid_from_account(account):
ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
fjid = jid
resource_for_chat = None
# Do we have a queue?
- qs = gajim.awaiting_events[account]
- no_queue = True
- if qs.has_key(fjid):
- no_queue = False
- popup = False
- if autopopup and (autopopupaway or gajim.connections[account].connected \
- in (1, 2)):
- popup = True
+ no_queue = len(gajim.events.get_events(account, fjid)) == 0
+
+ popup = helpers.allow_popup_window(account, advanced_notif_num)
if msg_type == 'normal' and popup: # it's single message to be autopopuped
dialogs.SingleMessageWindow(account, contact.jid,
@@ -2193,18 +2538,24 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return
# We save it in a queue
- if no_queue:
- qs[fjid] = []
- kind = 'chat'
+ type_ = 'chat'
if msg_type == 'normal':
- kind = 'normal'
- qs[fjid].append((kind, (msg, subject, msg_type, tim, encrypted,
- resource, msg_id)))
- self.nb_unread += 1
+ type_ = 'normal'
+ show_in_roster = notify.get_show_in_roster('message_received', account,
+ contact)
+ show_in_systray = notify.get_show_in_systray('message_received', account,
+ contact)
+ event = gajim.events.create_event(type_, (msg, subject, msg_type, tim,
+ encrypted, resource, msg_id), show_in_roster = show_in_roster,
+ show_in_systray = show_in_systray)
+ gajim.events.add_event(account, fjid, event)
if popup:
if not ctrl:
self.new_chat(contact, account, resource = resource_for_chat)
if path:
+ # we curently see contact in our roster OR he
+ # is not in the roster at all.
+ # show and select his line in roster
self.tree.expand_row(path[0:1], False)
self.tree.expand_row(path[0:2], False)
self.tree.scroll_to_cell(path)
@@ -2216,15 +2567,17 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
self.draw_parent_contact(jid, account)
self.show_title() # we show the * or [n]
if not path:
+ # contact is in roster but we curently don't see him online
+ # show him
self.add_contact_to_roster(jid, account)
iters = self.get_contact_iter(jid, account)
path = self.tree.get_model().get_path(iters[0])
+ # popup == False so we show awaiting event in roster
+ # show and select contact line in roster (even if he is not in roster)
self.tree.expand_row(path[0:1], False)
self.tree.expand_row(path[0:2], False)
self.tree.scroll_to_cell(path)
self.tree.set_cursor(path)
- if gajim.interface.systray_enabled:
- gajim.interface.systray.add_jid(fjid, account, kind)
def on_preferences_menuitem_activate(self, widget):
if gajim.interface.instances.has_key('preferences'):
@@ -2285,13 +2638,19 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
def on_profile_avatar_menuitem_activate(self, widget, account):
gajim.interface.edit_own_details(account)
- def close_all(self, dic):
+ def close_all_from_dict(self, dic):
'''close all the windows in the given dictionary'''
for w in dic.values():
if type(w) == type({}):
- self.close_all(w)
+ self.close_all_from_dict(w)
else:
w.window.destroy()
+
+ def close_all(self, account):
+ '''close all the windows from an account'''
+ self.close_all_from_dict(gajim.interface.instances[account])
+ for ctrl in gajim.interface.msg_win_mgr.get_controls(acct = account):
+ ctrl.parent_win.remove_tab(ctrl)
def on_roster_window_delete_event(self, widget, event):
'''When we want to close the window'''
@@ -2301,6 +2660,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
else:
accounts = gajim.connections.keys()
get_msg = False
+ self.quit_on_next_offline = 0
for acct in accounts:
if gajim.connections[acct].connected:
get_msg = True
@@ -2309,10 +2669,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
message = self.get_status_message('offline')
if message is None: # user pressed Cancel to change status message dialog
message = ''
+
for acct in accounts:
if gajim.connections[acct].connected:
- self.send_status(acct, 'offline', message, True)
- self.quit_gtkgui_interface()
+ self.quit_on_next_offline += 1
+ self.send_status(acct, 'offline', message)
+ if not self.quit_on_next_offline:
+ self.quit_gtkgui_interface()
return True # do NOT destory the window
def on_roster_window_focus_in_event(self, widget, event):
@@ -2324,25 +2687,25 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
# if a contact row is selected, update colors (eg. for status msg)
# because gtk engines may differ in bg when window is selected
# or not
- if self._last_selected_contact is not None:
- jid, account = self._last_selected_contact
- self.draw_contact(jid, account, selected = True,
+ if len(self._last_selected_contact):
+ for (jid, account) in self._last_selected_contact:
+ self.draw_contact(jid, account, selected = True,
focus = True)
def on_roster_window_focus_out_event(self, widget, event):
# if a contact row is selected, update colors (eg. for status msg)
# because gtk engines may differ in bg when window is selected
# or not
- if self._last_selected_contact is not None:
- jid, account = self._last_selected_contact
- self.draw_contact(jid, account, selected = True,
+ if len(self._last_selected_contact):
+ for (jid, account) in self._last_selected_contact:
+ self.draw_contact(jid, account, selected = True,
focus = False)
def on_roster_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
- treeselection = self.tree.get_selection()
- model, iter = treeselection.get_selected()
- if not iter and gajim.interface.systray_enabled and not gajim.config.get('quit_on_roster_x_button'):
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ if not len(list_of_paths) and gajim.interface.systray_enabled and \
+ not gajim.config.get('quit_on_roster_x_button'):
self.tooltip.hide_tooltip()
self.window.hide()
@@ -2366,7 +2729,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
gajim.interface.save_config()
for account in gajim.connections:
gajim.connections[account].quit(True)
- self.close_all(gajim.interface.instances)
+ self.close_all(account)
if gajim.interface.systray_enabled:
gajim.interface.hide_systray()
gtk.main_quit()
@@ -2385,14 +2748,14 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
# check if we have unread or recent mesages
unread = False
recent = False
- if self.nb_unread > 0:
+ if gajim.events.get_nb_events() > 0:
unread = True
for win in gajim.interface.msg_win_mgr.windows():
unrd = 0
for ctrl in win.controls():
if ctrl.type_id == message_control.TYPE_GC:
if gajim.config.get('notify_on_all_muc_messages'):
- unrd += ctrl.nb_unread
+ unrd += ctrl.get_nb_unread()
else:
if ctrl.attention_flag:
unrd += 1
@@ -2417,41 +2780,42 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
_('Messages will only be available for reading them later if you have history enabled.'))
if dialog.get_response() != gtk.RESPONSE_OK:
return
+ self.quit_on_next_offline = 0
for acct in accounts:
if gajim.connections[acct].connected:
- # send status asynchronously
- self.send_status(acct, 'offline', message, True)
- self.quit_gtkgui_interface()
+ self.quit_on_next_offline += 1
+ self.send_status(acct, 'offline', message)
+ else:
+ self.quit_on_next_offline = 0
+ if not self.quit_on_next_offline:
+ self.quit_gtkgui_interface()
def open_event(self, account, jid, event):
'''If an event was handled, return True, else return False'''
- if not event:
- return False
- typ = event[0]
- data = event[1]
+ data = event.parameters
ft = gajim.interface.instances['file_transfers']
- if typ == 'normal':
+ if event.type_ == 'normal':
dialogs.SingleMessageWindow(account, jid,
action = 'receive', from_whom = jid, subject = data[1],
message = data[0], resource = data[5])
- gajim.interface.remove_first_event(account, jid, typ)
+ gajim.interface.remove_first_event(account, jid, event.type_)
return True
- elif typ == 'file-request':
+ elif event.type_ == 'file-request':
contact = gajim.contacts.get_contact_with_highest_priority(account,
jid)
- gajim.interface.remove_first_event(account, jid, typ)
+ gajim.interface.remove_first_event(account, jid, event.type_)
ft.show_file_request(account, contact, data)
return True
- elif typ in ('file-request-error', 'file-send-error'):
- gajim.interface.remove_first_event(account, jid, typ)
+ elif event.type_ in ('file-request-error', 'file-send-error'):
+ gajim.interface.remove_first_event(account, jid, event.type_)
ft.show_send_error(data)
return True
- elif typ in ('file-error', 'file-stopped'):
- gajim.interface.remove_first_event(account, jid, typ)
+ elif event.type_ in ('file-error', 'file-stopped'):
+ gajim.interface.remove_first_event(account, jid, event.type_)
ft.show_stopped(jid, data)
return True
- elif typ == 'file-completed':
- gajim.interface.remove_first_event(account, jid, typ)
+ elif event.type_ == 'file-completed':
+ gajim.interface.remove_first_event(account, jid, event.type_)
ft.show_completed(jid, data)
return True
return False
@@ -2485,12 +2849,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
else:
self.tree.expand_row(path, False)
else:
- first_ev = gajim.get_first_event(account, jid)
+ first_ev = gajim.events.get_first_event(account, jid)
if not first_ev:
# look in other resources
for c in gajim.contacts.get_contact(account, jid):
fjid = c.get_full_jid()
- first_ev = gajim.get_first_event(account, fjid)
+ first_ev = gajim.events.get_first_event(account, fjid)
if first_ev:
resource = c.resource
break
@@ -2498,7 +2862,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
child_iter = model.iter_children(iter)
while not first_ev and child_iter:
child_jid = model[child_iter][C_JID].decode('utf-8')
- first_ev = gajim.get_first_event(account, child_jid)
+ first_ev = gajim.events.get_first_event(account, child_jid)
if first_ev:
jid = child_jid
else:
@@ -2510,6 +2874,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if self.open_event(account, fjid, first_ev):
return
c = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ if jid == gajim.get_jid_from_account(account):
+ resource = c.resource
self.on_open_chat_window(widget, c, account, resource = resource)
def on_roster_treeview_row_expanded(self, widget, iter, path):
@@ -2537,6 +2903,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if groupIter and gajim.groups[account][g]['expand']:
pathG = model.get_path(groupIter)
self.tree.expand_row(pathG, False)
+ self.draw_account(account)
elif type == 'contact':
jid = model[iter][C_JID].decode('utf-8')
account = model[iter][C_ACCOUNT].decode('utf-8')
@@ -2563,6 +2930,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
account = accounts[0] # There is only one cause we don't use merge
if not account in self.collapsed_rows:
self.collapsed_rows.append(account)
+ self.draw_account(account)
elif type == 'contact':
jid = model[iter][C_JID].decode('utf-8')
account = model[iter][C_ACCOUNT].decode('utf-8')
@@ -2746,8 +3114,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
model[iter][1] = self.jabber_state_images['16'][model[iter][2]]
iter = model.iter_next(iter)
# Update the systray
- if gajim.interface.systray_enabled:
- gajim.interface.systray.set_img()
+ gajim.interface.systray.set_img()
for win in gajim.interface.msg_win_mgr.windows():
for ctrl in win.controls():
@@ -2923,6 +3290,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
name2 = name2.decode('utf-8')
type1 = model[iter1][C_TYPE]
type2 = model[iter2][C_TYPE]
+ if type1 == 'self_contact':
+ return -1
+ if type2 == 'self_contact':
+ return 1
if type1 == 'group':
if name1 == _('Transports'):
return 1
@@ -2938,6 +3309,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return 0
account1 = account1.decode('utf-8')
account2 = account2.decode('utf-8')
+ if type1 == 'account':
+ if account1 < account2:
+ return -1
+ return 1
jid1 = model[iter1][C_JID].decode('utf-8')
jid2 = model[iter2][C_JID].decode('utf-8')
if type1 == 'contact':
@@ -2991,38 +3366,43 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return 0
def drag_data_get_data(self, treeview, context, selection, target_id, etime):
- treeselection = treeview.get_selection()
- model, iter = treeselection.get_selected()
- path = model.get_path(iter)
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ if len(list_of_paths) != 1:
+ return
+ path = list_of_paths[0]
data = ''
if len(path) >= 3:
- data = model[iter][C_JID]
+ data = model[path][C_JID]
selection.set(selection.target, 8, data)
- def on_drop_in_contact(self, widget, account, c_source, c_dest,
- was_big_brother, context, etime):
+ def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
+ c_dest, was_big_brother, context, etime):
# children must take the new tag too, so remember old tag
- old_tag = gajim.contacts.get_metacontacts_tag(account, c_source.jid)
+ old_tag = gajim.contacts.get_metacontacts_tag(account_source,
+ c_source.jid)
# remove the source row
- self.remove_contact(c_source, account)
+ self.remove_contact(c_source, account_source)
# brother inherite big brother groups
c_source.groups = []
for g in c_dest.groups:
c_source.groups.append(g)
- gajim.contacts.add_metacontact(account, c_dest.jid, account, c_source.jid)
+ gajim.connections[account_source].update_contact(c_source.jid,
+ c_source.name, c_source.groups)
+ gajim.contacts.add_metacontact(account_dest, c_dest.jid, account_source,
+ c_source.jid)
if was_big_brother:
# add brothers too
all_jid = gajim.contacts.get_metacontacts_jids(old_tag)
for _account in all_jid:
for _jid in all_jid[_account]:
- gajim.contacts.add_metacontact(account, c_dest.jid, _account,
- _jid)
+ gajim.contacts.add_metacontact(account_dest, c_dest.jid,
+ _account, _jid)
_c = gajim.contacts.get_first_contact_from_jid(_account, _jid)
self.remove_contact(_c, _account)
self.add_contact_to_roster(_jid, _account)
self.draw_contact(_jid, _account)
- self.add_contact_to_roster(c_source.jid, account)
- self.draw_contact(c_dest.jid, account)
+ self.add_contact_to_roster(c_source.jid, account_source)
+ self.draw_contact(c_dest.jid, account_dest)
context.finish(True, True, etime)
@@ -3071,10 +3451,18 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
iter_dest = model.get_iter(path_dest)
type_dest = model[iter_dest][C_TYPE].decode('utf-8')
jid_dest = model[iter_dest][C_JID].decode('utf-8')
- account = model[iter_dest][C_ACCOUNT].decode('utf-8')
+ account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
+
+ if account_dest == 'all':
+ # drop on account row in merged mode: we can't know which account it is
+ return
# if account is not connected, do nothing
- if gajim.connections[account].connected < 2:
+ if gajim.connections[account_dest].connected < 2:
+ return
+
+ # drop on self contact row
+ if type_dest == 'self_contact':
return
if info == self.TARGET_TYPE_URI_LIST:
@@ -3083,36 +3471,28 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return
if type_dest != 'contact':
return
- c_dest = gajim.contacts.get_contact_with_highest_priority(account,
+ c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
jid_dest)
uri = data.strip()
uri_splitted = uri.split() # we may have more than one file dropped
for uri in uri_splitted:
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_dest, path)
+ gajim.interface.instances['file_transfers'].send_file(
+ account_dest, c_dest, path)
return
if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
# dropped before a group : we drop it in the previous group
path_dest = (path_dest[0], path_dest[1]-1)
- iter_source = treeview.get_selection().get_selected()[1]
- path_source = model.get_path(iter_source)
+ path_source = treeview.get_selection().get_selected_rows()[1][0]
+ iter_source = model.get_iter(path_source)
type_source = model[iter_source][C_TYPE]
- if type_dest == 'account': # dropped on an account
- return
+ account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
if type_source != 'contact': # source is not a contact
return
- source_account = model[iter_source][C_ACCOUNT].decode('utf-8')
- disable_meta = False
- if account != source_account: # dropped in another account
- if self.regroup:
- # in merge mode it is ok to change group, but disable meta
- account = source_account
- disable_meta = True
- else:
- return
+ if type_dest == 'account' and account_source == account_dest:
+ return
it = iter_source
while model[it][C_TYPE] == 'contact':
it = model.iter_parent(it)
@@ -3121,36 +3501,47 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if grp_source in helpers.special_groups:
return
jid_source = data.decode('utf-8')
- c_source = gajim.contacts.get_contact_with_highest_priority(account,
- jid_source)
- # Get destination group
+ c_source = gajim.contacts.get_contact_with_highest_priority(
+ account_source, jid_source)
+
+ grp_dest = None
if type_dest == 'group':
grp_dest = model[iter_dest][C_JID].decode('utf-8')
- if grp_dest in helpers.special_groups:
- return
- if context.action == gtk.gdk.ACTION_COPY:
- self.on_drop_in_group(None, account, c_source, grp_dest, context,
- etime)
- return
- self.on_drop_in_group(None, account, c_source, grp_dest, context,
- etime, grp_source)
- return
- else:
+ elif type_dest in ('contact', 'agent'):
it = iter_dest
while model[it][C_TYPE] != 'group':
it = model.iter_parent(it)
grp_dest = model[it][C_JID].decode('utf-8')
+
+ if (type_dest == 'account' or not self.regroup) and \
+ account_source != account_dest:
+ # add contact to this account in that group
+ dialogs.AddNewContactWindow(account = account_dest, jid = jid_source,
+ user_nick = c_source.name, group = grp_dest)
+ return
+
+ # Get destination group
+ if type_dest == 'group':
+ if grp_dest in helpers.special_groups:
+ return
+ if context.action == gtk.gdk.ACTION_COPY:
+ self.on_drop_in_group(None, account_source, c_source, grp_dest,
+ context, etime)
+ return
+ self.on_drop_in_group(None, account_source, c_source, grp_dest,
+ context, etime, grp_source)
+ return
if grp_dest in helpers.special_groups:
return
if jid_source == jid_dest:
- if grp_source == grp_dest:
+ if grp_source == grp_dest and account_source == account_dest:
return
if context.action == gtk.gdk.ACTION_COPY:
- self.on_drop_in_group(None, account, c_source, grp_dest, context,
- etime)
+ self.on_drop_in_group(None, account_source, c_source, grp_dest,
+ context, etime)
return
- self.on_drop_in_group(None, account, c_source, grp_dest, context,
- etime, grp_source)
+ self.on_drop_in_group(None, account_source, c_source, grp_dest,
+ context, etime, grp_source)
return
if grp_source == grp_dest:
# Add meta contact
@@ -3159,59 +3550,61 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
# if context.action == gtk.gdk.ACTION_COPY:
# # Keep only MOVE
# return
- c_dest = gajim.contacts.get_contact_with_highest_priority(account,
+ c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
jid_dest)
is_big_brother = False
if model.iter_has_child(iter_source):
is_big_brother = True
- self.on_drop_in_contact(treeview, account, c_source, c_dest,
- is_big_brother, context, etime)
+ if not c_dest:
+ # c_dest is None if jid_dest doesn't belong to account
+ return
+ self.on_drop_in_contact(treeview, account_source, c_source,
+ account_dest, c_dest, is_big_brother, context, etime)
return
# We upgrade only the first user because user2.groups is a pointer to
# user1.groups
if context.action == gtk.gdk.ACTION_COPY:
- self.on_drop_in_group(None, account, c_source, grp_dest, context,
- etime)
+ self.on_drop_in_group(None, account_source, c_source, grp_dest,
+ context, etime)
else:
menu = gtk.Menu()
item = gtk.MenuItem(_('Drop %s in group %s') % (c_source.name,
grp_dest))
- item.connect('activate', self.on_drop_in_group, account, c_source,
+ item.connect('activate', self.on_drop_in_group, account_dest, c_source,
grp_dest, context, etime, grp_source)
menu.append(item)
- if not disable_meta:
- # source and dest account are the same, enable metacontacts
- c_dest = gajim.contacts.get_contact_with_highest_priority(account,
- jid_dest)
- item = gtk.MenuItem(_('Make %s and %s metacontacts') % (c_source.name,
- c_dest.name))
- is_big_brother = False
- if model.iter_has_child(iter_source):
- is_big_brother = True
- item.connect('activate', self.on_drop_in_contact, account, c_source,
- c_dest, is_big_brother, context, etime)
- else: #source and dest account are not the same, disable meta
- item = gtk.MenuItem(_('Can\'t create a metacontact with contacts from two different accounts'))
- item.set_sensitive(False)
+ c_dest = gajim.contacts.get_contact_with_highest_priority(
+ account_dest, jid_dest)
+ item = gtk.MenuItem(_('Make %s and %s metacontacts') % (c_source.name,
+ c_dest.name))
+ is_big_brother = False
+ if model.iter_has_child(iter_source):
+ is_big_brother = True
+ item.connect('activate', self.on_drop_in_contact, account_source,
+ c_source, account_dest, c_dest, is_big_brother, context, etime)
menu.append(item)
menu.attach_to_widget(self.tree, None)
menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.popup(None, None, None, 1, etime)
menu.show_all()
+ menu.popup(None, None, None, 1, etime)
def show_title(self):
change_title_allowed = gajim.config.get('change_roster_title')
if change_title_allowed:
start = ''
- if self.nb_unread > 1:
- start = '[' + str(self.nb_unread) + '] '
- elif self.nb_unread == 1:
+ nb_unread = gajim.events.get_nb_events(['chat', 'normal',
+ 'file-request', 'file-error', 'file-completed',
+ 'file-request-error', 'file-send-error', 'file-stopped', 'gc_msg',
+ 'printed_chat', 'printed_gc_msg'])
+ if nb_unread > 1:
+ start = '[' + str(nb_unread) + '] '
+ elif nb_unread == 1:
start = '* '
self.window.set_title(start + 'Gajim')
- gtkgui_helpers.set_unset_urgency_hint(self.window, self.nb_unread)
+ gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
def iter_is_separator(self, model, iter):
if model[iter][0] == 'SEPARATOR':
@@ -3239,22 +3632,32 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
contact[C_ACCOUNT].decode('utf-8'))
def _on_treeview_selection_changed(self, selection):
- model, selected_iter = selection.get_selected()
- if self._last_selected_contact is not None:
- # update unselected row
- jid, account = self._last_selected_contact
- self.draw_contact(jid, account)
- if selected_iter is None:
- self._last_selected_contact = None
+ model, list_of_paths = selection.get_selected_rows()
+ if len(list_of_paths) == 1 and model[list_of_paths[0]][C_EDITABLE]:
+ # We are editing this row, do not modify self._last_selected_contact
+ # Cause that cancel editing
return
- contact_row = model[selected_iter]
- if contact_row[C_TYPE] != 'contact':
- self._last_selected_contact = None
+ if len(self._last_selected_contact):
+ # update unselected rows
+ for (jid, account) in self._last_selected_contact:
+ try:
+ self.draw_contact(jid, account)
+ except:
+ # This can fail when last selected row was on an account we just
+ # removed. So we don't care if that fail
+ pass
+ self._last_selected_contact = []
+ if len(list_of_paths) == 0:
return
- jid = contact_row[C_JID].decode('utf-8')
- account = contact_row[C_ACCOUNT].decode('utf-8')
- self._last_selected_contact = (jid, account)
- self.draw_contact(jid, account, selected = True)
+ for path in list_of_paths:
+ row = model[path]
+ if row[C_TYPE] != 'contact':
+ self._last_selected_contact = []
+ return
+ jid = row[C_JID].decode('utf-8')
+ account = row[C_ACCOUNT].decode('utf-8')
+ self._last_selected_contact.append((jid, account))
+ self.draw_contact(jid, account, selected = True)
def __init__(self):
self.xml = gtkgui_helpers.get_glade('roster_window.glade')
@@ -3264,16 +3667,17 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if gajim.config.get('roster_window_skip_taskbar'):
self.window.set_property('skip-taskbar-hint', True)
self.tree = self.xml.get_widget('roster_treeview')
- self.tree.get_selection().connect('changed',
+ sel = self.tree.get_selection()
+ sel.set_mode(gtk.SELECTION_MULTIPLE)
+ sel.connect('changed',
self._on_treeview_selection_changed)
- self._last_selected_contact = None # None or holds jid, account tupple
+ self._last_selected_contact = [] # holds a list of (jid, account) tupples
self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
'closed': {}}
self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
'closed': {}}
- self.nb_unread = 0 # number of unread messages
self.last_save_dir = None
self.editing_path = None # path of row with cell in edit mode
self.add_new_contact_handler_id = False
@@ -3305,6 +3709,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
model.set_sort_func(1, self.compareIters)
model.set_sort_column_id(1, gtk.SORT_ASCENDING)
self.tree.set_model(model)
+ # when this value become 0 we quit main application
+ self.quit_on_next_offline = -1
self.make_jabber_state_images()
path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports')
diff --git a/src/systray.py b/src/systray.py
index 36417fc3c..926c077d1 100644
--- a/src/systray.py
+++ b/src/systray.py
@@ -2,7 +2,7 @@
##
## Copyright (C) 2003-2006 Yann Le Boulanger
## Copyright (C) 2003-2004 Vincent Hanquez
-## Copyright (C) 2005-2006 Nikos Kouremenos
+## Copyright (C) 2005-2006 Nikos Kouremenos
## Copyright (C) 2005 Dimitur Kirov
## Copyright (C) 2005-2006 Travis Shirk
## Copyright (C) 2005 Norman Rasmussen
@@ -18,7 +18,6 @@
##
import gtk
-import gtk.glade
import gobject
import os
@@ -29,7 +28,6 @@ import gtkgui_helpers
from common import gajim
from common import helpers
-from common import i18n
HAS_SYSTRAY_CAPABILITIES = True
@@ -42,19 +40,13 @@ except:
gajim.log.debug('No trayicon module available')
HAS_SYSTRAY_CAPABILITIES = False
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
-
class Systray:
'''Class for icon in the notification area
This class is both base class (for systraywin32.py) and normal class
for trayicon in GNU/Linux'''
-
+
def __init__(self):
- self.jids = [] # Contain things like [account, jid, type_of_msg]
self.single_message_handler_id = None
self.new_chat_handler_id = None
self.t = None
@@ -66,7 +58,9 @@ class Systray:
self.popup_menus = []
def set_img(self):
- if len(self.jids) > 0:
+ if not gajim.interface.systray_enabled:
+ return
+ if gajim.events.get_nb_systray_events():
state = 'message'
else:
state = self.status
@@ -76,26 +70,13 @@ class Systray:
elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
self.img_tray.set_from_pixbuf(image.get_pixbuf())
- def add_jid(self, jid, account, typ):
- l = [account, jid, typ]
- # We can keep several single message 'cause we open them one by one
- if not l in self.jids or typ == 'normal':
- self.jids.append(l)
- self.set_img()
-
- def remove_jid(self, jid, account, typ):
- l = [account, jid, typ]
- if l in self.jids:
- self.jids.remove(l)
- self.set_img()
-
def change_status(self, global_status):
''' set tray image to 'global_status' '''
# change image and status, only if it is different
if global_status is not None and self.status != global_status:
self.status = global_status
self.set_img()
-
+
def start_chat(self, widget, account, jid):
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
if gajim.interface.msg_win_mgr.has_window(jid, account):
@@ -106,13 +87,13 @@ class Systray:
gajim.interface.roster.new_chat(contact, account)
gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
jid, account)
-
+
def on_single_message_menuitem_activate(self, widget, account):
dialogs.SingleMessageWindow(account, action = 'send')
def on_new_chat(self, widget, account):
dialogs.NewChatDialog(account)
-
+
def make_menu(self, event = None):
'''create chat with and new message (sub) menus/menuitems
event is None when we're in Windows
@@ -125,7 +106,7 @@ class Systray:
single_message_menuitem = self.xml.get_widget('single_message_menuitem')
status_menuitem = self.xml.get_widget('status_menu')
join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
-
+
if self.single_message_handler_id:
single_message_menuitem.handler_disconnect(
self.single_message_handler_id)
@@ -137,7 +118,7 @@ class Systray:
sub_menu = gtk.Menu()
self.popup_menus.append(sub_menu)
status_menuitem.set_submenu(sub_menu)
-
+
gc_sub_menu = gtk.Menu() # gc is always a submenu
join_gc_menuitem.set_submenu(gc_sub_menu)
@@ -182,7 +163,7 @@ class Systray:
chat_with_menuitem.set_sensitive(iskey)
single_message_menuitem.set_sensitive(iskey)
join_gc_menuitem.set_sensitive(iskey)
-
+
if connected_accounts >= 2: # 2 or more connections? make submenus
account_menu_for_chat_with = gtk.Menu()
chat_with_menuitem.set_submenu(account_menu_for_chat_with)
@@ -191,7 +172,7 @@ class Systray:
account_menu_for_single_message = gtk.Menu()
single_message_menuitem.set_submenu(account_menu_for_single_message)
self.popup_menus.append(account_menu_for_single_message)
-
+
accounts_list = gajim.contacts.get_accounts()
accounts_list.sort()
for account in accounts_list:
@@ -215,7 +196,7 @@ class Systray:
gc_item.add(label)
gc_sub_menu.append(gc_item)
gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account)
-
+
elif connected_accounts == 1: # one account
# one account connected, no need to show 'as jid'
for account in gajim.connections:
@@ -230,7 +211,7 @@ class Systray:
# join gc
gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account)
break # No other connected account
-
+
if event is None:
# None means windows (we explicitly popup in systraywin32.py)
if self.added_hide_menuitem is False:
@@ -238,14 +219,18 @@ class Systray:
item = gtk.MenuItem(_('Hide this menu'))
self.systray_context_menu.prepend(item)
self.added_hide_menuitem = True
-
+
else: # GNU and Unices
- self.systray_context_menu.popup(None, None, None, event.button, event.time)
+ self.systray_context_menu.popup(None, None, None, event.button,
+ event.time)
self.systray_context_menu.show_all()
def on_show_all_events_menuitem_activate(self, widget):
- for i in range(len(self.jids)):
- self.handle_first_event()
+ events = gajim.events.get_systray_events()
+ for account in events:
+ for jid in events[account]:
+ for event in events[account][jid]:
+ gajim.interface.handle_event(account, jid, event.type_)
def on_show_roster_menuitem_activate(self, widget):
win = gajim.interface.roster.window
@@ -262,11 +247,11 @@ class Systray:
def on_left_click(self):
win = gajim.interface.roster.window
- if len(self.jids) == 0:
+ if len(gajim.events.get_systray_events()) == 0:
# no pending events, so toggle visible/hidden for roster window
if win.get_property('visible'): # visible in ANY virtual desktop?
win.hide() # we hide it from VD that was visible in
-
+
# but we could be in another VD right now. eg vd2
# and we want not only to hide it in vd1 but also show it in vd2
gtkgui_helpers.possibly_move_window_in_current_desktop(win)
@@ -276,10 +261,8 @@ class Systray:
self.handle_first_event()
def handle_first_event(self):
- account = self.jids[0][0]
- jid = self.jids[0][1]
- typ = self.jids[0][2]
- gajim.interface.handle_event(account, jid, typ)
+ account, jid, event = gajim.events.get_first_systray_event()
+ gajim.interface.handle_event(account, jid, event.type_)
def on_middle_click(self):
'''middle click raises window to have complete focus (fe. get kbd events)
@@ -292,13 +275,13 @@ class Systray:
def on_clicked(self, widget, event):
self.on_tray_leave_notify_event(widget, None)
- if event.button == 1: # Left click
+ if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: # Left click
self.on_left_click()
elif event.button == 2: # middle click
self.on_middle_click()
elif event.button == 3: # right click
self.make_menu(event)
-
+
def on_show_menuitem_activate(self, widget, show):
# we all add some fake (we cannot select those nor have them as show)
# but this helps to align with roster's status_combobox index positions
@@ -327,7 +310,7 @@ class Systray:
if self.tooltip.id == position:
size = widget.window.get_size()
self.tooltip.show_tooltip('', size[1], position[1])
-
+
def on_tray_motion_notify_event(self, widget, event):
wireq=widget.size_request()
position = widget.window.get_origin()
@@ -339,16 +322,23 @@ class Systray:
self.tooltip.id = position
self.tooltip.timeout = gobject.timeout_add(500,
self.show_tooltip, widget)
-
+
def on_tray_leave_notify_event(self, widget, event):
position = widget.window.get_origin()
if self.tooltip.timeout > 0 and \
self.tooltip.id == position:
self.tooltip.hide_tooltip()
-
+
+ def on_tray_destroyed(self, widget):
+ '''re-add trayicon when systray is destroyed'''
+ self.t = None
+ if gajim.interface.systray_enabled:
+ self.show_icon()
+
def show_icon(self):
if not self.t:
self.t = trayicon.TrayIcon('Gajim')
+ self.t.connect('destroy', self.on_tray_destroyed)
eb = gtk.EventBox()
# avoid draw seperate bg color in some gtk themes
eb.set_visible_window(False)
@@ -363,7 +353,7 @@ class Systray:
self.t.add(eb)
self.set_img()
self.t.show_all()
-
+
def hide_icon(self):
if self.t:
self.t.destroy()
diff --git a/src/systraywin32.py b/src/systraywin32.py
index bfe7cfebd..c758247e0 100644
--- a/src/systraywin32.py
+++ b/src/systraywin32.py
@@ -42,10 +42,6 @@ WM_TRAYMESSAGE = win32con.WM_USER + 20
import gtkgui_helpers
from common import gajim
from common import i18n
-_ = i18n._
-APP = i18n.APP
-gtk.glade.bindtextdomain(APP, i18n.DIR)
-gtk.glade.textdomain(APP)
class SystrayWINAPI:
def __init__(self, gtk_window):
@@ -249,36 +245,25 @@ class SystrayWin32(systray.Systray):
elif lparam == win32con.WM_LBUTTONUP: # Left click
self.on_left_click()
- def add_jid(self, jid, account, typ):
- systray.Systray.add_jid(self, jid, account, typ)
+ def set_img(self):
+ self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here
+ # see gajim.interface.roster.reload_jabber_state_images() to merge
- nb = gajim.interface.roster.nb_unread
- for acct in gajim.connections:
- # in chat / groupchat windows
- for kind in ('chats', 'gc'):
- jids = gajim.interface.instances[acct][kind]
- for jid in jids:
- if jid != 'tabbed':
- nb += jids[jid].nb_unread[jid]
-
- text = i18n.ngettext(
- 'Gajim - %d unread message',
- 'Gajim - %d unread messages',
- nb, nb, nb)
+ if len(self.jids) > 0:
+ state = 'message'
+ else:
+ state = self.status
+ hicon = self.tray_ico_imgs[state]
+ if hicon is None:
+ return
- self.systray_winapi.notify_icon.set_tooltip(text)
+ self.systray_winapi.remove_notify_icon()
+ self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
+ 'Gajim')
+ self.systray_winapi.notify_icon.menu = self.systray_context_menu
- def remove_jid(self, jid, account, typ):
- systray.Systray.remove_jid(self, jid, account, typ)
+ nb = gajim.events.get_nb_systray_events()
- nb = gajim.interface.roster.nb_unread
- for acct in gajim.connections:
- # in chat / groupchat windows
- for kind in ('chats', 'gc'):
- for jid in gajim.interface.instances[acct][kind]:
- if jid != 'tabbed':
- nb += gajim.interface.instances[acct][kind][jid].nb_unread[jid]
-
if nb > 0:
text = i18n.ngettext(
'Gajim - %d unread message',
@@ -288,23 +273,6 @@ class SystrayWin32(systray.Systray):
text = 'Gajim'
self.systray_winapi.notify_icon.set_tooltip(text)
- def set_img(self):
- self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here
- # see gajim.interface.roster.reload_jabber_state_images() to merge
-
- if len(self.jids) > 0:
- state = 'message'
- else:
- state = self.status
- hicon = self.tray_ico_imgs[state]
- if hicon is None:
- return
-
- self.systray_winapi.remove_notify_icon()
- self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
- 'Gajim')
- self.systray_winapi.notify_icon.menu = self.systray_context_menu
-
def load_icos(self):
'''load .ico files and return them to a dic of SHOW --> img_obj'''
iconset = str(gajim.config.get('iconset'))
diff --git a/src/tooltips.py b/src/tooltips.py
index f09cccbdd..53915df8a 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -27,9 +27,6 @@ from common import gajim
from common import helpers
from common import i18n
-_ = i18n._
-APP = i18n.APP
-
class BaseTooltip:
''' Base Tooltip class;
Usage:
@@ -285,34 +282,14 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
self.table.set_property('column-spacing', 1)
text, single_line = '', ''
- unread_chat = gajim.interface.roster.nb_unread
- unread_single_chat = 0
- unread_gc = 0
- unread_pm = 0
+ unread_chat = gajim.events.get_nb_events(types = ['printed_chat', 'chat'])
+ unread_single_chat = gajim.events.get_nb_events(types = ['normal'])
+ unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg',
+ 'gc_msg'])
+ unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm'])
accounts = self.get_accounts_info()
- for acct in gajim.connections:
- # Count unread chat messages
- chat_t = message_control.TYPE_CHAT
- for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
- unread_chat += ctrl.nb_unread
-
- # Count unread PM messages for which we have a control
- chat_t = message_control.TYPE_PM
- for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
- unread_pm += ctrl.nb_unread
-
- # we count unread gc/pm messages
- chat_t = message_control.TYPE_GC
- for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
- # These are PMs for which the PrivateChatControl has not yet been
- # created
- pm_msgs = ctrl.get_specific_unread()
- unread_gc += ctrl.nb_unread
- unread_gc -= pm_msgs
- unread_pm += pm_msgs
-
if unread_chat or unread_single_chat or unread_gc or unread_pm:
text = 'Gajim '
awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm
@@ -391,8 +368,9 @@ class GCTooltip(BaseTooltip):
if contact.jid.strip() != '':
jid_markup = '' + contact.jid + ''
else:
- jid_markup = '' + contact.get_shown_name() + \
- ''
+ jid_markup = '' + \
+ gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name()) \
+ + ''
properties.append((jid_markup, None))
properties.append((_('Role: '), helpers.get_uf_role(contact.role)))
properties.append((_('Affiliation: '), contact.affiliation.capitalize()))
@@ -510,11 +488,12 @@ class RosterTooltip(NotificationAreaTooltip):
properties = []
jid_markup = '' + prim_contact.jid + ''
properties.append((jid_markup, None))
+
properties.append((_('Name: '), gtkgui_helpers.escape_for_pango_markup(
- prim_contact.get_shown_name())))
+ prim_contact.get_shown_name())))
if prim_contact.sub:
properties.append(( _('Subscription: '),
- gtkgui_helpers.escape_for_pango_markup(prim_contact.sub)))
+ gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
if prim_contact.keyID:
keyID = None
if len(prim_contact.keyID) == 8:
@@ -525,17 +504,27 @@ class RosterTooltip(NotificationAreaTooltip):
properties.append((_('OpenPGP: '),
gtkgui_helpers.escape_for_pango_markup(keyID)))
num_resources = 0
+ # put contacts in dict, where key is priority
+ contacts_dict = {}
for contact in contacts:
if contact.resource:
num_resources += 1
-
- if num_resources== 1 and contact.resource:
- properties.append((_('Resource: '), gtkgui_helpers.escape_for_pango_markup(
- contact.resource) + ' (' + unicode(contact.priority) + ')'))
+ if contact.priority in contacts_dict:
+ contacts_dict[contact.priority].append(contact)
+ else:
+ contacts_dict[contact.priority] = [contact]
+
+ if num_resources == 1 and contact.resource:
+ properties.append((_('Resource: '),
+ gtkgui_helpers.escape_for_pango_markup(contact.resource) + ' (' + \
+ unicode(contact.priority) + ')'))
if num_resources > 1:
properties.append((_('Status: '), ' '))
- for contact in contacts:
- if contact.resource:
+ contact_keys = contacts_dict.keys()
+ contact_keys.sort()
+ contact_keys.reverse()
+ for priority in contact_keys:
+ for contact in contacts_dict[priority]:
status_line = self.get_status_info(contact.resource,
contact.priority, contact.show, contact.status)
diff --git a/src/vcard.py b/src/vcard.py
index 91cd1c20b..009605b38 100644
--- a/src/vcard.py
+++ b/src/vcard.py
@@ -14,7 +14,6 @@
##
import gtk
-import gtk.glade
import gobject
import base64
import mimetypes
@@ -27,12 +26,7 @@ import dialogs
from common import helpers
from common import gajim
-from common import i18n
-_ = i18n._
-Q_ = i18n.Q_
-APP = i18n.APP
-gtk.glade.bindtextdomain (APP, i18n.DIR)
-gtk.glade.textdomain (APP)
+from common.i18n import Q_
def get_avatar_pixbuf_encoded_mime(photo):
'''return the pixbuf of the image
@@ -42,16 +36,20 @@ def get_avatar_pixbuf_encoded_mime(photo):
img_decoded = None
avatar_encoded = None
avatar_mime_type = None
- if photo.has_key('BINVAL') and photo.has_key('TYPE'):
+ if photo.has_key('BINVAL'):
img_encoded = photo['BINVAL']
avatar_encoded = img_encoded
- avatar_mime_type = photo['TYPE']
try:
img_decoded = base64.decodestring(img_encoded)
except:
pass
if img_decoded:
- pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
+ if photo.has_key('TYPE'):
+ avatar_mime_type = photo['TYPE']
+ pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
+ else:
+ pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
+ img_decoded, want_type=True)
else:
pixbuf = None
return pixbuf, avatar_encoded, avatar_mime_type
@@ -59,50 +57,25 @@ def get_avatar_pixbuf_encoded_mime(photo):
class VcardWindow:
'''Class for contact's information window'''
- def __init__(self, contact, account, vcard = False, is_fake = False):
+ def __init__(self, contact, account, is_fake = False):
# the contact variable is the jid if vcard is true
self.xml = gtkgui_helpers.get_glade('vcard_information_window.glade')
self.window = self.xml.get_widget('vcard_information_window')
- self.publish_button = self.xml.get_widget('publish_button')
- self.retrieve_button = self.xml.get_widget('retrieve_button')
- self.nickname_entry = self.xml.get_widget('nickname_entry')
- if not vcard: # Maybe gc_vcard ?
- self.nickname_entry.set_property('editable', False)
-
- self.publish_button.set_no_show_all(True)
- self.retrieve_button.set_no_show_all(True)
- self.xml.get_widget('photo_vbuttonbox').set_no_show_all(True)
-
- self.contact = contact # don't use it if vcard is true
+ self.contact = contact
self.account = account
- self.vcard = vcard
self.is_fake = is_fake
+
self.avatar_mime_type = None
self.avatar_encoded = None
- if vcard: # we view/edit our own vcard
- self.jid = contact
- # remove Jabber tab & show publish/retrieve/close/set_avatar buttons
- # and make entries and textview editable
- self.change_to_vcard()
- else: # we see someone else's vcard
- self.publish_button.hide()
- self.retrieve_button.hide()
- self.jid = contact.jid
- self.fill_jabber_page()
-
- # if we are editing our own vcard publish button should publish
- # vcard data we have typed including nickname, it's why we connect only
- # here (when we see someone else's vcard)
- self.nickname_entry.connect('focus-out-event',
- self.on_nickname_entry_focus_out_event)
+ self.fill_jabber_page()
self.xml.signal_autoconnect(self)
self.window.show_all()
def on_vcard_information_window_destroy(self, widget):
- del gajim.interface.instances[self.account]['infos'][self.jid]
+ del gajim.interface.instances[self.account]['infos'][self.contact.jid]
def on_vcard_information_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
@@ -123,86 +96,20 @@ class VcardWindow:
if oldlog != log:
gajim.config.set_per('accounts', self.account, 'no_log_for',
' '.join(no_log_for))
-
- def on_nickname_entry_focus_out_event(self, widget, event):
- '''Save contact information and update
- the roster item on the Jabber server'''
- new_name = self.nickname_entry.get_text().decode('utf-8')
- # update contact.name with new nickname if that is not ''
- if new_name != self.contact.name and new_name != '':
- self.contact.name = new_name
- # update roster model
- model = gajim.interface.roster.tree.get_model()
- for iter_ in gajim.interface.roster.get_contact_iter(self.contact.jid,
- self.account):
- model[iter_][1] = new_name
- gajim.connections[self.account].update_contact(self.contact.jid,
- self.contact.name, self.contact.groups)
- # update opened chat window
- ctrl = gajim.interface.msg_win_mgr.get_control(self.contact.jid,
- self.account)
- if ctrl:
- ctrl.update_ui()
- win = gajim.interface.msg_win_mgr.get_window(self.contact.jid,
- self.account)
- win.redraw_tab(ctrl)
- win.show_title()
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_clear_button_clicked(self, widget):
- # empty the image
- self.xml.get_widget('PHOTO_image').set_from_pixbuf(None)
- self.avatar_encoded = None
-
- def on_set_avatar_button_clicked(self, widget):
- f = None
- def on_ok(widget, path_to_file):
- filesize = os.path.getsize(path_to_file) # in bytes
- #FIXME: use messages for invalid file for 0.11
- invalid_file = False
- msg = ''
- if os.path.isfile(path_to_file):
- stat = os.stat(path_to_file)
- if stat[6] == 0:
- invalid_file = True
- else:
- invalid_file = True
- if not invalid_file and filesize > 16384: # 16 kb
- try:
- pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
- # get the image at 'notification size'
- # and use that user did not specify in ACE crazy size
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
- 'tooltip')
- except gobject.GError, msg: # unknown format
- # msg should be string, not object instance
- msg = str(msg)
- invalid_file = True
- if invalid_file:
- if True: # keep identation
- dialogs.ErrorDialog(_('Could not load image'), msg)
- return
- if filesize > 16384:
- if scaled_pixbuf:
- path_to_file = os.path.join(gajim.TMP,
- 'avatar_scaled.png')
- scaled_pixbuf.save(path_to_file, 'png')
- self.dialog.destroy()
-
- fd = open(path_to_file, 'rb')
- data = fd.read()
- pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
- # rescale it
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- image = self.xml.get_widget('PHOTO_image')
- image.set_from_pixbuf(pixbuf)
- self.avatar_encoded = base64.encodestring(data)
- # returns None if unknown type
- self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
-
- self.dialog = dialogs.ImageChooserDialog(on_response_ok = on_ok)
+ def on_PHOTO_eventbox_button_press_event(self, widget, event):
+ '''If right-clicked, show popup'''
+ if event.button == 3: # right click
+ menu = gtk.Menu()
+ 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.name + '.jpeg')
+ menu.append(menuitem)
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
def set_value(self, entry_name, value):
try:
@@ -215,9 +122,11 @@ class VcardWindow:
if i == 'PHOTO':
pixbuf, self.avatar_encoded, self.avatar_mime_type = \
get_avatar_pixbuf_encoded_mime(vcard[i])
- if not pixbuf:
- continue
image = self.xml.get_widget('PHOTO_image')
+ if not pixbuf:
+ image.set_from_icon_name('stock_person',
+ gtk.ICON_SIZE_DIALOG)
+ continue
pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
image.set_from_pixbuf(pixbuf)
continue
@@ -227,21 +136,23 @@ class VcardWindow:
if 'WORK' in entry:
add_on = '_WORK'
for j in entry.keys():
- self.set_value(i + add_on + '_' + j + '_entry', entry[j])
+ self.set_value(i + add_on + '_' + j + '_label', entry[j])
if isinstance(vcard[i], dict):
for j in vcard[i].keys():
- self.set_value(i + '_' + j + '_entry', vcard[i][j])
+ self.set_value(i + '_' + j + '_label', vcard[i][j])
else:
if i == 'DESC':
self.xml.get_widget('DESC_textview').get_buffer().set_text(
vcard[i], 0)
else:
- self.set_value(i + '_entry', vcard[i])
+ self.set_value(i + '_label', vcard[i])
def set_last_status_time(self):
self.fill_status_label()
def set_os_info(self, resource, client_info, os_info):
+ if self.xml.get_widget('information_notebook').get_n_pages() < 4:
+ return
i = 0
client = ''
os = ''
@@ -265,6 +176,8 @@ class VcardWindow:
self.xml.get_widget('os_label').set_text(os)
def fill_status_label(self):
+ if self.xml.get_widget('information_notebook').get_n_pages() < 4:
+ return
contact_list = gajim.contacts.get_contact(self.account, self.contact.jid)
# stats holds show and status message
stats = ''
@@ -280,7 +193,7 @@ class VcardWindow:
stats += '\n' + _('since %s') % time.strftime('%c',
c.last_status_time).decode(locale.getpreferredencoding())
one = False
- elif not self.vcard: # Maybe gc_vcard ?
+ else: # Maybe gc_vcard ?
stats = helpers.get_uf_show(self.contact.show)
if self.contact.status:
stats += ': ' + self.contact.status
@@ -294,8 +207,10 @@ class VcardWindow:
def fill_jabber_page(self):
tooltips = gtk.Tooltips()
- self.xml.get_widget('nickname_label').set_text(
- self.contact.get_shown_name())
+ self.xml.get_widget('nickname_label').set_markup(
+ '' +
+ self.contact.get_shown_name() +
+ '')
self.xml.get_widget('jid_label').set_text(self.contact.jid)
uf_sub = helpers.get_uf_sub(self.contact.sub)
self.xml.get_widget('subscription_label').set_text(uf_sub)
@@ -317,7 +232,6 @@ class VcardWindow:
if self.contact.ask == 'subscribe':
tooltips.set_tip(eb,
_("You are waiting contact's answer about your subscription request"))
- self.nickname_entry.set_text(self.contact.name)
log = True
if self.contact.jid in gajim.config.get_per('accounts', self.account,
'no_log_for').split(' '):
@@ -339,8 +253,8 @@ class VcardWindow:
# Request os info in contact is connected
if self.contact.show not in ('offline', 'error'):
- gajim.connections[self.account].request_os_info(self.contact.jid,
- self.contact.resource)
+ gobject.idle_add(gajim.connections[self.account].request_os_info,
+ self.contact.jid, self.contact.resource)
self.os_info = {0: {'resource': self.contact.resource, 'client': '',
'os': ''}}
i = 1
@@ -353,7 +267,8 @@ class VcardWindow:
uf_resources += '\n' + c.resource + \
_(' resource with priority ') + unicode(c.priority)
if c.show not in ('offline', 'error'):
- gajim.connections[self.account].request_os_info(c.jid,
+ gobject.idle_add(
+ gajim.connections[self.account].request_os_info, c.jid,
c.resource)
gajim.connections[self.account].request_last_status_time(c.jid,
c.resource)
@@ -368,117 +283,3 @@ class VcardWindow:
self.fill_status_label()
gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
-
- def add_to_vcard(self, vcard, entry, txt):
- '''Add an information to the vCard dictionary'''
- entries = entry.split('_')
- loc = vcard
- if len(entries) == 3: # We need to use lists
- if not loc.has_key(entries[0]):
- loc[entries[0]] = []
- found = False
- for e in loc[entries[0]]:
- if entries[1] in e:
- found = True
- break
- if found:
- e[entries[2]] = txt
- else:
- loc[entries[0]].append({entries[1]: '', entries[2]: txt})
- return vcard
- while len(entries) > 1:
- if not loc.has_key(entries[0]):
- loc[entries[0]] = {}
- loc = loc[entries[0]]
- del entries[0]
- loc[entries[0]] = txt
- return vcard
-
- def make_vcard(self):
- '''make the vCard dictionary'''
- entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
- 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
- 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
- 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
- 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
- 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
- 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
- vcard = {}
- for e in entries:
- txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8')
- if txt != '':
- vcard = self.add_to_vcard(vcard, e, txt)
-
- # DESC textview
- buff = self.xml.get_widget('DESC_textview').get_buffer()
- start_iter = buff.get_start_iter()
- end_iter = buff.get_end_iter()
- txt = buff.get_text(start_iter, end_iter, 0)
- if txt != '':
- vcard['DESC'] = txt.decode('utf-8')
-
- # Avatar
- if self.avatar_encoded:
- vcard['PHOTO'] = {'BINVAL': self.avatar_encoded}
- if self.avatar_mime_type:
- vcard['PHOTO']['TYPE'] = self.avatar_mime_type
- return vcard
-
- def on_publish_button_clicked(self, widget):
- if gajim.connections[self.account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
- _('Without a connection you can not publish your contact '
- 'information.'))
- return
- vcard = self.make_vcard()
- nick = ''
- if vcard.has_key('NICKNAME'):
- nick = vcard['NICKNAME']
- if nick == '':
- nick = gajim.config.get_per('accounts', self.account, 'name')
- gajim.nicks[self.account] = nick
- gajim.connections[self.account].send_vcard(vcard)
-
- def on_retrieve_button_clicked(self, widget):
- entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
- 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
- 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
- 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
- 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR',
- 'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE',
- 'ADR_WORK_CTRY']
- if gajim.connections[self.account].connected > 1:
- # clear all entries
- for e in entries:
- self.xml.get_widget(e + '_entry').set_text('')
- self.xml.get_widget('DESC_textview').get_buffer().set_text('')
- self.xml.get_widget('PHOTO_image').set_from_pixbuf(None)
- gajim.connections[self.account].request_vcard(self.jid)
- else:
- dialogs.ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not get your contact information.'))
-
- def change_to_vcard(self):
- self.xml.get_widget('information_notebook').remove_page(0)
- self.xml.get_widget('nickname_label').set_text(_('Personal details'))
-
- self.publish_button.show()
- self.retrieve_button.show()
-
- #photo_vbuttonbox visible
- self.xml.get_widget('photo_vbuttonbox').show()
-
- #make all entries editable
- entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
- 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
- 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
- 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
- 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
- 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
- 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
- for e in entries:
- self.xml.get_widget(e + '_entry').set_property('editable', True)
-
- description_textview = self.xml.get_widget('DESC_textview')
- description_textview.set_editable(True)
- description_textview.set_cursor_visible(True)