Add blocking list dialog
This commit is contained in:
parent
b0742377f0
commit
1a7d930fc4
|
@ -44,6 +44,7 @@ from gajim.gtk.history import HistoryWindow
|
||||||
from gajim.gtk.accounts import AccountsWindow
|
from gajim.gtk.accounts import AccountsWindow
|
||||||
from gajim.gtk.proxies import ManageProxies
|
from gajim.gtk.proxies import ManageProxies
|
||||||
from gajim.gtk.discovery import ServiceDiscoveryWindow
|
from gajim.gtk.discovery import ServiceDiscoveryWindow
|
||||||
|
from gajim.gtk.blocking import BlockingList
|
||||||
|
|
||||||
|
|
||||||
# General Actions
|
# General Actions
|
||||||
|
@ -211,6 +212,15 @@ def on_mam_preferences(action, param):
|
||||||
window.present()
|
window.present()
|
||||||
|
|
||||||
|
|
||||||
|
def on_blocking_list(action, param):
|
||||||
|
account = param.get_string()
|
||||||
|
window = app.get_app_window(MamPreferences, account)
|
||||||
|
if window is None:
|
||||||
|
BlockingList(account)
|
||||||
|
else:
|
||||||
|
window.present()
|
||||||
|
|
||||||
|
|
||||||
def on_history_sync(action, param):
|
def on_history_sync(action, param):
|
||||||
account = param.get_string()
|
account = param.get_string()
|
||||||
if 'history_sync' in interface.instances[account]:
|
if 'history_sync' in interface.instances[account]:
|
||||||
|
|
|
@ -469,6 +469,7 @@ class GajimApplication(Gtk.Application):
|
||||||
('-archive', a.on_mam_preferences, 'feature', 's'),
|
('-archive', a.on_mam_preferences, 'feature', 's'),
|
||||||
('-sync-history', a.on_history_sync, 'online', 's'),
|
('-sync-history', a.on_history_sync, 'online', 's'),
|
||||||
('-privacylists', a.on_privacy_lists, 'feature', 's'),
|
('-privacylists', a.on_privacy_lists, 'feature', 's'),
|
||||||
|
('-blocking', a.on_blocking_list, 'feature', 's'),
|
||||||
('-send-server-message', a.on_send_server_message, 'online', 's'),
|
('-send-server-message', a.on_send_server_message, 'online', 's'),
|
||||||
('-set-motd', a.on_set_motd, 'online', 's'),
|
('-set-motd', a.on_set_motd, 'online', 's'),
|
||||||
('-update-motd', a.on_update_motd, 'online', 's'),
|
('-update-motd', a.on_update_motd, 'online', 's'),
|
||||||
|
@ -517,3 +518,6 @@ class GajimApplication(Gtk.Application):
|
||||||
elif event.feature == nbxmpp.NS_PRIVACY:
|
elif event.feature == nbxmpp.NS_PRIVACY:
|
||||||
action = '%s-privacylists' % event.account
|
action = '%s-privacylists' % event.account
|
||||||
self.lookup_action(action).set_enabled(True)
|
self.lookup_action(action).set_enabled(True)
|
||||||
|
elif event.feature == nbxmpp.NS_BLOCKING:
|
||||||
|
action = '%s-blocking' % event.account
|
||||||
|
self.lookup_action(action).set_enabled(True)
|
||||||
|
|
|
@ -15,15 +15,26 @@
|
||||||
# XEP-0191: Blocking Command
|
# XEP-0191: Blocking Command
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
|
from gajim.common.nec import NetworkEvent
|
||||||
from gajim.common.nec import NetworkIncomingEvent
|
from gajim.common.nec import NetworkIncomingEvent
|
||||||
|
|
||||||
log = logging.getLogger('gajim.c.m.blocking')
|
log = logging.getLogger('gajim.c.m.blocking')
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_online(func):
|
||||||
|
@wraps(func)
|
||||||
|
def func_wrapper(self, *args, **kwargs):
|
||||||
|
if not app.account_is_connected(self._account):
|
||||||
|
return
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
return func_wrapper
|
||||||
|
|
||||||
|
|
||||||
class Blocking:
|
class Blocking:
|
||||||
def __init__(self, con):
|
def __init__(self, con):
|
||||||
self._con = con
|
self._con = con
|
||||||
|
@ -37,37 +48,48 @@ class Blocking:
|
||||||
|
|
||||||
self.supported = False
|
self.supported = False
|
||||||
|
|
||||||
|
self._nbmxpp_methods = [
|
||||||
|
'block',
|
||||||
|
'unblock',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if key not in self._nbmxpp_methods:
|
||||||
|
raise AttributeError
|
||||||
|
if not app.account_is_connected(self._account):
|
||||||
|
log.warning('Account %s not connected, cant use %s',
|
||||||
|
self._account, key)
|
||||||
|
return
|
||||||
|
module = self._con.connection.get_module('Blocking')
|
||||||
|
return getattr(module, key)
|
||||||
|
|
||||||
def pass_disco(self, from_, _identities, features, _data, _node):
|
def pass_disco(self, from_, _identities, features, _data, _node):
|
||||||
if nbxmpp.NS_BLOCKING not in features:
|
if nbxmpp.NS_BLOCKING not in features:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.supported = True
|
self.supported = True
|
||||||
|
app.nec.push_incoming_event(
|
||||||
|
NetworkEvent('feature-discovered',
|
||||||
|
account=self._account,
|
||||||
|
feature=nbxmpp.NS_BLOCKING))
|
||||||
|
|
||||||
log.info('Discovered blocking: %s', from_)
|
log.info('Discovered blocking: %s', from_)
|
||||||
|
|
||||||
def get_blocking_list(self) -> None:
|
@ensure_online
|
||||||
if not self.supported:
|
def get_blocking_list(self, callback=None):
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq('get', nbxmpp.NS_BLOCKING)
|
|
||||||
iq.setQuery('blocklist')
|
|
||||||
log.info('Request list')
|
log.info('Request list')
|
||||||
self._con.connection.SendAndCallForResponse(
|
if callback is None:
|
||||||
iq, self._blocking_list_received)
|
callback = self._blocking_list_received
|
||||||
|
|
||||||
def _blocking_list_received(self, stanza: nbxmpp.Iq) -> None:
|
self._con.connection.get_module('Blocking').get_blocking_list(
|
||||||
if not nbxmpp.isResultNode(stanza):
|
callback=callback)
|
||||||
log.info('Error: %s', stanza.getError())
|
|
||||||
|
def _blocking_list_received(self, result):
|
||||||
|
if result.is_error:
|
||||||
|
log.info('Error: %s', result.error)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.blocked = []
|
self.blocked = result.blocking_list
|
||||||
blocklist = stanza.getTag('blocklist', namespace=nbxmpp.NS_BLOCKING)
|
|
||||||
if blocklist is None:
|
|
||||||
log.error('No blocklist node')
|
|
||||||
return
|
|
||||||
|
|
||||||
for item in blocklist.getTags('item'):
|
|
||||||
self.blocked.append(item.getAttr('jid'))
|
|
||||||
log.info('Received list: %s', self.blocked)
|
|
||||||
|
|
||||||
app.nec.push_incoming_event(
|
app.nec.push_incoming_event(
|
||||||
BlockingEvent(None, conn=self._con, changed=self.blocked))
|
BlockingEvent(None, conn=self._con, changed=self.blocked))
|
||||||
|
|
||||||
|
@ -129,35 +151,6 @@ class Blocking:
|
||||||
probe = nbxmpp.Presence(jid, 'probe', frm=self._con.get_own_jid())
|
probe = nbxmpp.Presence(jid, 'probe', frm=self._con.get_own_jid())
|
||||||
self._con.connection.send(probe)
|
self._con.connection.send(probe)
|
||||||
|
|
||||||
def block(self, contact_list):
|
|
||||||
if not self.supported:
|
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
|
|
||||||
query = iq.setQuery(name='block')
|
|
||||||
|
|
||||||
for contact in contact_list:
|
|
||||||
query.addChild(name='item', attrs={'jid': contact.jid})
|
|
||||||
log.info('Block: %s', contact.jid)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_result_handler, {})
|
|
||||||
|
|
||||||
def unblock(self, contact_list):
|
|
||||||
if not self.supported:
|
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
|
|
||||||
query = iq.setQuery(name='unblock')
|
|
||||||
|
|
||||||
for contact in contact_list:
|
|
||||||
query.addChild(name='item', attrs={'jid': contact.jid})
|
|
||||||
log.info('Unblock: %s', contact.jid)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_result_handler, {})
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _default_result_handler(_con, stanza):
|
|
||||||
if not nbxmpp.isResultNode(stanza):
|
|
||||||
log.warning('Operation failed: %s', stanza.getError())
|
|
||||||
|
|
||||||
|
|
||||||
class BlockingEvent(NetworkIncomingEvent):
|
class BlockingEvent(NetworkIncomingEvent):
|
||||||
name = 'blocking'
|
name = 'blocking'
|
||||||
|
|
|
@ -304,7 +304,8 @@ class PrivacyLists:
|
||||||
|
|
||||||
def block_contacts(self, contact_list, message):
|
def block_contacts(self, contact_list, message):
|
||||||
if not self.supported:
|
if not self.supported:
|
||||||
self._con.get_module('Blocking').block(contact_list)
|
jid_list = [contact.jid for contact in contact_list]
|
||||||
|
self._con.get_module('Blocking').block(jid_list)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.default_list is None:
|
if self.default_list is None:
|
||||||
|
@ -350,7 +351,8 @@ class PrivacyLists:
|
||||||
|
|
||||||
def unblock_contacts(self, contact_list):
|
def unblock_contacts(self, contact_list):
|
||||||
if not self.supported:
|
if not self.supported:
|
||||||
self._con.get_module('Blocking').unblock(contact_list)
|
jid_list = [contact.jid for contact in contact_list]
|
||||||
|
self._con.get_module('Blocking').unblock(jid_list)
|
||||||
return
|
return
|
||||||
|
|
||||||
new_blocked_list = []
|
new_blocked_list = []
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.12"/>
|
||||||
|
<object class="GtkListStore" id="blocking_store">
|
||||||
|
<columns>
|
||||||
|
<!-- column-name jid -->
|
||||||
|
<column type="gchararray"/>
|
||||||
|
</columns>
|
||||||
|
</object>
|
||||||
|
<object class="GtkGrid" id="blocking_grid">
|
||||||
|
<property name="width_request">400</property>
|
||||||
|
<property name="height_request">300</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">18</property>
|
||||||
|
<property name="margin_right">18</property>
|
||||||
|
<property name="margin_top">18</property>
|
||||||
|
<property name="margin_bottom">18</property>
|
||||||
|
<property name="row_spacing">5</property>
|
||||||
|
<property name="column_spacing">10</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButtonBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
|
<property name="layout_style">start</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="add_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="_on_add" swapped="no"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">list-add-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
<property name="non_homogeneous">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="remove_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="_on_remove" swapped="no"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">list-remove-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
<property name="non_homogeneous">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="save_button">
|
||||||
|
<property name="label">Save</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<signal name="clicked" handler="_on_save" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkOverlay" id="overlay">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="height_request">150</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="block_view">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="model">blocking_store</property>
|
||||||
|
<property name="search_column">0</property>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection" id="treeview-selection2"/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeViewColumn" id="treeviewcolumn1">
|
||||||
|
<property name="title" translatable="yes">Jabber ID</property>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="clickable">True</property>
|
||||||
|
<property name="sort_indicator">True</property>
|
||||||
|
<property name="sort_column_id">0</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCellRendererText" id="cellrenderertext3">
|
||||||
|
<property name="editable">True</property>
|
||||||
|
<property name="placeholder_text">user@example.org</property>
|
||||||
|
<signal name="edited" handler="_jid_edited" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="text">0</attribute>
|
||||||
|
</attributes>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="index">-1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,150 @@
|
||||||
|
# This file is part of Gajim.
|
||||||
|
#
|
||||||
|
# Gajim 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 3 only.
|
||||||
|
#
|
||||||
|
# Gajim 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.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import Gdk
|
||||||
|
|
||||||
|
from gajim.common import app
|
||||||
|
from gajim.common.i18n import _
|
||||||
|
|
||||||
|
from gajim.gtk.util import get_builder
|
||||||
|
from gajim.gtk.dialogs import HigDialog
|
||||||
|
|
||||||
|
log = logging.getLogger('gajim.gtk.blocking_list')
|
||||||
|
|
||||||
|
|
||||||
|
class BlockingList(Gtk.ApplicationWindow):
|
||||||
|
def __init__(self, account):
|
||||||
|
Gtk.ApplicationWindow.__init__(self)
|
||||||
|
self.set_application(app.app)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
self.set_show_menubar(False)
|
||||||
|
self.set_title(_('Blocking List for %s') % account)
|
||||||
|
|
||||||
|
self.connect('key-press-event', self._on_key_press)
|
||||||
|
|
||||||
|
self.account = account
|
||||||
|
self._con = app.connections[account]
|
||||||
|
self._prev_blocked_jids = set()
|
||||||
|
self._await_results = 2
|
||||||
|
self._received_errors = False
|
||||||
|
|
||||||
|
self._ui = get_builder('blocking_list.ui')
|
||||||
|
self.add(self._ui.blocking_grid)
|
||||||
|
|
||||||
|
self._spinner = Gtk.Spinner()
|
||||||
|
self._ui.overlay.add_overlay(self._spinner)
|
||||||
|
|
||||||
|
self._set_grid_state(False)
|
||||||
|
self._ui.connect_signals(self)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
self._activate_spinner()
|
||||||
|
|
||||||
|
self._con.get_module('Blocking').get_blocking_list(
|
||||||
|
callback=self._on_blocking_list_received)
|
||||||
|
|
||||||
|
def _reset_after_error(self):
|
||||||
|
self._received_errors = False
|
||||||
|
self._await_results = 2
|
||||||
|
self._disable_spinner()
|
||||||
|
self._set_grid_state(True)
|
||||||
|
|
||||||
|
def _show_error(self, error):
|
||||||
|
dialog = HigDialog(
|
||||||
|
self, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
|
||||||
|
_('Error!'),
|
||||||
|
error)
|
||||||
|
dialog.popup()
|
||||||
|
|
||||||
|
def _on_blocking_list_received(self, result):
|
||||||
|
self._disable_spinner()
|
||||||
|
self._set_grid_state(not result.is_error)
|
||||||
|
|
||||||
|
if result.is_error:
|
||||||
|
self._show_error(result.error)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._prev_blocked_jids = set(result.blocking_list)
|
||||||
|
self._ui.blocking_store.clear()
|
||||||
|
for item in result.blocking_list:
|
||||||
|
self._ui.blocking_store.append((item,))
|
||||||
|
|
||||||
|
def _on_save_result(self, result):
|
||||||
|
self._await_results -= 1
|
||||||
|
if result.is_error and not self._received_errors:
|
||||||
|
self._show_error(result.error)
|
||||||
|
self._received_errors = True
|
||||||
|
|
||||||
|
if not self._await_results:
|
||||||
|
if self._received_errors:
|
||||||
|
self._reset_after_error()
|
||||||
|
else:
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def _set_grid_state(self, state):
|
||||||
|
self._ui.blocking_grid.set_sensitive(state)
|
||||||
|
|
||||||
|
def _jid_edited(self, _renderer, path, new_text):
|
||||||
|
iter_ = self._ui.blocking_store.get_iter(path)
|
||||||
|
self._ui.blocking_store.set_value(iter_, 0, new_text)
|
||||||
|
|
||||||
|
def _on_add(self, _button):
|
||||||
|
self._ui.blocking_store.append([''])
|
||||||
|
|
||||||
|
def _on_remove(self, _button):
|
||||||
|
mod, paths = self._ui.block_view.get_selection().get_selected_rows()
|
||||||
|
for path in paths:
|
||||||
|
iter_ = mod.get_iter(path)
|
||||||
|
self._ui.blocking_store.remove(iter_)
|
||||||
|
|
||||||
|
def _on_save(self, _button):
|
||||||
|
self._activate_spinner()
|
||||||
|
self._set_grid_state(False)
|
||||||
|
|
||||||
|
blocked_jids = set()
|
||||||
|
for item in self._ui.blocking_store:
|
||||||
|
blocked_jids.add(item[0].lower())
|
||||||
|
|
||||||
|
unblock_jids = self._prev_blocked_jids - blocked_jids
|
||||||
|
if unblock_jids:
|
||||||
|
self._con.get_module('Blocking').unblock(
|
||||||
|
unblock_jids, callback=self._on_save_result)
|
||||||
|
else:
|
||||||
|
self._await_results -= 1
|
||||||
|
|
||||||
|
block_jids = blocked_jids - self._prev_blocked_jids
|
||||||
|
if block_jids:
|
||||||
|
self._con.get_module('Blocking').block(
|
||||||
|
block_jids, callback=self._on_save_result)
|
||||||
|
else:
|
||||||
|
self._await_results -= 1
|
||||||
|
|
||||||
|
if not self._await_results:
|
||||||
|
# No changes
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def _activate_spinner(self):
|
||||||
|
self._spinner.show()
|
||||||
|
self._spinner.start()
|
||||||
|
|
||||||
|
def _disable_spinner(self):
|
||||||
|
self._spinner.hide()
|
||||||
|
self._spinner.stop()
|
||||||
|
|
||||||
|
def _on_key_press(self, _widget, event):
|
||||||
|
if event.keyval == Gdk.KEY_Escape:
|
||||||
|
self.destroy()
|
|
@ -788,6 +788,7 @@ def get_account_menu(account):
|
||||||
('-start-single-chat', _('Send Single Message…')),
|
('-start-single-chat', _('Send Single Message…')),
|
||||||
(_('Advanced'), [
|
(_('Advanced'), [
|
||||||
('-archive', _('Archiving Preferences')),
|
('-archive', _('Archiving Preferences')),
|
||||||
|
('-blocking', _('Blocking List')),
|
||||||
('-sync-history', _('Synchronise History')),
|
('-sync-history', _('Synchronise History')),
|
||||||
('-privacylists', _('Privacy Lists')),
|
('-privacylists', _('Privacy Lists')),
|
||||||
('-server-info', _('Server Info')),
|
('-server-info', _('Server Info')),
|
||||||
|
|
Loading…
Reference in New Issue