Commit fc692495 authored by Martin's avatar Martin

Merge tag 'upstream/1.0.0' into debian/master

parents 43e5e31c 8f6b8b6c
Gajim 0.99.2 (17 February 2018)
* Speed up DB querys on MAM sync
* Fix config file encoding
* Bug fixes
Gajim 1.0.0 (17 March 2018)
Gajim 0.99.1 (08 February 2018)
* Ported to GTK3 / Python3
* Integrate HTTPUpload
* Add Navigation buttons in History Window
* Added XEP-0368 (SRV records for XMPP over TLS)
* Improvements for HiDPI Screens
* Move Chat Menu button so we are not forced to use CSD
* Depend on the python keyring package for password storage
* Bug fixes
Gajim 0.98.2 (17 December 2017)
* Fix DB migration
Gajim 0.98.1 (15 December 2017)
* Ported to GTK3 / Python3
* Flatpak support
* Lots of refactoring
* New Emoji support
......@@ -33,14 +17,22 @@ Gajim 0.98.1 (15 December 2017)
* Added mam:1 and mam:2 support (mam:0 was removed)
* Added MAM for MUCs support
* Added support for showing XEP-0084 Avatars
* Add support for geo: URIs
* Added xmpp URI handling directly in Gajim
* Removed Gajim-Remote
* Removed XEP-0012 (Last Activity)
* Removed XEP-0136 (Message Archiving)
* Added XEP-0156 (Discovering Alternative XMPP Connection Methods)
* Added XEP-0319 (Last User Interaction in Presence)
* Added XEP-0368 (SRV records for XMPP over TLS)
* Added XEP-0380 (Explicit Message Encryption)
* Added Jingle FT:5 support
* Lots of other small bugfixes
KNOWN ISSUES:
- Meta Contacts: Filtering the roster could lead to a crash in some circumstances. Use CTRL + N for starting new chats as a workaround
- Audio/Video support is currently not maintained and most likely not working
Gajim 0.16.9 (30 November 2017)
......
import subprocess
__version__ = "0.99.2"
__version__ = "1.0.0"
try:
node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
stdout=subprocess.PIPE).communicate()[0]
p = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
node = p.communicate()[0]
if node:
__version__ += '+' + node.decode('utf-8').strip()
except Exception:
pass
......@@ -177,7 +177,7 @@ class AccountsWindow(Gtk.ApplicationWindow):
def on_remove_account(self, button, account):
if app.events.get_events(account):
app.interface.raise_dialog('unread-events-on-remove')
app.interface.raise_dialog('unread-events-on-remove-account')
return
if app.config.get_per('accounts', account, 'is_zeroconf'):
......@@ -211,7 +211,8 @@ class AccountsWindow(Gtk.ApplicationWindow):
_('You have opened chat in account %s') % account,
_('All chat and groupchat windows will be closed. '
'Do you want to continue?'),
on_response_ok=(remove, account))
on_response_ok=(remove, account),
transient_for=self)
else:
remove(account)
......
......@@ -286,3 +286,13 @@ class AppActions():
win.present()
else:
app.interface.create_ipython_window()
def show_next_pending_event(self, action, param):
"""
Show the window(s) with next pending event in tabbed/group chats
"""
if app.events.get_nb_events():
account, jid, event = app.events.get_first_systray_event()
if not event:
return
app.interface.handle_event(account, jid, event.type_)
......@@ -238,8 +238,11 @@ class ChatControl(ChatControlBase):
self._nec_chatstate_received)
app.ged.register_event_handler('caps-received', ged.GUI1,
self._nec_caps_received)
app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_POSTCORE,
app.ged.register_event_handler('message-sent', ged.OUT_POSTCORE,
self._message_sent)
app.ged.register_event_handler(
'mam-decrypted-message-received',
ged.GUI1, self._nec_mam_decrypted_message_received)
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
......@@ -599,7 +602,7 @@ class ChatControl(ChatControlBase):
if 'location' in self.contact.pep:
location = self.contact.pep['location']._pep_specific_data
if ('lat' in location) and ('lon' in location):
uri = 'http://www.openstreetmap.org/?' + \
uri = 'https://www.openstreetmap.org/?' + \
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
'lon': location['lon']}
helpers.launch_browser_mailer('url', uri)
......@@ -817,6 +820,20 @@ class ChatControl(ChatControlBase):
app.plugin_manager.extension_point(
'encryption_dialog' + self.encryption, self)
def _nec_mam_decrypted_message_received(self, obj):
if obj.conn.name != self.account:
return
if obj.with_ != self.contact.jid:
return
kind = '' # incoming
if obj.kind == KindConstant.CHAT_MSG_SENT:
kind = 'outgoing'
self.print_conversation(obj.msgtxt, kind, tim=obj.timestamp,
encrypted=obj.encrypted, correct_id=obj.correct_id,
msg_stanza_id=obj.message_id, additional_data=obj.additional_data)
def _message_sent(self, obj):
if obj.conn.name != self.account:
return
......@@ -1154,7 +1171,7 @@ class ChatControl(ChatControlBase):
self._nec_chatstate_received)
app.ged.remove_event_handler('caps-received', ged.GUI1,
self._nec_caps_received)
app.ged.remove_event_handler('stanza-message-outgoing', ged.OUT_POSTCORE,
app.ged.remove_event_handler('message-sent', ged.OUT_POSTCORE,
self._message_sent)
self.unsubscribe_events()
......
......@@ -1380,13 +1380,12 @@ class ScrolledWindow(Gtk.ScrolledWindow):
def do_get_preferred_height(self):
min_height, natural_height = Gtk.ScrolledWindow.do_get_preferred_height(self)
child = self.get_child()
# Gtk Bug: If policy is set to Automatic, the ScrolledWindow
# has a min size of around 46 depending on the System. Because
# we want it smaller, we set policy NEVER if the height is < 50
# has a min size of around 46-82 depending on the System. Because
# we want it smaller, we set policy NEVER if the height is < 90
# so the ScrolledWindow will shrink to around 26 (1 line heigh).
# Once it gets over 50 its no problem to restore the policy.
if natural_height < 50:
# Once it gets over 90 its no problem to restore the policy.
if natural_height < 90:
GLib.idle_add(self.set_policy,
Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.NEVER)
......
......@@ -228,7 +228,8 @@ try:
session = conference.new_session(Farstream.MediaType.AUDIO)
del session
del conference
except GLib.GError:
except Exception as e:
glog.info(e)
HAVE_FARSTREAM = False
except (ImportError, ValueError):
......
......@@ -64,6 +64,7 @@ from gajim.common import exceptions
from gajim.common import check_X509
from gajim.common.connection_handlers import *
from gajim.common.helpers import version_condition
from gajim.common.contacts import GC_Contact
from gajim.gtkgui_helpers import get_action
......@@ -373,6 +374,10 @@ class CommonConnection:
contact = app.contacts.get_contact_with_highest_priority(
self.name, obj.jid)
# Mark Message as MUC PM
if isinstance(contact, GC_Contact):
msg_iq.setTag('x', namespace=nbxmpp.NS_MUC_USER)
# chatstates - if peer supports xep85, send chatstates
# please note that the only valid tag inside a message containing a
# <body> tag is the active event
......@@ -704,11 +709,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self._nec_gc_stanza_message_outgoing)
app.ged.register_event_handler('stanza-message-outgoing',
ged.OUT_CORE, self._nec_stanza_message_outgoing)
h = app.config.get_per('accounts', self.name, 'hostname')
if h:
app.resolver.resolve('_xmppconnect.' + helpers.idn_to_ascii(h),
self._on_resolve_txt, type_='txt')
# END __init__
def cleanup(self):
......@@ -1088,6 +1088,10 @@ class Connection(CommonConnection, ConnectionHandlers):
]
self._hostname = hostname
if h:
app.resolver.resolve('_xmppconnect.' + helpers.idn_to_ascii(h),
self._on_resolve_txt, type_='txt')
if use_srv and self._proxy is None:
self._srv_hosts = []
......@@ -2070,10 +2074,7 @@ class Connection(CommonConnection, ConnectionHandlers):
obj.timestamp = time.time()
obj.stanza_id = self.connection.send(obj.msg_iq, now=obj.now)
app.nec.push_incoming_event(MessageSentEvent(
None, conn=self, jid=obj.jid, message=obj.message, keyID=obj.keyID,
chatstate=obj.chatstate, automatic_message=obj.automatic_message,
stanza_id=obj.stanza_id, additional_data=obj.additional_data))
app.nec.push_incoming_event(MessageSentEvent(None, **vars(obj)))
if isinstance(obj.jid, list):
for j in obj.jid:
......
......@@ -163,6 +163,25 @@ class HelperEvent:
if forwarded is not None:
return forwarded.getTag('message', protocol=True)
def _is_self_message(self, message):
if self.self_message is not None:
return self.self_message
own_jid = self.conn.get_own_jid()
frm = message.getFrom()
to = message.getTo()
# If 'to' is not set we assume own jid
self.self_message = frm.bareMatch(to or own_jid)
return self.self_message
def _is_muc_pm(self, message):
if self.muc_pm is not None:
return self.muc_pm
self.muc_pm = False
muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER)
if muc_user is not None:
self.muc_pm = muc_user.getChildren() == []
return self.muc_pm
class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
name = 'http-auth-received'
base_network_events = []
......@@ -1015,6 +1034,8 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.encrypted = False
self.groupchat = False
self.nick = None
self.self_message = None
self.muc_pm = None
def generate(self):
account = self.conn.name
......@@ -1031,6 +1052,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
# use stanza-id as unique-id
self.unique_id, origin_id = self.get_unique_id()
self.message_id = self.msg_.getID()
# Check for duplicates
if app.logger.find_stanza_id(account,
......@@ -1081,6 +1103,11 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
def get_unique_id(self):
stanza_id = self.get_stanza_id(self.result, query=True)
if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_):
origin_id = self.msg_.getOriginID()
return stanza_id, origin_id
if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()):
# message we sent
origin_id = self.msg_.getOriginID()
......@@ -1101,6 +1128,8 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
:stanza: Complete stanza Node
:forwarded: Forwarded Node
:result: Result Node
:muc_pm: True, if this is a MUC PM
propagated to MamDecryptedMessageReceivedEvent
'''
self._set_base_event_vars_as_attributes(base_event)
self.additional_data = {}
......@@ -1123,6 +1152,7 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
return False
self.unique_id = self.get_stanza_id(self.result, query=True)
self.message_id = self.msg_.getID()
# Check for duplicates
if app.logger.find_stanza_id(account,
......@@ -1171,33 +1201,43 @@ class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
base_network_events = []
def generate(self):
self.correct_id = None
if not self.msgtxt:
# For example Chatstates, Receipts, Chatmarkers
log.debug('Received MAM message without text')
return
replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT)
if replace is not None:
self.correct_id = replace.getAttr('id')
self.get_oob_data(self.msg_)
if self.groupchat:
return True
self.is_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
if self.is_pm is None:
# Check if this event is triggered after a disco, so we dont
# run into an endless loop
if hasattr(self, 'disco'):
log.error('JID not known even after sucessful disco')
if not self.muc_pm:
# muc_pm = False, means only there was no muc#user namespace
# This could still be a muc pm, we check the database if we
# know this jid. If not we disco it.
self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
if self.muc_pm is None:
# Check if this event is triggered after a disco, so we dont
# run into an endless loop
if hasattr(self, 'disco'):
log.error('JID not known even after sucessful disco')
return
# we don't know this JID, we need to disco it.
server = self.with_.getDomain()
if server not in self.conn.mam_awaiting_disco_result:
self.conn.mam_awaiting_disco_result[server] = [self]
self.conn.discoverInfo(server)
else:
self.conn.mam_awaiting_disco_result[server].append(self)
return
# we don't know this JID, we need to disco it.
server = self.with_.getDomain()
if server not in self.conn.mam_awaiting_disco_result:
self.conn.mam_awaiting_disco_result[server] = [self]
self.conn.discoverInfo(server)
else:
self.conn.mam_awaiting_disco_result[server].append(self)
return
if self.is_pm:
if self.muc_pm:
self.with_ = str(self.with_)
else:
self.with_ = self.with_.getStripped()
......@@ -1217,6 +1257,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.forwarded = False
self.sent = False
self.encrypted = False
self.self_message = None
self.muc_pm = None
account = self.conn.name
if self.stanza.getFrom() == self.conn.get_own_jid(warn=True):
......@@ -1250,11 +1292,16 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.unique_id = self.get_unique_id()
# Check groupchat messages for duplicates,
# We do this because of MUC History messages
if self.stanza.getType() == 'groupchat':
type_ = self.stanza.getType()
if type_ == 'groupchat' or self.self_message or self.muc_pm:
if type_ == 'groupchat':
archive_jid = self.stanza.getFrom().getStripped()
else:
archive_jid = self.conn.get_own_jid().getStripped()
if app.logger.find_stanza_id(account,
self.stanza.getFrom().getStripped(),
archive_jid,
self.unique_id,
groupchat=True):
groupchat=type_ == 'groupchat'):
return
address_tag = self.stanza.getTag('addresses',
......@@ -1406,6 +1453,21 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
return True
def get_unique_id(self):
'''
Messages to self:
Messages to self are stored multiple times in MAM so we cant use
stanza-id to deduplicate. We use origin-id instead. Its not perfect
but there is no better way for now.
We drop "received"-Carbons of Message to self, so we dont have to
parse origin-id in that case.
MUC PMs:
MUC PMs are also stored multiple times, we also depend on origin-id
for now.
'''
if self.stanza.getType() == 'groupchat':
# TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced
return self.get_stanza_id(self.stanza)
......@@ -1424,6 +1486,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
protocol=True)
if sent_carbon is not None:
message = self.get_forwarded_message(sent_carbon)
if self._is_self_message(message) or self._is_muc_pm(message):
return message.getOriginID()
return self.get_stanza_id(message)
# Received Carbon
......@@ -1432,9 +1496,13 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
protocol=True)
if received_carbon is not None:
message = self.get_forwarded_message(received_carbon)
if self._is_muc_pm(message):
return message.getOriginID()
return self.get_stanza_id(message)
# Normal Message
if self._is_self_message(self.stanza) or self._is_muc_pm(self.stanza):
return self.stanza.getOriginID()
return self.get_stanza_id(self.stanza)
class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
......@@ -1547,6 +1615,7 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.forwarded = self.msg_obj.forwarded
self.sent = self.msg_obj.sent
self.conn = self.msg_obj.conn
self.muc_pm = self.msg_obj.muc_pm
self.popup = False
self.msg_log_id = None # id in log database
self.attention = False # XEP-0224
......
......@@ -509,6 +509,15 @@ class Contacts():
return c
return self._contacts[jid][0]
def get_contact_strict(self, jid, resource):
"""
Return the contact instance for the given resource or None
"""
if jid in self._contacts:
for c in self._contacts[jid]:
if c.resource == resource:
return c
def get_avatar(self, jid, size=None, scale=None):
if jid not in self._contacts:
return None
......@@ -554,7 +563,7 @@ class Contacts():
Get Contact object for specific resource of given jid
"""
barejid, resource = common.app.get_room_and_nick_from_fjid(fjid)
return self.get_contact(barejid, resource)
return self.get_contact_strict(barejid, resource)
def get_first_contact_from_jid(self, jid):
if jid in self._contacts:
......
......@@ -431,6 +431,8 @@ def get_uf_sub(sub):
uf_sub = _('From')
elif sub == 'both':
uf_sub = _('Both')
elif sub is None:
uf_sub = _('Unknown')
else:
uf_sub = sub
......@@ -1592,3 +1594,35 @@ def version_condition(current_version, required_version):
if V(current_version) < V(required_version):
return False
return True
def get_available_emoticon_themes():
emoticons_themes = []
emoticons_data_path = os.path.join(app.DATA_DIR, 'emoticons')
font_theme_path = os.path.join(
app.DATA_DIR, 'emoticons', 'font-emoticons', 'emoticons_theme.py')
folders = os.listdir(emoticons_data_path)
if os.path.isdir(app.MY_EMOTS_PATH):
folders += os.listdir(app.MY_EMOTS_PATH)
file = 'emoticons_theme.py'
if os.name == 'nt' and not os.path.exists(font_theme_path):
# When starting Gajim from source .py files are available
# We test this with font-emoticons and fallback to .pyc files otherwise
file = 'emoticons_theme.pyc'
for theme in folders:
theme_path = os.path.join(emoticons_data_path, theme, file)
if os.path.exists(theme_path):
emoticons_themes.append(theme)
emoticons_themes.sort()
return emoticons_themes
def get_emoticon_theme_path(theme):
emoticons_data_path = os.path.join(app.DATA_DIR, 'emoticons', theme)
if os.path.exists(emoticons_data_path):
return emoticons_data_path
emoticons_user_path = os.path.join(app.MY_EMOTS_PATH, theme)
if os.path.exists(emoticons_user_path):
return emoticons_user_path
......@@ -1104,8 +1104,12 @@ class Logger:
:param account_jid: The jid of the account
"""
jid_id = self.get_jid_id(account_jid)
try:
jid_id = self.get_jid_id(account_jid)
except ValueError:
# This happens if the JID never made it to the Database
# because the account was never connected
return
sql = '''
DELETE FROM roster_entry WHERE account_jid_id = {jid_id};
......@@ -1158,6 +1162,7 @@ class Logger:
:param account: The account
:param archive_jid: The jid of the archive the stanza-id belongs to
only used if groupchat=True
:param stanza_id: The stanza-id
......@@ -1182,7 +1187,7 @@ class Logger:
if groupchat:
# Stanza ID is only unique within a specific archive.
# So a Stanza ID could be repeated in different MUCs, so we
# filter also for the archive JID
# filter also for the archive JID which is the bare MUC jid.
sql = '''
SELECT stanza_id FROM logs
WHERE stanza_id IN ({values})
......@@ -1193,14 +1198,14 @@ class Logger:
else:
sql = '''
SELECT stanza_id FROM logs
WHERE stanza_id IN ({values}) AND account_id = ? LIMIT 1
WHERE stanza_id IN ({values}) AND account_id = ? AND kind != ? LIMIT 1
'''.format(values=', '.join('?' * len(ids)))
result = self.con.execute(
sql, tuple(ids) + (account_id,)).fetchone()
sql, tuple(ids) + (account_id, KindConstant.GC_MSG)).fetchone()
if result is not None:
log.info('Found duplicated message, stanza-id: %s, origin-id: %s, '
'archive-jid: %s, account: %s', stanza_id, origin_id, archive_id, account_id)
'archive-jid: %s, account: %s', stanza_id, origin_id, archive_jid, account_id)
return True
return False
......
......@@ -200,13 +200,19 @@ class ConnectionArchive313:
def _nec_mam_decrypted_message_received(self, obj):
if obj.conn.name != self.name:
return
# if self.archiving_namespace != nbxmpp.NS_MAM_2:
# Fallback duplicate search without stanza-id
duplicate = app.logger.search_for_duplicate(
self.name, obj.with_, obj.timestamp, obj.msgtxt)
if duplicate:
# dont propagate the event further
return True
namespace = self.archiving_namespace
if obj.groupchat:
namespace = muc_caps_cache.get_mam_namespace(obj.room_jid)
if namespace != nbxmpp.NS_MAM_2:
# Fallback duplicate search without stanza-id
duplicate = app.logger.search_for_duplicate(
self.name, obj.with_, obj.timestamp, obj.msgtxt)
if duplicate:
# dont propagate the event further
return True
app.logger.insert_into_logs(self.name,
obj.with_,
obj.timestamp,
......
......@@ -423,7 +423,7 @@ class SocksQueue:
connections with 1
"""
if idx != -1:
for key in self.senders.keys():
for key in list(self.senders.keys()):
if idx in key:
self.remove_sender_by_key(key, do_disconnect=do_disconnect)
if not remove_all:
......
......@@ -64,6 +64,7 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
# we don't need a password, but must be non-empty
self.password = 'zeroconf'
self.autoconnect = False
self.httpupload = False
CommonConnection.__init__(self, name)
self.is_zeroconf = True
......@@ -76,24 +77,6 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
Get name, host, port from config, or create zeroconf account with default
values
"""
if not app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, 'name'):
log.debug('Creating zeroconf account')
app.config.add_per('accounts', app.ZEROCONF_ACC_NAME)
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME,
'autoconnect', True)
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME, 'no_log_for',
'')
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME, 'password',
'zeroconf')
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME,
'sync_with_global_status', True)
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME,
'custom_port', 5298)
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME,
'is_zeroconf', True)
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME,
'use_ft_proxies', False)
self.host = socket.gethostname()
app.config.set_per('accounts', app.ZEROCONF_ACC_NAME, 'hostname',
self.host)
......
......@@ -143,29 +143,17 @@ class PreferencesWindow:
# emoticons
emoticons_combobox = self.xml.get_object('emoticons_combobox')
emoticons_list = os.listdir(os.path.join(app.DATA_DIR, 'emoticons'))
# user themes
if os.path.isdir(app.MY_EMOTS_PATH):
emoticons_list += os.listdir(app.MY_EMOTS_PATH)
emoticons_list.sort()
renderer_text = Gtk.CellRendererText()
emoticons_combobox.pack_start(renderer_text, True)
emoticons_combobox.add_attribute(renderer_text, 'text', 0)
model = Gtk.ListStore(str)
emoticons_combobox.set_model(model)
l = [_('Disabled')]
for dir_ in emoticons_list:
if not os.path.isdir(os.path.join(app.DATA_DIR, 'emoticons', dir_)) \
and not os.path.isdir(os.path.join(app.MY_EMOTS_PATH, dir_)) :
continue
if dir_ != '.svn':
l.append(dir_)
for i in range(len(l)):
model.append([l[i]])
if app.config.get('emoticons_theme') == l[i]:
emoticons_combobox.set_active(i)
if not app.config.get('emoticons_theme'):
emoticons_combobox.set_active(0)
emoticon_themes = helpers.get_available_emoticon_themes()
emoticons_combobox.append_text(_('Disabled'))
for theme in emoticon_themes:
emoticons_combobox.append_text(theme)
config_theme = app.config.get('emoticons_theme')
if config_theme not in emoticon_themes:
config_theme = _('Disabled')
emoticons_combobox.set_id_column(0)
emoticons_combobox.set_active_id(config_theme)
# Set default for single window type
choices = c_config.opt_one_window_types
......@@ -1786,7 +1774,8 @@ class RemoveAccountWindow:
self.account = account
xml = gtkgui_helpers.get_gtk_builder('remove_account_window.ui')
self.window = xml.get_object('remove_account_window')
self.window.set_transient_for(app.interface.roster.window)
active_window = app.app.get_active_window()
self.window.set_transient_for(active_window)
self.remove_and_unregister_radiobutton = xml.get_object(
'remove_and_unregister_radiobutton')
self.window.set_title(_('Removing %s account') % self.account)
......@@ -1806,7 +1795,8 @@ class RemoveAccountWindow:
dialogs.ErrorDialog(
_('Account is disabled'),
_('To unregister from a server, account must be '
'enabled.'))
'enabled.'),
transient_for=self.window)
return
if not app.connections[self.account].password:
def on_ok(passphrase, checked):
......@@ -1820,7 +1810,8 @@ class RemoveAccountWindow:
dialogs.PassphraseDialog(
_('Password Required'),