2022-10-19 20:09:31 +02:00
|
|
|
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from gi.repository import GLib
|
|
|
|
from gi.repository import Gdk
|
|
|
|
from gi.repository import Pango
|
|
|
|
|
|
|
|
from gajim.common import app
|
|
|
|
from gajim.common import passwords
|
|
|
|
from gajim.common.i18n import _
|
|
|
|
from gajim.common.i18n import Q_
|
|
|
|
|
|
|
|
from gajim import gtkgui_helpers
|
|
|
|
|
|
|
|
from .util import get_image_button
|
|
|
|
from .util import MaxWidthComboBoxText
|
|
|
|
from .util import open_window
|
|
|
|
from .const import SettingKind
|
|
|
|
from .const import SettingType
|
|
|
|
|
|
|
|
|
|
|
|
class SettingsDialog(Gtk.ApplicationWindow):
|
|
|
|
def __init__(self, parent, title, flags, settings, account,
|
|
|
|
extend=None):
|
|
|
|
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.get_style_context().add_class('settings-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 = SettingsBox(account, extend=extend)
|
|
|
|
self.listbox.set_hexpand(True)
|
|
|
|
self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
|
|
|
|
|
|
|
|
for setting in settings:
|
|
|
|
self.listbox.add_setting(setting)
|
|
|
|
self.listbox.update_states()
|
|
|
|
|
|
|
|
self.add(self.listbox)
|
|
|
|
|
|
|
|
self.show_all()
|
|
|
|
self.connect_after('key-press-event', self.on_key_press)
|
|
|
|
|
|
|
|
def on_key_press(self, _widget, event):
|
|
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
|
|
self.destroy()
|
|
|
|
|
|
|
|
def get_setting(self, name):
|
|
|
|
return self.listbox.get_setting(name)
|
|
|
|
|
|
|
|
|
|
|
|
class SettingsBox(Gtk.ListBox):
|
|
|
|
def __init__(self, account=None, jid=None, extend=None):
|
|
|
|
Gtk.ListBox.__init__(self)
|
|
|
|
self.get_style_context().add_class('settings-box')
|
|
|
|
self.account = account
|
|
|
|
self.jid = jid
|
|
|
|
self.named_settings = {}
|
|
|
|
|
|
|
|
self.map = {
|
|
|
|
SettingKind.SWITCH: SwitchSetting,
|
|
|
|
SettingKind.SPIN: SpinSetting,
|
|
|
|
SettingKind.DIALOG: DialogSetting,
|
|
|
|
SettingKind.ENTRY: EntrySetting,
|
|
|
|
SettingKind.COLOR: ColorSetting,
|
|
|
|
SettingKind.ACTION: ActionSetting,
|
|
|
|
SettingKind.LOGIN: LoginSetting,
|
|
|
|
SettingKind.FILECHOOSER: FileChooserSetting,
|
|
|
|
SettingKind.CALLBACK: CallbackSetting,
|
|
|
|
SettingKind.PRIORITY: PrioritySetting,
|
|
|
|
SettingKind.HOSTNAME: CutstomHostnameSetting,
|
|
|
|
SettingKind.CHANGEPASSWORD: ChangePasswordSetting,
|
|
|
|
SettingKind.COMBO: ComboSetting,
|
|
|
|
SettingKind.POPOVER: PopoverSetting,
|
|
|
|
SettingKind.AUTO_AWAY: CutstomAutoAwaySetting,
|
|
|
|
SettingKind.AUTO_EXTENDED_AWAY: CutstomAutoExtendedAwaySetting,
|
|
|
|
SettingKind.USE_STUN_SERVER: CustomStunServerSetting,
|
|
|
|
SettingKind.NOTIFICATIONS: NotificationsSetting,
|
|
|
|
}
|
|
|
|
|
|
|
|
if extend is not None:
|
|
|
|
for setting, callback in extend:
|
|
|
|
self.map[setting] = callback
|
|
|
|
|
|
|
|
self.connect('row-activated', self.on_row_activated)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def on_row_activated(_listbox, row):
|
|
|
|
row.on_row_activated()
|
|
|
|
|
|
|
|
def add_setting(self, setting):
|
|
|
|
if not isinstance(setting, Gtk.ListBoxRow):
|
|
|
|
if setting.props is not None:
|
|
|
|
listitem = self.map[setting.kind](self.account,
|
|
|
|
self.jid,
|
|
|
|
*setting[1:-1],
|
|
|
|
**setting.props)
|
|
|
|
else:
|
|
|
|
listitem = self.map[setting.kind](self.account,
|
|
|
|
self.jid,
|
|
|
|
*setting[1:-1])
|
|
|
|
|
|
|
|
if setting.name is not None:
|
|
|
|
self.named_settings[setting.name] = listitem
|
|
|
|
self.add(listitem)
|
|
|
|
|
|
|
|
def get_setting(self, name):
|
|
|
|
return self.named_settings[name]
|
|
|
|
|
|
|
|
def update_states(self):
|
|
|
|
for row in self.get_children():
|
|
|
|
row.update_activatable()
|
|
|
|
|
|
|
|
|
|
|
|
class GenericSetting(Gtk.ListBoxRow):
|
|
|
|
def __init__(self,
|
|
|
|
account,
|
|
|
|
jid,
|
|
|
|
label,
|
|
|
|
type_,
|
|
|
|
value,
|
|
|
|
name,
|
|
|
|
callback,
|
|
|
|
data,
|
|
|
|
desc,
|
|
|
|
bind,
|
|
|
|
inverted,
|
|
|
|
enabled_func):
|
|
|
|
|
|
|
|
Gtk.ListBoxRow.__init__(self)
|
|
|
|
self._grid = Gtk.Grid()
|
|
|
|
self._grid.set_size_request(-1, 30)
|
|
|
|
self._grid.set_column_spacing(12)
|
|
|
|
|
|
|
|
self.callback = callback
|
|
|
|
self.type_ = type_
|
|
|
|
self.value = value
|
|
|
|
self.data = data
|
|
|
|
self.label = label
|
|
|
|
self.account = account
|
|
|
|
self.jid = jid
|
|
|
|
self.name = name
|
|
|
|
self.bind = bind
|
|
|
|
self.inverted = inverted
|
|
|
|
self.enabled_func = enabled_func
|
|
|
|
self.setting_value = self.get_value()
|
|
|
|
|
|
|
|
description_box = Gtk.Box(
|
|
|
|
orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
|
|
|
description_box.set_valign(Gtk.Align.CENTER)
|
|
|
|
|
|
|
|
settingtext = Gtk.Label(label=label)
|
|
|
|
settingtext.set_hexpand(True)
|
|
|
|
settingtext.set_halign(Gtk.Align.START)
|
|
|
|
settingtext.set_valign(Gtk.Align.CENTER)
|
|
|
|
settingtext.set_vexpand(True)
|
|
|
|
description_box.add(settingtext)
|
|
|
|
|
|
|
|
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.set_xalign(0)
|
|
|
|
description.set_line_wrap(True)
|
|
|
|
description.set_line_wrap_mode(Pango.WrapMode.WORD)
|
|
|
|
description.set_max_width_chars(50)
|
|
|
|
description_box.add(description)
|
|
|
|
|
|
|
|
self._grid.add(description_box)
|
|
|
|
|
|
|
|
self.setting_box = Gtk.Box(spacing=12)
|
|
|
|
self.setting_box.set_size_request(200, -1)
|
|
|
|
self.setting_box.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.setting_box.set_name('GenericSettingBox')
|
|
|
|
self._grid.add(self.setting_box)
|
|
|
|
self.add(self._grid)
|
|
|
|
|
|
|
|
self._bind_sensitive_state()
|
|
|
|
|
|
|
|
def _bind_sensitive_state(self):
|
|
|
|
if self.bind is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
bind_setting_type, setting, account, jid = self._parse_bind()
|
|
|
|
|
|
|
|
app.settings.bind_signal(setting,
|
|
|
|
self,
|
|
|
|
'set_sensitive',
|
|
|
|
account=account,
|
|
|
|
jid=jid,
|
|
|
|
inverted=self.inverted)
|
|
|
|
|
|
|
|
if bind_setting_type == SettingType.CONTACT:
|
|
|
|
value = app.settings.get_contact_setting(account, jid, setting)
|
|
|
|
|
|
|
|
elif bind_setting_type == SettingType.GROUP_CHAT:
|
|
|
|
value = app.settings.get_group_chat_setting(account, jid, setting)
|
|
|
|
|
|
|
|
elif bind_setting_type == SettingType.ACCOUNT_CONFIG:
|
|
|
|
value = app.settings.get_account_setting(account, setting)
|
|
|
|
|
|
|
|
else:
|
|
|
|
value = app.settings.get(setting)
|
|
|
|
|
|
|
|
if self.inverted:
|
|
|
|
value = not value
|
|
|
|
self.set_sensitive(value)
|
|
|
|
|
|
|
|
def _parse_bind(self):
|
|
|
|
if '::' not in self.bind:
|
|
|
|
return SettingType.CONFIG, self.bind, None, None
|
|
|
|
|
|
|
|
bind_setting_type, setting = self.bind.split('::')
|
|
|
|
if bind_setting_type == 'account':
|
|
|
|
return SettingType.ACCOUNT_CONFIG, setting, self.account, None
|
|
|
|
|
|
|
|
if bind_setting_type == 'contact':
|
|
|
|
return SettingType.CONTACT, setting, self.account, self.jid
|
|
|
|
|
|
|
|
if bind_setting_type == 'group_chat':
|
|
|
|
return SettingType.GROUP_CHAT, setting, self.account, self.jid
|
|
|
|
raise ValueError(f'Invalid bind argument: {self.bind}')
|
|
|
|
|
|
|
|
def get_value(self):
|
|
|
|
return self.__get_value(self.type_,
|
|
|
|
self.value,
|
|
|
|
self.account,
|
|
|
|
self.jid)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __get_value(type_, value, account, jid):
|
|
|
|
if value is None:
|
|
|
|
return None
|
|
|
|
if type_ == SettingType.VALUE:
|
|
|
|
return value
|
|
|
|
|
|
|
|
if type_ == SettingType.CONTACT:
|
|
|
|
return app.settings.get_contact_setting(account, jid, value)
|
|
|
|
|
|
|
|
if type_ == SettingType.GROUP_CHAT:
|
|
|
|
return app.settings.get_group_chat_setting(
|
|
|
|
account, jid, value)
|
|
|
|
|
|
|
|
if type_ == SettingType.CONFIG:
|
|
|
|
return app.settings.get(value)
|
|
|
|
|
|
|
|
if type_ == SettingType.ACCOUNT_CONFIG:
|
|
|
|
if value == 'password':
|
|
|
|
return passwords.get_password(account)
|
|
|
|
if value == 'no_log_for':
|
|
|
|
no_log = app.settings.get_account_setting(
|
|
|
|
account, 'no_log_for').split()
|
|
|
|
return account not in no_log
|
|
|
|
return app.settings.get_account_setting(account, value)
|
|
|
|
|
|
|
|
if type_ == SettingType.ACTION:
|
|
|
|
if value.startswith('-'):
|
|
|
|
return account + value
|
|
|
|
return value
|
|
|
|
|
|
|
|
raise ValueError('Wrong SettingType?')
|
|
|
|
|
|
|
|
def set_value(self, state):
|
|
|
|
if self.type_ == SettingType.CONFIG:
|
|
|
|
app.settings.set(self.value, state)
|
|
|
|
|
|
|
|
elif self.type_ == SettingType.ACCOUNT_CONFIG:
|
|
|
|
if self.value == 'password':
|
2022-10-19 20:54:49 +02:00
|
|
|
passwords.save_password(self.account, state,
|
|
|
|
# guessing - for xdg:schema="org.qt.keychain"
|
|
|
|
user=self.jid,
|
|
|
|
)
|
2022-10-19 20:09:31 +02:00
|
|
|
if self.value == 'no_log_for':
|
|
|
|
self.set_no_log_for(self.account, state)
|
|
|
|
else:
|
|
|
|
app.settings.set_account_setting(self.account,
|
|
|
|
self.value,
|
|
|
|
state)
|
|
|
|
|
|
|
|
elif self.type_ == SettingType.CONTACT:
|
|
|
|
app.settings.set_contact_setting(
|
|
|
|
self.account, self.jid, self.value, state)
|
|
|
|
|
|
|
|
elif self.type_ == SettingType.GROUP_CHAT:
|
|
|
|
app.settings.set_group_chat_setting(
|
|
|
|
self.account, self.jid, self.value, state)
|
|
|
|
|
|
|
|
if self.callback is not None:
|
|
|
|
self.callback(state, self.data)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def set_no_log_for(account, state):
|
|
|
|
no_log = app.settings.get_account_setting(account, 'no_log_for').split()
|
|
|
|
if state and account in no_log:
|
|
|
|
no_log.remove(account)
|
|
|
|
elif not state and account not in no_log:
|
|
|
|
no_log.append(account)
|
|
|
|
app.settings.set_account_setting(account,
|
|
|
|
'no_log_for',
|
|
|
|
' '.join(no_log))
|
|
|
|
|
|
|
|
def on_row_activated(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def update_activatable(self):
|
|
|
|
if self.enabled_func is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
enabled_func_value = self.enabled_func()
|
|
|
|
self.set_activatable(enabled_func_value)
|
|
|
|
self.set_sensitive(enabled_func_value)
|
|
|
|
|
|
|
|
def _add_action_button(self, kwargs):
|
|
|
|
icon_name = kwargs.get('button-icon-name')
|
|
|
|
button_text = kwargs.get('button-text')
|
|
|
|
tooltip_text = kwargs.get('button-tooltip') or ''
|
|
|
|
style = kwargs.get('button-style')
|
|
|
|
|
|
|
|
if icon_name is not None:
|
|
|
|
button = Gtk.Button.new_from_icon_name(icon_name, Gtk.IconSize.MENU)
|
|
|
|
|
|
|
|
elif button_text is not None:
|
|
|
|
button = Gtk.Button(label=button_text)
|
|
|
|
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
if style is not None:
|
|
|
|
for css_class in style.split(' '):
|
|
|
|
button.get_style_context().add_class(css_class)
|
|
|
|
|
|
|
|
button.connect('clicked', kwargs['button-callback'])
|
|
|
|
button.set_tooltip_text(tooltip_text)
|
|
|
|
self.setting_box.add(button)
|
|
|
|
|
|
|
|
|
|
|
|
class SwitchSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
self.switch = Gtk.Switch()
|
|
|
|
if self.type_ == SettingType.ACTION:
|
|
|
|
self.switch.set_action_name('app.%s' % self.setting_value)
|
|
|
|
state = app.app.get_action_state(self.setting_value)
|
|
|
|
self.switch.set_active(state.get_boolean())
|
|
|
|
else:
|
|
|
|
self.switch.set_active(self.setting_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._switch_state_label = Gtk.Label()
|
|
|
|
self._switch_state_label.set_xalign(1)
|
|
|
|
self._switch_state_label.set_valign(Gtk.Align.CENTER)
|
|
|
|
self._set_label(self.setting_value)
|
|
|
|
|
|
|
|
box = Gtk.Box(spacing=12)
|
|
|
|
box.set_halign(Gtk.Align.END)
|
|
|
|
box.add(self._switch_state_label)
|
|
|
|
box.add(self.switch)
|
|
|
|
self.setting_box.add(box)
|
|
|
|
|
|
|
|
self._add_action_button(kwargs)
|
|
|
|
|
|
|
|
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)
|
|
|
|
self._set_label(value)
|
|
|
|
|
|
|
|
def _set_label(self, active):
|
|
|
|
text = Q_('?switch:On') if active else Q_('?switch:Off')
|
|
|
|
self._switch_state_label.set_text(text)
|
|
|
|
|
|
|
|
|
|
|
|
class EntrySetting(GenericSetting):
|
|
|
|
def __init__(self, *args):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
self.entry = Gtk.Entry()
|
|
|
|
self.entry.set_text(str(self.setting_value))
|
|
|
|
self.entry.connect('notify::text', self.on_text_change)
|
|
|
|
self.entry.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.entry.set_alignment(1)
|
|
|
|
|
|
|
|
if self.value == 'password':
|
|
|
|
self.entry.set_invisible_char('*')
|
|
|
|
self.entry.set_visibility(False)
|
|
|
|
|
|
|
|
self.setting_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 ColorSetting(GenericSetting):
|
|
|
|
def __init__(self, *args):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
rgba = Gdk.RGBA()
|
|
|
|
rgba.parse(self.setting_value)
|
|
|
|
self.color_button = Gtk.ColorButton()
|
|
|
|
self.color_button.set_rgba(rgba)
|
|
|
|
self.color_button.connect('color-set', self.on_color_set)
|
|
|
|
self.color_button.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.color_button.set_halign(Gtk.Align.END)
|
|
|
|
|
|
|
|
self.setting_box.pack_end(self.color_button, True, True, 0)
|
|
|
|
|
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
def on_color_set(self, button):
|
|
|
|
rgba = button.get_rgba()
|
|
|
|
self.set_value(rgba.to_string())
|
|
|
|
app.css_config.refresh()
|
|
|
|
|
|
|
|
def on_row_activated(self):
|
|
|
|
self.color_button.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
class DialogSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, dialog):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
self.dialog = dialog
|
|
|
|
|
|
|
|
self.setting_value = Gtk.Label()
|
|
|
|
self.setting_value.set_text(self.get_setting_value())
|
|
|
|
self.setting_value.set_halign(Gtk.Align.END)
|
|
|
|
self.setting_box.pack_start(self.setting_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.setting_value.set_text(self.get_setting_value())
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
self.setting_value.hide()
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def on_row_activated(self):
|
|
|
|
self.show_dialog(self.get_toplevel())
|
|
|
|
|
|
|
|
|
|
|
|
class SpinSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, range_):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
lower, upper = range_
|
|
|
|
adjustment = Gtk.Adjustment(value=0,
|
|
|
|
lower=lower,
|
|
|
|
upper=upper,
|
|
|
|
step_increment=1,
|
|
|
|
page_increment=10,
|
|
|
|
page_size=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.setting_value)
|
|
|
|
self.spin.set_halign(Gtk.Align.FILL)
|
|
|
|
self.spin.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.spin.connect('notify::value', self.on_value_change)
|
|
|
|
|
|
|
|
self.setting_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 FileChooserSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, filefilter):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
button = Gtk.FileChooserButton(title=self.label,
|
|
|
|
action=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.setting_value:
|
|
|
|
button.set_filename(self.setting_value)
|
|
|
|
button.connect('selection-changed', self.on_select)
|
|
|
|
|
|
|
|
clear_button = get_image_button(
|
|
|
|
'edit-clear-all-symbolic', _('Clear File'))
|
|
|
|
clear_button.connect('clicked', lambda *args: button.unselect_all())
|
|
|
|
self.setting_box.pack_start(button, True, True, 0)
|
|
|
|
self.setting_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 CallbackSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, callback):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
self.callback = callback
|
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
def on_row_activated(self):
|
|
|
|
self.callback()
|
|
|
|
|
|
|
|
|
|
|
|
class ActionSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, account):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
action_name = '%s%s' % (account, self.value)
|
|
|
|
self.action = gtkgui_helpers.get_action(action_name)
|
|
|
|
self.variant = GLib.Variant.new_string(account)
|
|
|
|
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 LoginSetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
self.setting_value.set_selectable(True)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
jid = app.get_jid_from_account(self.account)
|
|
|
|
return jid
|
|
|
|
|
|
|
|
|
|
|
|
class PopoverSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, entries, **kwargs):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
self._entries = self._convert_to_dict(entries)
|
|
|
|
|
|
|
|
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
|
|
|
spacing=12)
|
|
|
|
box.set_halign(Gtk.Align.END)
|
|
|
|
box.set_hexpand(True)
|
|
|
|
|
|
|
|
self._default_text = kwargs.get('default-text')
|
|
|
|
|
|
|
|
self._current_label = Gtk.Label()
|
|
|
|
self._current_label.set_valign(Gtk.Align.CENTER)
|
|
|
|
image = Gtk.Image.new_from_icon_name('pan-down-symbolic',
|
|
|
|
Gtk.IconSize.MENU)
|
|
|
|
image.set_valign(Gtk.Align.CENTER)
|
|
|
|
|
|
|
|
box.add(self._current_label)
|
|
|
|
box.add(image)
|
|
|
|
|
|
|
|
self._menu_listbox = Gtk.ListBox()
|
|
|
|
self._menu_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
|
|
|
|
self._add_menu_entries()
|
|
|
|
self._menu_listbox.connect('row-activated',
|
|
|
|
self._on_menu_row_activated)
|
|
|
|
|
|
|
|
scrolled_window = Gtk.ScrolledWindow()
|
|
|
|
scrolled_window.set_propagate_natural_height(True)
|
|
|
|
scrolled_window.set_propagate_natural_width(True)
|
|
|
|
scrolled_window.set_max_content_height(400)
|
|
|
|
scrolled_window.set_policy(Gtk.PolicyType.NEVER,
|
|
|
|
Gtk.PolicyType.AUTOMATIC)
|
|
|
|
scrolled_window.add(self._menu_listbox)
|
|
|
|
scrolled_window.show_all()
|
|
|
|
|
|
|
|
self._popover = Gtk.Popover()
|
|
|
|
self._popover.get_style_context().add_class('combo')
|
|
|
|
self._popover.set_relative_to(image)
|
|
|
|
self._popover.set_position(Gtk.PositionType.BOTTOM)
|
|
|
|
self._popover.add(scrolled_window)
|
|
|
|
|
|
|
|
self.setting_box.add(box)
|
|
|
|
|
|
|
|
self._add_action_button(kwargs)
|
|
|
|
|
|
|
|
text = self._entries.get(self.setting_value, self._default_text or '')
|
|
|
|
self._current_label.set_text(text)
|
|
|
|
|
|
|
|
app.settings.connect_signal(self.value,
|
|
|
|
self._on_setting_changed,
|
|
|
|
account=self.account,
|
|
|
|
jid=self.jid)
|
|
|
|
|
|
|
|
self.connect('destroy', self._on_destroy)
|
|
|
|
|
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _convert_to_dict(entries):
|
|
|
|
if isinstance(entries, list):
|
|
|
|
entries = {key: key for key in entries}
|
|
|
|
return entries
|
|
|
|
|
|
|
|
def _on_setting_changed(self, value, *args):
|
|
|
|
text = self._entries.get(value)
|
|
|
|
if text is None:
|
|
|
|
text = self._default_text or ''
|
|
|
|
|
|
|
|
self._current_label.set_text(text)
|
|
|
|
|
|
|
|
def _add_menu_entries(self):
|
|
|
|
if self._default_text is not None:
|
|
|
|
self._menu_listbox.add(PopoverRow(self._default_text, ''))
|
|
|
|
|
|
|
|
for value, label in self._entries.items():
|
|
|
|
self._menu_listbox.add(PopoverRow(label, value))
|
|
|
|
|
|
|
|
self._menu_listbox.show_all()
|
|
|
|
|
|
|
|
def _on_menu_row_activated(self, listbox, row):
|
|
|
|
listbox.unselect_all()
|
|
|
|
self._popover.popdown()
|
|
|
|
|
|
|
|
self.set_value(row.value)
|
|
|
|
|
|
|
|
def on_row_activated(self):
|
|
|
|
self._popover.popup()
|
|
|
|
|
|
|
|
def update_entries(self, entries):
|
|
|
|
self._entries = self._convert_to_dict(entries)
|
|
|
|
self._menu_listbox.foreach(self._menu_listbox.remove)
|
|
|
|
self._add_menu_entries()
|
|
|
|
|
|
|
|
def _on_destroy(self, *args):
|
|
|
|
app.settings.disconnect_signals(self)
|
|
|
|
|
|
|
|
|
|
|
|
class PopoverRow(Gtk.ListBoxRow):
|
|
|
|
def __init__(self, label, value):
|
|
|
|
Gtk.ListBoxRow.__init__(self)
|
|
|
|
self.label = label
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
label = Gtk.Label(label=label)
|
|
|
|
label.set_xalign(0)
|
|
|
|
self.add(label)
|
|
|
|
|
|
|
|
|
|
|
|
class ComboSetting(GenericSetting):
|
|
|
|
def __init__(self, *args, combo_items):
|
|
|
|
GenericSetting.__init__(self, *args)
|
|
|
|
|
|
|
|
self.combo = MaxWidthComboBoxText()
|
|
|
|
self.combo.set_valign(Gtk.Align.CENTER)
|
|
|
|
|
|
|
|
for index, value in enumerate(combo_items):
|
|
|
|
if isinstance(value, tuple):
|
|
|
|
value, label = value
|
|
|
|
self.combo.append(value, _(label))
|
|
|
|
else:
|
|
|
|
self.combo.append(value, value)
|
|
|
|
if value == self.setting_value or index == 0:
|
|
|
|
self.combo.set_active(index)
|
|
|
|
|
|
|
|
self.combo.connect('changed', self.on_value_change)
|
|
|
|
|
|
|
|
self.setting_box.pack_start(self.combo, True, True, 0)
|
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
def on_value_change(self, combo):
|
|
|
|
self.set_value(combo.get_active_id())
|
|
|
|
|
|
|
|
def on_row_activated(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class PrioritySetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
adjust = app.settings.get_account_setting(
|
|
|
|
self.account, 'adjust_priority_with_status')
|
|
|
|
if adjust:
|
|
|
|
return _('Adjust to Status')
|
|
|
|
|
|
|
|
priority = app.settings.get_account_setting(self.account, 'priority')
|
|
|
|
return str(priority)
|
|
|
|
|
|
|
|
|
|
|
|
class CutstomHostnameSetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
custom = app.settings.get_account_setting(self.account,
|
|
|
|
'use_custom_host')
|
|
|
|
return Q_('?switch:On') if custom else Q_('?switch:Off')
|
|
|
|
|
|
|
|
|
|
|
|
class ChangePasswordSetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def show_dialog(self, parent):
|
|
|
|
parent.destroy()
|
|
|
|
open_window('ChangePassword', account=self.account)
|
|
|
|
|
|
|
|
def update_activatable(self):
|
|
|
|
activatable = False
|
|
|
|
if self.account in app.connections:
|
|
|
|
con = app.connections[self.account]
|
|
|
|
activatable = (con.state.is_available and
|
|
|
|
con.get_module('Register').supported)
|
|
|
|
self.set_activatable(activatable)
|
|
|
|
|
|
|
|
|
|
|
|
class CutstomAutoAwaySetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
value = app.settings.get('autoaway')
|
|
|
|
return Q_('?switch:On') if value else Q_('?switch:Off')
|
|
|
|
|
|
|
|
|
|
|
|
class CutstomAutoExtendedAwaySetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
value = app.settings.get('autoxa')
|
|
|
|
return Q_('?switch:On') if value else Q_('?switch:Off')
|
|
|
|
|
|
|
|
|
|
|
|
class CustomStunServerSetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
value = app.settings.get('use_stun_server')
|
|
|
|
return Q_('?switch:On') if value else Q_('?switch:Off')
|
|
|
|
|
|
|
|
|
|
|
|
class NotificationsSetting(DialogSetting):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
DialogSetting.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_setting_value(self):
|
|
|
|
value = app.settings.get('show_notifications')
|
|
|
|
return Q_('?switch:On') if value else Q_('?switch:Off')
|