Merge branch 'newaccount' into 'master'

New Account Window

See merge request !129
This commit is contained in:
Philipp Hörist 2017-09-16 16:22:59 +02:00
commit 2d8def8c1b
13 changed files with 1549 additions and 2868 deletions

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
<property name="default_width">350</property> <property name="default_width">350</property>
<property name="default_height">300</property> <property name="default_height">300</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<signal name="destroy" handler="on_destroy" swapped="no"/>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox7"> <object class="GtkBox" id="dialog-vbox7">
<property name="visible">True</property> <property name="visible">True</property>

View File

@ -30,14 +30,34 @@ popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; }
#ServerInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; } #ServerInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; }
#ServerInfoGrid > list > row.activatable:active { box-shadow: none; } #ServerInfoGrid > list > row.activatable:active { box-shadow: none; }
/* Generic Options Dialog */ /* OptionsBox */
#OptionsDialog list > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; } #OptionsBox > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; }
#OptionsDialog list > row:last-child { border-bottom: 0px} #OptionsBox > row:last-child { border-bottom: 0px}
#OptionsDialog list > row { padding: 10px; } #OptionsBox > row.activatable:active { box-shadow: none; }
#OptionsDialog list > row.activatable:active { box-shadow: none; } #OptionsBox > row { padding: 10px 20px 10px 10px; }
#OptionsBox > row:not(.activatable) label { color: @insensitive_fg_color }
/* GenericOption */
#SubDescription { color: @insensitive_fg_color;}
#GenericOptionBox { margin-left: 30px; }
#GenericOptionBox > label { padding-right: 3px; }
/* Generic Popover Menu with Buttons */ /* Generic Popover Menu with Buttons */
.PopoverButtonListbox { padding-left: 0px; padding-right: 0px; } .PopoverButtonListbox { padding-left: 0px; padding-right: 0px; }
.PopoverButtonListbox > list { margin-top: 10px; margin-bottom: 10px; } .PopoverButtonListbox > list { margin-top: 10px; margin-bottom: 10px; }
.PopoverButtonListbox > list > row { padding: 10px 20px 10px 20px; } .PopoverButtonListbox > list > row { padding: 10px 20px 10px 20px; }
.PopoverButtonListbox > list > row.activatable:active { box-shadow: none; background-color: @theme_selected_bg_color } .PopoverButtonListbox > list > row.activatable:active { box-shadow: none; background-color: @theme_selected_bg_color }
/* Accounts Window */
#AccountsWindow > box { padding:30px 30px 30px 30px;}
#AccountsWindow scrolledwindow {border: none;}
#AccountsWindow list {border: 1px solid; border-color: @borders;}
#AccountsWindow > box actionbar box {border: none;}
#AccountNameEntry:disabled { font-size: 16px;
font-weight: bold;
border: none;
background-color: @theme_unfocused_bg_color;
color: @theme_text_color; }

698
gajim/accounts_window.py Normal file
View File

@ -0,0 +1,698 @@
from functools import partial
from gi.repository import Gtk, Gio, GLib, Gdk
from gajim.common import app
from gajim.gtkgui_helpers import get_image_button
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
from gajim.common import passwords
from gajim import dialogs
from gajim import config
from gajim.common import helpers
from gajim.common.connection import Connection
from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf
from gajim.options_dialog import OptionsDialog, OptionsBox
from gajim.common.const import Option, OptionKind, OptionType
class AccountsWindow(Gtk.ApplicationWindow):
def __init__(self):
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_name('AccountsWindow')
self.set_size_request(500, -1)
self.set_resizable(False)
self.need_relogin = {}
glade_objects = [
'stack', 'box', 'actionbar', 'headerbar', 'back_button',
'menu_button', 'account_page', 'account_list']
self.builder = gtkgui_helpers.get_gtk_builder('accounts_window.ui')
for obj in glade_objects:
setattr(self, obj, self.builder.get_object(obj))
self.set_titlebar(self.headerbar)
menu = Gio.Menu()
menu.append('Merge Accounts', 'app.merge')
menu.append('Use PGP Agent', 'app.agent')
self.menu_button.set_menu_model(menu)
button = get_image_button('list-add-symbolic', 'Add')
button.set_action_name('app.add-account')
self.actionbar.pack_start(button)
accounts = app.config.get_per('accounts')
accounts.sort()
for account in accounts:
self.need_relogin[account] = self.get_relogin_options(account)
account_item = Account(account, self)
self.account_list.add(account_item)
account_item.set_activatable()
self.add(self.box)
self.builder.connect_signals(self)
self.connect('destroy', self.on_destroy)
self.connect('key-press-event', self.on_key_press)
self.show_all()
def on_key_press(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def on_destroy(self, *args):
self.check_relogin()
del app.interface.instances['accounts']
def on_child_visible(self, stack, *args):
page = stack.get_visible_child_name()
if page is None:
return
if page == 'main':
self.menu_button.show()
self.back_button.hide()
self.check_relogin()
else:
self.back_button.show()
self.menu_button.hide()
def on_back_button(self, *args):
page = self.stack.get_visible_child_name()
child = self.stack.get_visible_child()
self.remove_all_pages()
if page == 'account':
child.toggle.set_active(False)
self.stack.add_named(self.account_page, 'main')
self.stack.set_visible_child_name('main')
self.update_accounts()
else:
self.stack.add_named(child.parent, 'account')
self.stack.set_visible_child_name('account')
def update_accounts(self):
for row in self.account_list.get_children():
row.get_child().update()
@staticmethod
def on_row_activated(listbox, row):
row.get_child().on_row_activated()
def remove_all_pages(self):
for page in self.stack.get_children():
self.stack.remove(page)
def set_page(self, page, name):
self.remove_all_pages()
self.stack.add_named(page, name)
page.update()
page.show_all()
self.stack.set_visible_child(page)
def update_proxy_list(self):
page = self.stack.get_child_by_name('connetion')
if page is None:
return
page.options['proxy'].update_values()
def check_relogin(self):
for account in self.need_relogin:
options = self.get_relogin_options(account)
active = app.config.get_per('accounts', account, 'active')
if options != self.need_relogin[account]:
self.need_relogin[account] = options
if active:
self.relog(account)
break
def relog(self, account):
if app.connections[account].connected == 0:
return
if account == app.ZEROCONF_ACC_NAME:
app.connections[app.ZEROCONF_ACC_NAME].update_details()
return
def login(account, show_before, status_before):
"""
Login with previous status
"""
# first make sure connection is really closed,
# 0.5 may not be enough
app.connections[account].disconnect(True)
app.interface.roster.send_status(
account, show_before, status_before)
def relog(account):
show_before = app.SHOW_LIST[app.connections[account].connected]
status_before = app.connections[account].status
app.interface.roster.send_status(
account, 'offline', _('Be right back.'))
GLib.timeout_add(500, login, account, show_before, status_before)
dialogs.YesNoDialog(
_('Relogin now?'),
_('If you want all the changes to apply instantly, '
'you must relogin.'),
transient_for=self,
on_response_yes=lambda *args: relog(account))
@staticmethod
def get_relogin_options(account):
if account == app.ZEROCONF_ACC_NAME:
options = ['zeroconf_first_name', 'zeroconf_last_name',
'zeroconf_jabber_id', 'zeroconf_email', 'keyid']
else:
options = ['client_cert', 'proxy', 'resource',
'use_custom_host', 'custom_host', 'custom_port',
'keyid']
values = []
for option in options:
values.append(app.config.get_per('accounts', account, option))
return values
def on_remove_account(self, button, account):
if app.events.get_events(account):
dialogs.ErrorDialog(
_('Unread events'),
_('Read all pending events before removing this account.'),
transient_for=self)
return
if app.config.get_per('accounts', account, 'is_zeroconf'):
# Should never happen as button is insensitive
return
win_opened = False
if app.interface.msg_win_mgr.get_controls(acct=account):
win_opened = True
elif account in app.interface.instances:
for key in app.interface.instances[account]:
if (app.interface.instances[account][key] and
key != 'remove_account'):
win_opened = True
break
# Detect if we have opened windows for this account
def remove(account):
if (account in app.interface.instances and
'remove_account' in app.interface.instances[account]):
dialog = app.interface.instances[account]['remove_account']
dialog.window.present()
else:
if account not in app.interface.instances:
app.interface.instances[account] = {}
app.interface.instances[account]['remove_account'] = \
config.RemoveAccountWindow(account)
if win_opened:
dialogs.ConfirmationDialog(
_('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))
else:
remove(account)
def remove_account(self, account):
for row in self.account_list.get_children():
if row.get_child().account == account:
self.account_list.remove(row)
del self.need_relogin[account]
break
def add_account(self, account):
account_item = Account(account, self)
self.account_list.add(account_item)
account_item.set_activatable()
self.account_list.show_all()
self.stack.show_all()
self.need_relogin[account] = self.get_relogin_options(account)
def select_account(self, account):
for row in self.account_list.get_children():
if row.get_child().account == account:
self.account_list.emit('row-activated', row)
break
@staticmethod
def enable_account(account):
if account == app.ZEROCONF_ACC_NAME:
app.connections[account] = ConnectionZeroconf(account)
else:
app.connections[account] = Connection(account)
# update variables
app.interface.instances[account] = {
'infos': {}, 'disco': {}, 'gc_config': {}, 'search': {},
'online_dialog': {}, 'sub_request': {}}
app.interface.minimized_controls[account] = {}
app.connections[account].connected = 0
app.groups[account] = {}
app.contacts.add_account(account)
app.gc_connected[account] = {}
app.automatic_rooms[account] = {}
app.newly_added[account] = []
app.to_be_removed[account] = []
if account == app.ZEROCONF_ACC_NAME:
app.nicks[account] = app.ZEROCONF_ACC_NAME
else:
app.nicks[account] = app.config.get_per(
'accounts', account, 'name')
app.block_signed_in_notifications[account] = True
app.sleeper_state[account] = 'off'
app.encrypted_chats[account] = []
app.last_message_time[account] = {}
app.status_before_autoaway[account] = ''
app.transport_avatar[account] = {}
app.gajim_optional_features[account] = []
app.caps_hash[account] = ''
helpers.update_optional_features(account)
# refresh roster
if len(app.connections) >= 2:
# Do not merge accounts if only one exists
app.interface.roster.regroup = app.config.get('mergeaccounts')
else:
app.interface.roster.regroup = False
app.interface.roster.setup_and_draw_roster()
gui_menu_builder.build_accounts_menu()
@staticmethod
def disable_account(account):
app.interface.roster.close_all(account)
if account == app.ZEROCONF_ACC_NAME:
app.connections[account].disable_account()
app.connections[account].cleanup()
del app.connections[account]
del app.interface.instances[account]
del app.interface.minimized_controls[account]
del app.nicks[account]
del app.block_signed_in_notifications[account]
del app.groups[account]
app.contacts.remove_account(account)
del app.gc_connected[account]
del app.automatic_rooms[account]
del app.to_be_removed[account]
del app.newly_added[account]
del app.sleeper_state[account]
del app.encrypted_chats[account]
del app.last_message_time[account]
del app.status_before_autoaway[account]
del app.transport_avatar[account]
del app.gajim_optional_features[account]
del app.caps_hash[account]
if len(app.connections) >= 2:
# Do not merge accounts if only one exists
app.interface.roster.regroup = app.config.get('mergeaccounts')
else:
app.interface.roster.regroup = False
app.interface.roster.setup_and_draw_roster()
gui_menu_builder.build_accounts_menu()
class Account(Gtk.Box):
def __init__(self, account, parent):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL,
spacing=12)
self.account = account
if account == app.ZEROCONF_ACC_NAME:
self.options = ZeroConfPage(account)
else:
self.options = AccountPage(account)
self.parent = parent
switch = Gtk.Switch()
switch.set_active(app.config.get_per('accounts', account, 'active'))
switch.set_vexpand(False)
switch.set_valign(Gtk.Align.CENTER)
switch.set_halign(Gtk.Align.START)
if account == app.ZEROCONF_ACC_NAME and not app.HAVE_ZEROCONF:
switch.set_sensitive(False)
switch.set_active(False)
switch.connect('notify::active', self.on_switch, self.account)
account_label = app.config.get_per('accounts', account, 'account_label')
self.label = Gtk.Label(label=account_label or account)
self.label.set_halign(Gtk.Align.START)
self.label.set_hexpand(True)
self.add(switch)
self.add(self.label)
if account != app.ZEROCONF_ACC_NAME:
button = get_image_button('list-remove-symbolic', 'Remove')
button.connect('clicked', parent.on_remove_account, account)
self.add(button)
def set_activatable(self):
if self.account == app.ZEROCONF_ACC_NAME:
self.get_parent().set_activatable(app.HAVE_ZEROCONF)
def on_switch(self, switch, param, account):
old_state = app.config.get_per('accounts', account, 'active')
state = switch.get_active()
if old_state == state:
return
if (account in app.connections and
app.connections[account].connected > 0):
# connecting or connected
dialogs.ErrorDialog(
_('You are currently connected to the server'),
_('To disable the account, you must be disconnected.'),
transient_for=self.parent)
switch.set_active(not state)
return
if state:
self.parent.enable_account(account)
else:
self.parent.disable_account(account)
app.config.set_per('accounts', account, 'active', state)
def on_row_activated(self):
self.options.update_states()
self.parent.set_page(self.options, 'account')
def update(self):
account_label = app.config.get_per(
'accounts', self.account, 'account_label')
self.label.set_text(account_label or self.account)
class GenericOptionPage(Gtk.Box):
def __init__(self, account, parent, options):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=12)
self.account = account
self.parent = parent
self.toggle = get_image_button('document-edit-symbolic',
_('Rename account label'), toggle=True)
self.toggle.connect('toggled', self.set_entry_text)
self.entry = Gtk.Entry()
self.entry.set_sensitive(False)
self.entry.set_name('AccountNameEntry')
self.set_entry_text(self.toggle, update=True)
box = Gtk.Box()
if isinstance(self, AccountPage):
box.pack_start(self.toggle, False, True, 0)
box.pack_start(self.entry, True, True, 0)
self.listbox = OptionsBox(account)
self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
for option in options:
self.listbox.add_option(option)
self.listbox.update_states()
self.pack_start(box, False, False, 0)
self.pack_start(self.listbox, True, True, 0)
self.listbox.connect('row-activated', self.on_row_activated)
def update_states(self):
self.listbox.update_states()
def on_row_activated(self, listbox, row):
self.toggle.set_active(False)
row.get_child().on_row_activated()
def set_entry_text(self, toggle, update=False):
account_label = app.config.get_per(
'accounts', self.account, 'account_label')
if update:
self.entry.set_text(account_label or self.account)
return
if toggle.get_active():
self.entry.set_sensitive(True)
self.entry.grab_focus()
else:
self.entry.set_sensitive(False)
value = self.entry.get_text()
if not value:
value = account_label or self.account
app.config.set_per('accounts', self.account,
'account_label', value or self.account)
if app.config.get_per('accounts', self.account, 'active'):
app.interface.roster.draw_account(self.account)
def update(self):
self.set_entry_text(self.toggle, update=True)
def set_page(self, options, name):
options.update_states()
self.get_toplevel().set_page(options, name)
class AccountPage(GenericOptionPage):
def __init__(self, account, parent=None):
general = partial(
self.set_page, GeneralPage(account, self), 'general')
connection = partial(
self.set_page, ConnectionPage(account, self), 'connection')
options = [
Option(OptionKind.LOGIN, _('Login'), OptionType.DIALOG,
props={'dialog': LoginDialog}),
Option(OptionKind.ACTION, _('Profile'), OptionType.ACTION,
'-profile', props={'action_args': account}),
Option(OptionKind.CALLBACK, _('General'),
name='general', props={'callback': general}),
Option(OptionKind.CALLBACK, _('Connection'),
name='connection', props={'callback': connection}),
Option(OptionKind.ACTION, _('Import Contacts'), OptionType.ACTION,
'-import-contacts', props={'action_args': account}),
Option(OptionKind.DIALOG, _('Client Certificate'),
OptionType.DIALOG, props={'dialog': CertificateDialog}),
Option(OptionKind.GPG, _('OpenPGP Key'), OptionType.DIALOG,
props={'dialog': None}),
]
GenericOptionPage.__init__(self, account, parent, options)
class GeneralPage(GenericOptionPage):
def __init__(self, account, parent=None):
options = [
Option(OptionKind.SWITCH, _('Connect on startup'),
OptionType.ACCOUNT_CONFIG, 'autoconnect'),
Option(OptionKind.SWITCH, _('Reconnect when connection is lost'),
OptionType.ACCOUNT_CONFIG, 'autoreconnect'),
Option(OptionKind.SWITCH, _('Save conversations for all contacts'),
OptionType.ACCOUNT_CONFIG, 'no_log_for',
desc=_('Store conversations on the harddrive')),
Option(OptionKind.SWITCH, _('Server Message Archive'),
OptionType.ACCOUNT_CONFIG, 'sync_logs_with_server',
desc=_('Messages get stored on the server.\n'
'The archive is used to sync messages\n'
'between multiple devices.\n'
'XEP-0313')),
Option(OptionKind.SWITCH, _('Global Status'),
OptionType.ACCOUNT_CONFIG, 'sync_with_global_status',
desc=_('Synchronise the status of all accounts')),
Option(OptionKind.SWITCH, _('Message Carbons'),
OptionType.ACCOUNT_CONFIG, 'enable_message_carbons',
desc=_('All your other online devices get copies\n'
'of sent and received messages.\n'
'XEP-0280')),
Option(OptionKind.SWITCH, _('Use file transfer proxies'),
OptionType.ACCOUNT_CONFIG, 'use_ft_proxies'),
]
GenericOptionPage.__init__(self, account, parent, options)
class ConnectionPage(GenericOptionPage):
def __init__(self, account, parent=None):
options = [
Option(OptionKind.SWITCH, 'HTTP_PROXY',
OptionType.ACCOUNT_CONFIG, 'use_env_http_proxy',
desc=_('Use environment variable')),
Option(OptionKind.PROXY, _('Proxy'),
OptionType.ACCOUNT_CONFIG, 'proxy', name='proxy'),
Option(OptionKind.SWITCH, _('Warn on insecure connection'),
OptionType.ACCOUNT_CONFIG,
'warn_when_insecure_ssl_connection'),
Option(OptionKind.SWITCH, _('Send keep-alive packets'),
OptionType.ACCOUNT_CONFIG, 'keep_alives_enabled'),
Option(OptionKind.HOSTNAME, _('Hostname'), OptionType.DIALOG,
desc=_('Manually set the hostname for the server'),
props={'dialog': CutstomHostnameDialog}),
Option(OptionKind.ENTRY, _('Resource'),
OptionType.ACCOUNT_CONFIG, 'resource'),
Option(OptionKind.PRIORITY, _('Priority'),
OptionType.DIALOG, props={'dialog': PriorityDialog}),
]
GenericOptionPage.__init__(self, account, parent, options)
class ZeroConfPage(GenericOptionPage):
def __init__(self, account, parent=None):
options = [
Option(OptionKind.DIALOG, _('Credentials'),
OptionType.DIALOG, props={'dialog': CredentialsDialog}),
Option(OptionKind.SWITCH, _('Connect on startup'),
OptionType.ACCOUNT_CONFIG, 'autoconnect',
desc=_('Use environment variable')),
Option(OptionKind.SWITCH, _('Save conversations for all contacts'),
OptionType.ACCOUNT_CONFIG, 'no_log_for',
desc=_('Store conversations on the harddrive')),
Option(OptionKind.SWITCH, _('Global Status'),
OptionType.ACCOUNT_CONFIG, 'sync_with_global_status',
desc=_('Synchronize the status of all accounts')),
Option(OptionKind.GPG, _('OpenPGP Key'),
OptionType.DIALOG, props={'dialog': None}),
]
GenericOptionPage.__init__(self, account, parent, options)
class CredentialsDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.ENTRY, _('First Name'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_first_name'),
Option(OptionKind.ENTRY, _('Last Name'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_last_name'),
Option(OptionKind.ENTRY, _('Jabber ID'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_jabber_id'),
Option(OptionKind.ENTRY, _('Email'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_email'),
]
OptionsDialog.__init__(self, parent, _('Credential Options'),
Gtk.DialogFlags.MODAL, options, account)
class PriorityDialog(OptionsDialog):
def __init__(self, account, parent):
neg_priority = app.config.get('enable_negative_priority')
if neg_priority:
range_ = (-128, 127)
else:
range_ = (0, 127)
options = [
Option(OptionKind.SWITCH, _('Adjust to status'),
OptionType.ACCOUNT_CONFIG, 'adjust_priority_with_status',
'adjust'),
Option(OptionKind.SPIN, _('Priority'),
OptionType.ACCOUNT_CONFIG, 'priority',
enabledif=('adjust', False), props={'range_': range_}),
]
OptionsDialog.__init__(self, parent, _('Priority'),
Gtk.DialogFlags.MODAL, options, account)
self.connect('destroy', self.on_destroy)
def on_destroy(self, *args):
# Update priority
if self.account not in app.connections:
return
show = app.SHOW_LIST[app.connections[self.account].connected]
status = app.connections[self.account].status
app.connections[self.account].change_status(show, status)
class CutstomHostnameDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.SWITCH, _('Enable'),
OptionType.ACCOUNT_CONFIG, 'use_custom_host', name='custom'),
Option(OptionKind.ENTRY, _('Hostname'),
OptionType.ACCOUNT_CONFIG, 'custom_host',
enabledif=('custom', True)),
Option(OptionKind.ENTRY, _('Port'),
OptionType.ACCOUNT_CONFIG, 'custom_port',
enabledif=('custom', True)),
]
OptionsDialog.__init__(self, parent, _('Connection Options'),
Gtk.DialogFlags.MODAL, options, account)
class CertificateDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.FILECHOOSER, _('Client Certificate'),
OptionType.ACCOUNT_CONFIG, 'client_cert',
props={'filefilter': (_('PKCS12 Files'), '*.p12')}),
Option(OptionKind.SWITCH, _('Encrypted Certificate'),
OptionType.ACCOUNT_CONFIG, 'client_cert_encrypted'),
]
OptionsDialog.__init__(self, parent, _('Certificate Options'),
Gtk.DialogFlags.MODAL, options, account)
class LoginDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.ENTRY, _('Password'),
OptionType.ACCOUNT_CONFIG, 'password', name='password',
enabledif=('savepass', True)),
Option(OptionKind.SWITCH, _('Save Password'),
OptionType.ACCOUNT_CONFIG, 'savepass', name='savepass'),
Option(OptionKind.CHANGEPASSWORD, _('Change Password'),
OptionType.DIALOG, callback=self.on_password_change,
props={'dialog': None}),
]
OptionsDialog.__init__(self, parent, _('Login Options'),
Gtk.DialogFlags.MODAL, options, account)
self.connect('destroy', self.on_destroy)
def on_password_change(self, new_password, data):
self.get_option('password').entry.set_text(new_password)
def on_destroy(self, *args):
savepass = app.config.get_per('accounts', self.account, 'savepass')
if not savepass:
passwords.save_password(self.account, '')

View File

@ -25,10 +25,12 @@ from gajim.common.exceptions import GajimGeneralException
from gi.repository import Gtk from gi.repository import Gtk
import sys import sys
import os import os
from gajim import config from gajim import config
from gajim import dialogs from gajim import dialogs
from gajim import features_window from gajim import features_window
from gajim import shortcuts_window from gajim import shortcuts_window
from gajim import accounts_window
import gajim.plugins.gui import gajim.plugins.gui
from gajim import history_window from gajim import history_window
from gajim import disco from gajim import disco
@ -57,10 +59,10 @@ class AppActions():
interface.instances['plugins'] = gajim.plugins.gui.PluginsWindow() interface.instances['plugins'] = gajim.plugins.gui.PluginsWindow()
def on_accounts(self, action, param): def on_accounts(self, action, param):
if 'accounts' in interface.instances: if 'accounts' in app.interface.instances:
interface.instances['accounts'].window.present() app.interface.instances['accounts'].present()
else: else:
interface.instances['accounts'] = config.AccountsWindow() app.interface.instances['accounts'] = accounts_window.AccountsWindow()
def on_history_manager(self, action, param): def on_history_manager(self, action, param):
from gajim.history_manager import HistoryManager from gajim.history_manager import HistoryManager
@ -131,6 +133,35 @@ class AppActions():
def on_single_message(self, action, param): def on_single_message(self, action, param):
dialogs.SingleMessageWindow(param.get_string(), action='send') dialogs.SingleMessageWindow(param.get_string(), action='send')
def on_merge_accounts(self, action, param):
action.set_state(param)
value = param.get_boolean()
app.config.set('mergeaccounts', value)
if len(app.connections) >= 2: # Do not merge accounts if only one active
app.interface.roster.regroup = value
else:
app.interface.roster.regroup = False
app.interface.roster.setup_and_draw_roster()
def on_use_pgp_agent(self, action, param):
action.set_state(param)
app.config.set('use_gpg_agent', param.get_boolean())
def on_add_account(self, action, param):
if 'account_creation_wizard' in app.interface.instances:
app.interface.instances['account_creation_wizard'].window.present()
else:
app.interface.instances['account_creation_wizard'] = \
config.AccountCreationWizardWindow()
def on_import_contacts(self, action, param):
account = param.get_string()
if 'import_contacts' in app.interface.instances:
app.interface.instances['import_contacts'].dialog.present()
else:
app.interface.instances['import_contacts'] = \
dialogs.SynchroniseSelectAccountDialog(account)
# Advanced Actions # Advanced Actions
def on_archiving_preferences(self, action, param): def on_archiving_preferences(self, action, param):
@ -174,6 +205,13 @@ class AppActions():
interface.instances[account]['xml_console'] = \ interface.instances[account]['xml_console'] = \
dialogs.XMLConsoleWindow(account) dialogs.XMLConsoleWindow(account)
def on_manage_proxies(self, action, param):
if 'manage_proxies' in app.interface.instances:
app.interface.instances['manage_proxies'].window.present()
else:
app.interface.instances['manage_proxies'] = \
config.ManageProxiesWindow(interface.roster.window)
# Admin Actions # Admin Actions
def on_set_motd(self, action, param): def on_set_motd(self, action, param):

View File

@ -325,6 +325,7 @@ class Config:
__options_per_key = { __options_per_key = {
'accounts': ({ 'accounts': ({
'name': [ opt_str, '', '', True ], 'name': [ opt_str, '', '', True ],
'account_label': [ opt_str, '', '', False ],
'hostname': [ opt_str, '', '', True ], 'hostname': [ opt_str, '', '', True ],
'anonymous_auth': [ opt_bool, False ], 'anonymous_auth': [ opt_bool, False ],
'client_cert': [ opt_str, '', '', True ], 'client_cert': [ opt_str, '', '', True ],

View File

@ -1,3 +1,34 @@
from enum import IntEnum, unique
from collections import namedtuple
Option = namedtuple('Option', 'kind label type value name callback data desc enabledif props')
Option.__new__.__defaults__ = (None,) * len(Option._fields)
@unique
class OptionKind(IntEnum):
ENTRY = 0
SWITCH = 1
SPIN = 2
ACTION = 3
LOGIN = 4
DIALOG = 5
CALLBACK = 6
PROXY = 7
HOSTNAME = 8
PRIORITY = 9
FILECHOOSER = 10
CHANGEPASSWORD = 11
GPG = 12
@unique
class OptionType(IntEnum):
ACCOUNT_CONFIG = 0
CONFIG = 1
BOOL = 2
ACTION = 3
DIALOG = 4
THANKS = u"""\ THANKS = u"""\
Alexander Futász Alexander Futász
Alexander V. Butenko Alexander V. Butenko

File diff suppressed because it is too large Load Diff

View File

@ -48,6 +48,8 @@ from random import randrange
from gajim.common import pep from gajim.common import pep
from gajim.common import ged from gajim.common import ged
from gajim.common import const from gajim.common import const
from gajim.options_dialog import OptionsDialog
from gajim.common.const import Option, OptionKind, OptionType
try: try:
from gajim import gtkspell from gajim import gtkspell
@ -2675,7 +2677,7 @@ class SynchroniseSelectAccountDialog:
self.account = account self.account = account
self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui') self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui')
self.dialog = self.xml.get_object('synchronise_select_account_dialog') self.dialog = self.xml.get_object('synchronise_select_account_dialog')
self.dialog.set_transient_for(app.interface.instances['accounts'].window) self.dialog.set_transient_for(app.interface.instances['accounts'])
self.accounts_treeview = self.xml.get_object('accounts_treeview') self.accounts_treeview = self.xml.get_object('accounts_treeview')
model = Gtk.ListStore(str, str, bool) model = Gtk.ListStore(str, str, bool)
self.accounts_treeview.set_model(model) self.accounts_treeview.set_model(model)
@ -2731,6 +2733,10 @@ class SynchroniseSelectAccountDialog:
return return
self.dialog.destroy() self.dialog.destroy()
@staticmethod
def on_destroy(widget):
del app.interface.instances['import_contacts']
class SynchroniseSelectContactsDialog: class SynchroniseSelectContactsDialog:
def __init__(self, account, remote_account): def __init__(self, account, remote_account):
self.local_account = account self.local_account = account
@ -3323,6 +3329,8 @@ class XMLConsoleWindow(Gtk.Window):
setattr(self, obj, self.builder.get_object(obj)) setattr(self, obj, self.builder.get_object(obj))
self.set_titlebar(self.headerbar) self.set_titlebar(self.headerbar)
jid = app.get_jid_from_account(account)
self.headerbar.set_subtitle(jid)
self.set_default_size(600, 600) self.set_default_size(600, 600)
self.add(self.box) self.add(self.box)
@ -3434,26 +3442,30 @@ class XMLConsoleWindow(Gtk.Window):
def on_filter_options(self, *args): def on_filter_options(self, *args):
options = [ options = [
SwitchOption('Presence', self.presence, Option(OptionKind.SWITCH, 'Presence',
self.on_option, OptionType.BOOL, self.presence,
'presence'), callback=self.on_option, data='presence'),
SwitchOption('Message', self.message,
self.on_option,
'message'),
SwitchOption('Iq', self.iq,
self.on_option,
'iq'),
SwitchOption('Stream\nManagement', self.stream,
self.on_option,
'stream'),
SwitchOption('In', self.incoming,
self.on_option,
'incoming'),
SwitchOption('Out', self.outgoing,
self.on_option,
'outgoing')]
OptionsDialog(self, 'Filter', options) Option(OptionKind.SWITCH, 'Message',
OptionType.BOOL, self.message,
callback=self.on_option, data='message'),
Option(OptionKind.SWITCH, 'Iq', OptionType.BOOL, self.iq,
callback=self.on_option, data='iq'),
Option(OptionKind.SWITCH, 'Stream\nManagement',
OptionType.BOOL, self.stream,
callback=self.on_option, data='stream'),
Option(OptionKind.SWITCH, 'In', OptionType.BOOL, self.incoming,
callback=self.on_option, data='incoming'),
Option(OptionKind.SWITCH, 'Out', OptionType.BOOL, self.outgoing,
callback=self.on_option, data='outgoing'),
]
OptionsDialog(self, 'Filter', Gtk.DialogFlags.DESTROY_WITH_PARENT,
options, self.account)
def on_clear(self, *args): def on_clear(self, *args):
buffer_ = self.textview.get_buffer().set_text('') buffer_ = self.textview.get_buffer().set_text('')
@ -3468,13 +3480,12 @@ class XMLConsoleWindow(Gtk.Window):
def on_enable(self, switch, param): def on_enable(self, switch, param):
self.enabled = switch.get_active() self.enabled = switch.get_active()
def on_option(self, switch, param, *user_data): def on_option(self, value, data):
kind = user_data[0] setattr(self, data, value)
setattr(self, kind, switch.get_active()) value = not value
value = not switch.get_active()
table = self.textview.get_buffer().get_tag_table() table = self.textview.get_buffer().get_tag_table()
tag = table.lookup(kind) tag = table.lookup(data)
if kind in ('incoming', 'outgoing'): if data in ('incoming', 'outgoing'):
if value: if value:
tag.set_priority(table.get_size() - 1) tag.set_priority(table.get_size() - 1)
else: else:
@ -3522,58 +3533,6 @@ class XMLConsoleWindow(Gtk.Window):
if at_the_end: if at_the_end:
GLib.idle_add(gtkgui_helpers.scroll_to_end, self.scrolled) GLib.idle_add(gtkgui_helpers.scroll_to_end, self.scrolled)
class OptionsDialog(Gtk.Dialog):
def __init__(self, parent, title, options):
Gtk.Dialog.__init__(self, title, parent,
Gtk.DialogFlags.DESTROY_WITH_PARENT)
self.set_name('OptionsDialog')
self.set_resizable(False)
self.set_default_size(250, -1)
self.remove(self.get_content_area())
listbox = Gtk.ListBox()
listbox.set_hexpand(True)
listbox.set_selection_mode(Gtk.SelectionMode.NONE)
for option in options:
listbox.add(option)
self.add(listbox)
self.show_all()
listbox.connect('row-activated', self.on_row_activated)
def on_row_activated(self, listbox, row):
row.get_child().set_switch_state()
class SwitchOption(Gtk.Grid):
def __init__(self, label, state, callback, *user_data):
Gtk.Grid.__init__(self)
self.set_column_spacing(6)
label = Gtk.Label(label=label)
label.set_hexpand(True)
label.set_halign(Gtk.Align.START)
self.switch = Gtk.Switch()
self.switch.set_active(state)
self.switch.connect("notify::active", callback, *user_data)
self.switch.set_hexpand(True)
self.switch.set_halign(Gtk.Align.END)
self.switch.set_valign(Gtk.Align.CENTER)
self.add(label)
self.add(self.switch)
self.show_all()
def set_switch_state(self):
state = self.switch.get_active()
self.switch.set_active(not state)
#Action that can be done with an incoming list of contacts #Action that can be done with an incoming list of contacts
TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'),
'remove': _('remove')} 'remove': _('remove')}
@ -4580,7 +4539,7 @@ class ClientCertChooserDialog(FileChooserDialog):
FileChooserDialog.__init__(self, FileChooserDialog.__init__(self,
title_text=_('Choose Client Cert #PCKS12'), title_text=_('Choose Client Cert #PCKS12'),
transient_for=app.interface.instances['accounts'].window, transient_for=app.interface.instances['accounts'],
action=Gtk.FileChooserAction.OPEN, action=Gtk.FileChooserAction.OPEN,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK), Gtk.STOCK_OPEN, Gtk.ResponseType.OK),

View File

@ -319,6 +319,7 @@ class GajimApplication(Gtk.Application):
def add_actions(self): def add_actions(self):
''' Build Application Actions ''' ''' Build Application Actions '''
from gajim.app_actions import AppActions from gajim.app_actions import AppActions
from gajim.common import app
action = AppActions(self) action = AppActions(self)
self.account_actions = [ self.account_actions = [
@ -339,12 +340,30 @@ class GajimApplication(Gtk.Application):
('-update-motd', action.on_update_motd, 'online', 's'), ('-update-motd', action.on_update_motd, 'online', 's'),
('-delete-motd', action.on_delete_motd, 'online', 's'), ('-delete-motd', action.on_delete_motd, 'online', 's'),
('-activate-bookmark', ('-activate-bookmark',
action.on_activate_bookmark, 'online', 'a{sv}') action.on_activate_bookmark, 'online', 'a{sv}'),
('-import-contacts', action.on_import_contacts, 'online', 's')
] ]
# General Stateful Actions
act = Gio.SimpleAction.new_stateful(
'merge', None,
GLib.Variant.new_boolean(app.config.get('mergeaccounts')))
act.connect('change-state', action.on_merge_accounts)
self.add_action(act)
act = Gio.SimpleAction.new_stateful(
'agent', None,
GLib.Variant.new_boolean(app.config.get('use_gpg_agent')))
self.add_action(act)
# General Actions
self.general_actions = [ self.general_actions = [
('quit', action.on_quit), ('quit', action.on_quit),
('accounts', action.on_accounts), ('accounts', action.on_accounts),
('add-account', action.on_add_account),
('manage-proxies', action.on_manage_proxies),
('bookmarks', action.on_manage_bookmarks), ('bookmarks', action.on_manage_bookmarks),
('history-manager', action.on_history_manager), ('history-manager', action.on_history_manager),
('preferences', action.on_preferences), ('preferences', action.on_preferences),
@ -364,8 +383,7 @@ class GajimApplication(Gtk.Application):
act.connect("activate", func) act.connect("activate", func)
self.add_action(act) self.add_action(act)
from gajim.common import app accounts_list = sorted(app.config.get_per('accounts'))
accounts_list = sorted(app.contacts.get_accounts())
if not accounts_list: if not accounts_list:
return return
if len(accounts_list) > 1: if len(accounts_list) > 1:

View File

@ -107,15 +107,16 @@ def add_image_to_button(button, icon_name):
button.set_image(img) button.set_image(img)
def get_image_button(icon_name, tooltip, toggle=False): def get_image_button(icon_name, tooltip, toggle=False):
if toggle:
button = Gtk.ToggleButton()
icon = get_icon_pixmap(icon_name) icon = get_icon_pixmap(icon_name)
image = Gtk.Image() image = Gtk.Image()
image.set_from_pixbuf(icon) image.set_from_pixbuf(icon)
if toggle:
button = Gtk.ToggleButton()
else:
button = Gtk.Button()
button.set_tooltip_text(_(tooltip))
button.set_image(image) button.set_image(image)
else:
button = Gtk.Button.new_from_icon_name(
icon_name, Gtk.IconSize.MENU)
button.set_tooltip_text(_(tooltip))
return button return button
GUI_DIR = os.path.join(app.DATA_DIR, 'gui') GUI_DIR = os.path.join(app.DATA_DIR, 'gui')

585
gajim/options_dialog.py Normal file
View File

@ -0,0 +1,585 @@
from gi.repository import Gtk, GLib, Gdk, GObject
from gajim.common import app
from gajim.common import passwords
from gajim import gtkgui_helpers
from gajim.common.const import OptionKind, OptionType
from gajim.common.exceptions import GajimGeneralException
from gajim import dialogs
class OptionsDialog(Gtk.ApplicationWindow):
def __init__(self, parent, title, flags, options, account):
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_show_menubar(False)
self.set_title(title)
self.set_transient_for(parent)
self.set_resizable(False)
self.set_default_size(250, -1)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.account = account
if flags == Gtk.DialogFlags.MODAL:
self.set_modal(True)
elif flags == Gtk.DialogFlags.DESTROY_WITH_PARENT:
self.set_destroy_with_parent(True)
self.listbox = OptionsBox(account)
self.listbox.set_hexpand(True)
self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
for option in options:
self.listbox.add_option(option)
self.listbox.update_states()
self.add(self.listbox)
self.show_all()
self.listbox.connect('row-activated', self.on_row_activated)
@staticmethod
def on_row_activated(listbox, row):
row.get_child().on_row_activated()
def get_option(self, name):
return self.listbox.get_option(name)
class OptionsBox(Gtk.ListBox):
def __init__(self, account):
Gtk.ListBox.__init__(self)
self.set_name('OptionsBox')
self.account = account
self.named_options = {}
self.map = {
OptionKind.SWITCH: SwitchOption,
OptionKind.SPIN: SpinOption,
OptionKind.DIALOG: DialogOption,
OptionKind.ENTRY: EntryOption,
OptionKind.ACTION: ActionOption,
OptionKind.LOGIN: LoginOption,
OptionKind.FILECHOOSER: FileChooserOption,
OptionKind.CALLBACK: CallbackOption,
OptionKind.PROXY: ProxyComboOption,
OptionKind.PRIORITY: PriorityOption,
OptionKind.HOSTNAME: CutstomHostnameOption,
OptionKind.CHANGEPASSWORD: ChangePasswordOption,
OptionKind.GPG: GPGOption,
}
def add_option(self, option):
if option.props is not None:
listitem = self.map[option.kind](
self.account, *option[1:-1], **option.props)
else:
listitem = self.map[option.kind](self.account, *option[1:-1])
listitem.connect('notify::option-value', self.on_option_changed)
if option.name is not None:
self.named_options[option.name] = listitem
self.add(listitem)
def get_option(self, name):
return self.named_options[name]
def update_states(self):
values = []
values.append((None, None))
for row in self.get_children():
name = row.get_child().name
if name is None:
continue
value = row.get_child().get_property('option-value')
values.append((name, value))
for name, value in values:
for row in self.get_children():
row.get_child().set_activatable(name, value)
def on_option_changed(self, widget, *args):
value = widget.get_property('option-value')
for row in self.get_children():
row.get_child().set_activatable(widget.name, value)
class GenericOption(Gtk.Grid):
def __init__(self, account, label, type_, value,
name, callback, data, desc, enabledif):
Gtk.Grid.__init__(self)
self.set_column_spacing(12)
self.set_size_request(-1, 25)
self.callback = callback
self.type_ = type_
self.value = value
self.data = data
self.label = label
self.account = account
self.name = name
self.enabledif = enabledif
self.option_value = self.get_value()
description_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=0)
description_box.set_valign(Gtk.Align.CENTER)
optiontext = Gtk.Label(label=label)
optiontext.set_hexpand(True)
optiontext.set_halign(Gtk.Align.START)
optiontext.set_valign(Gtk.Align.CENTER)
optiontext.set_vexpand(True)
description_box.add(optiontext)
if desc is not None:
description = Gtk.Label(label=desc)
description.set_name('SubDescription')
description.set_hexpand(True)
description.set_halign(Gtk.Align.START)
description.set_valign(Gtk.Align.CENTER)
description_box.add(description)
self.add(description_box)
self.option_box = Gtk.Box(spacing=6)
self.option_box.set_size_request(200, -1)
self.option_box.set_valign(Gtk.Align.CENTER)
self.option_box.set_name('GenericOptionBox')
self.add(self.option_box)
def do_get_property(self, prop):
if prop.name == 'option-value':
return self.option_value
else:
raise AttributeError('unknown property %s' % prop.name)
def do_set_property(self, prop, value):
if prop.name == 'option-value':
self.option_value = value
else:
raise AttributeError('unknown property %s' % prop.name)
def get_value(self):
return self.__get_value(self.type_, self.value, self.account)
@staticmethod
def __get_value(type_, value, account):
if value is None:
return
if type_ == OptionType.BOOL:
return value
elif type_ == OptionType.CONFIG:
return app.config.get(value)
elif type_ == OptionType.ACCOUNT_CONFIG:
if value == 'password':
return passwords.get_password(account)
elif value == 'no_log_for':
no_log = app.config.get_per(
'accounts', account, 'no_log_for').split()
return account not in no_log
else:
return app.config.get_per('accounts', account, value)
elif type_ == OptionType.ACTION:
if value.startswith('-'):
return account + value
return value
else:
raise ValueError('Wrong OptionType?')
def set_value(self, state):
if self.type_ == OptionType.CONFIG:
app.config.set(self.value, state)
if self.type_ == OptionType.ACCOUNT_CONFIG:
if self.value == 'password':
passwords.save_password(self.account, state)
if self.value == 'no_log_for':
self.set_no_log_for(self.account, state)
else:
app.config.set_per('accounts', self.account, self.value, state)
if self.callback is not None:
self.callback(state, self.data)
self.set_property('option-value', state)
@staticmethod
def set_no_log_for(account, jid):
no_log = app.config.get_per('accounts', account, 'no_log_for').split()
if jid and account in no_log:
no_log.remove(account)
elif not jid and account not in no_log:
no_log.append(account)
app.config.set_per('accounts', account, 'no_log_for', ' '.join(no_log))
def on_row_activated(self):
raise NotImplementedError
def set_activatable(self, name, value):
if self.enabledif is None or self.enabledif[0] != name:
return
activatable = (name, value) == self.enabledif
self.get_parent().set_activatable(activatable)
self.set_sensitive(activatable)
class SwitchOption(GenericOption):
__gproperties__ = {
"option-value": (bool, 'Switch Value', '', False,
GObject.ParamFlags.READWRITE),}
def __init__(self, *args):
GenericOption.__init__(self, *args)
self.switch = Gtk.Switch()
self.switch.set_active(self.option_value)
self.switch.connect('notify::active', self.on_switch)
self.switch.set_hexpand(True)
self.switch.set_halign(Gtk.Align.END)
self.switch.set_valign(Gtk.Align.CENTER)
self.option_box.add(self.switch)
self.show_all()
def on_row_activated(self):
state = self.switch.get_active()
self.switch.set_active(not state)
def on_switch(self, switch, *args):
value = switch.get_active()
self.set_value(value)
class EntryOption(GenericOption):
__gproperties__ = {
"option-value": (str, 'Entry Value', '', '',
GObject.ParamFlags.READWRITE),}
def __init__(self, *args):
GenericOption.__init__(self, *args)
self.entry = Gtk.Entry()
self.entry.set_text(str(self.option_value))
self.entry.connect('notify::text', self.on_text_change)
self.entry.set_valign(Gtk.Align.CENTER)
if self.value == 'password':
self.entry.set_invisible_char('*')
self.entry.set_visibility(False)
self.option_box.pack_end(self.entry, True, True, 0)
self.show_all()
def on_text_change(self, *args):
text = self.entry.get_text()
self.set_value(text)
def on_row_activated(self):
self.entry.grab_focus()
class DialogOption(GenericOption):
__gproperties__ = {
"option-value": (str, 'Dummy', '', '',
GObject.ParamFlags.READWRITE),}
def __init__(self, *args, dialog):
GenericOption.__init__(self, *args)
self.dialog = dialog
self.option_value = Gtk.Label()
self.option_value.set_text(self.get_option_value())
self.option_value.set_halign(Gtk.Align.END)
self.option_box.pack_start(self.option_value, True, True, 0)
self.show_all()
def show_dialog(self, parent):
if self.dialog:
dialog = self.dialog(self.account, parent)
dialog.connect('destroy', self.on_destroy)
def on_destroy(self, *args):
self.option_value.set_text(self.get_option_value())
def get_option_value(self):
self.option_value.hide()
return ''
def on_row_activated(self):
self.show_dialog(self.get_toplevel())
class SpinOption(GenericOption):
__gproperties__ = {
"option-value": (int, 'Priority', '', -128, 127, 0,
GObject.ParamFlags.READWRITE),}
def __init__(self, *args, range_):
GenericOption.__init__(self, *args)
lower, upper = range_
adjustment = Gtk.Adjustment(0, lower, upper, 1, 10, 0)
self.spin = Gtk.SpinButton()
self.spin.set_adjustment(adjustment)
self.spin.set_numeric(True)
self.spin.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
self.spin.set_value(self.option_value)
self.spin.set_halign(Gtk.Align.END)
self.spin.set_valign(Gtk.Align.CENTER)
self.spin.connect('notify::value', self.on_value_change)
self.option_box.pack_start(self.spin, True, True, 0)
self.show_all()
def on_row_activated(self):
self.spin.grab_focus()
def on_value_change(self, spin, *args):
value = spin.get_value_as_int()
self.set_value(value)
class FileChooserOption(GenericOption):
__gproperties__ = {
"option-value": (str, 'Certificate Path', '', '',
GObject.ParamFlags.READWRITE),}
def __init__(self, *args, filefilter):
GenericOption.__init__(self, *args)
button = Gtk.FileChooserButton(self.label, Gtk.FileChooserAction.OPEN)
button.set_halign(Gtk.Align.END)
# GTK Bug: The FileChooserButton expands without limit
# get the label and use set_max_wide_chars()
label = button.get_children()[0].get_children()[0].get_children()[1]
label.set_max_width_chars(20)
if filefilter:
name, pattern = filefilter
filter_ = Gtk.FileFilter()
filter_.set_name(name)
filter_.add_pattern(pattern)
button.add_filter(filter_)
button.set_filter(filter_)
filter_ = Gtk.FileFilter()
filter_.set_name(_('All files'))
filter_.add_pattern('*')
button.add_filter(filter_)
if self.option_value:
button.set_filename(self.option_value)
button.connect('selection-changed', self.on_select)
clear_button = gtkgui_helpers.get_image_button(
'edit-clear-all-symbolic', 'Clear File')
clear_button.connect('clicked', lambda *args: button.unselect_all())
self.option_box.pack_start(button, True, True, 0)
self.option_box.pack_start(clear_button, False, False, 0)
self.show_all()
def on_select(self, filechooser):
self.set_value(filechooser.get_filename() or '')
def on_row_activated(self):
pass
class CallbackOption(GenericOption):
__gproperties__ = {
"option-value": (str, 'Dummy', '', '',
GObject.ParamFlags.READWRITE),}
def __init__(self, *args, callback):
GenericOption.__init__(self, *args)
self.callback = callback
self.show_all()
def on_row_activated(self):
self.callback()
class ActionOption(GenericOption):
__gproperties__ = {
"option-value": (str, 'Dummy', '', '',
GObject.ParamFlags.READWRITE),}
def __init__(self, *args, action_args):
GenericOption.__init__(self, *args)
self.action = gtkgui_helpers.get_action(self.option_value)
self.variant = GLib.Variant.new_string(action_args)
self.on_enable()
self.show_all()
self.action.connect('notify::enabled', self.on_enable)
def on_enable(self, *args):
self.set_sensitive(self.action.get_enabled())
def on_row_activated(self):
self.action.activate(self.variant)
class LoginOption(DialogOption):
def __init__(self, *args, **kwargs):
DialogOption.__init__(self, *args, **kwargs)
self.option_value.set_selectable(True)
def get_option_value(self):
jid = app.get_jid_from_account(self.account)
return jid
def set_activatable(self, name, value):
DialogOption.set_activatable(self, name, value)
anonym = app.config.get_per('accounts', self.account, 'anonymous_auth')
self.get_parent().set_activatable(not anonym)
class ProxyComboOption(GenericOption):
__gproperties__ = {
"option-value": (str, 'Proxy', '', '',
GObject.ParamFlags.READWRITE),}
def __init__(self, *args):
GenericOption.__init__(self, *args)
self.combo = Gtk.ComboBoxText()
self.update_values()
self.combo.connect('changed', self.on_value_change)
self.combo.set_valign(Gtk.Align.CENTER)
button = gtkgui_helpers.get_image_button(
'preferences-system-symbolic', 'Manage Proxies')
button.set_action_name('app.manage-proxies')
button.set_valign(Gtk.Align.CENTER)
self.option_box.pack_start(self.combo, True, True, 0)
self.option_box.pack_start(button, False, True, 0)
self.show_all()
def update_values(self):
proxies = app.config.get_per('proxies')
proxies.insert(0, _('None'))
self.combo.remove_all()
for index, value in enumerate(proxies):
self.combo.insert_text(-1, value)
if value == self.option_value or index == 0:
self.combo.set_active(index)
def on_value_change(self, combo):
self.set_value(combo.get_active_text())
def on_row_activated(self):
pass
class PriorityOption(DialogOption):
def __init__(self, *args, **kwargs):
DialogOption.__init__(self, *args, **kwargs)
def get_option_value(self):
adjust = app.config.get_per(
'accounts', self.account, 'adjust_priority_with_status')
if adjust:
return _('Adjust to Status')
priority = app.config.get_per('accounts', self.account, 'priority')
return str(priority)
class CutstomHostnameOption(DialogOption):
def __init__(self, *args, **kwargs):
DialogOption.__init__(self, *args, **kwargs)
def get_option_value(self):
custom = app.config.get_per('accounts', self.account, 'use_custom_host')
return _('On') if custom else _('Off')
class ChangePasswordOption(DialogOption):
def __init__(self, *args, **kwargs):
DialogOption.__init__(self, *args, **kwargs)
def show_dialog(self, parent):
try:
self.change_dialog = dialogs.ChangePasswordDialog(
self.account, self.on_changed, parent)
except GajimGeneralException:
return
self.change_dialog.dialog.set_modal(True)
def on_changed(self, new_password):
if new_password is not None:
app.connections[self.account].change_password(new_password)
self.set_value(new_password)
def set_activatable(self, name, value):
activatable = False
if self.account in app.connections:
con = app.connections[self.account]
activatable = con.connected >= 2 and con.register_supported
self.get_parent().set_activatable(activatable)
class GPGOption(DialogOption):
def __init__(self, *args, **kwargs):
DialogOption.__init__(self, *args, **kwargs)
def show_dialog(self, parent):
secret_keys = app.connections[self.account].ask_gpg_secrete_keys()
secret_keys[_('None')] = _('None')
if not secret_keys:
dialogs.ErrorDialog(
_('Failed to get secret keys'),
_('There is no OpenPGP secret key available.'),
transient_for=parent)
return
dialog = dialogs.ChooseGPGKeyDialog(
_('OpenPGP Key Selection'), _('Choose your OpenPGP key'),
secret_keys, self.on_key_selected, transient_for=parent)
dialog.window.connect('destroy', self.on_destroy)
def on_key_selected(self, keyID):
if keyID is None:
return
keyid_new, keyname_new = keyID
keyid = app.config.get_per('accounts', self.account, 'keyid')
if keyid_new == _('None'):
if keyid == '':
return
app.config.set_per('accounts', self.account, 'keyname', '')
app.config.set_per('accounts', self.account, 'keyid', '')
else:
if keyid == keyid_new:
return
app.config.set_per(
'accounts', self.account, 'keyname', keyname_new)
app.config.set_per(
'accounts', self.account, 'keyid', keyid_new)
def get_option_value(self):
keyid = app.config.get_per('accounts', self.account, 'keyid')
keyname = app.config.get_per('accounts', self.account, 'keyname')
if keyid is not None:
return '\n'.join((keyid, keyname))
return ''
def set_activatable(self, name, value):
active = self.account in app.connections
self.get_parent().set_activatable(app.HAVE_GPG and active)

View File

@ -1052,7 +1052,8 @@ class RosterWindow:
account_name = _('Merged accounts') account_name = _('Merged accounts')
accounts = [] accounts = []
else: else:
account_name = account acclabel = app.config.get_per('accounts', account, 'account_label')
account_name = acclabel or account
accounts = [account] accounts = [account]
if account in self.collapsed_rows and \ if account in self.collapsed_rows and \
@ -3229,7 +3230,7 @@ class RosterWindow:
def on_edit_account(self, widget, account): def on_edit_account(self, widget, account):
if 'accounts' in app.interface.instances: if 'accounts' in app.interface.instances:
app.interface.instances['accounts'].window.present() app.interface.instances['accounts'].present()
else: else:
app.interface.instances['accounts'] = config.AccountsWindow() app.interface.instances['accounts'] = config.AccountsWindow()
app.interface.instances['accounts'].select_account(account) app.interface.instances['accounts'].select_account(account)