receiver: implement KEYBOARD_DISABLE_KEYS feature
(the UI needs some improvement)
This commit is contained in:
parent
c99f470dd5
commit
ef54a750dc
|
|
@ -54,11 +54,12 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None,
|
||||||
|
|
||||||
if protocol is not None:
|
if protocol is not None:
|
||||||
# ? 2.0 devices should not have any registers
|
# ? 2.0 devices should not have any registers
|
||||||
|
_kind = lambda s : s._rw.kind if hasattr(s, '_rw') else s._rw_kind
|
||||||
if protocol < 2.0:
|
if protocol < 2.0:
|
||||||
assert settings is None or all(s._rw.kind == 1 for s in settings)
|
assert settings is None or all(_kind(s) == 1 for s in settings)
|
||||||
else:
|
else:
|
||||||
assert registers is None
|
assert registers is None
|
||||||
assert settings is None or all(s._rw.kind == 2 for s in settings)
|
assert settings is None or all(_kind(s) == 2 for s in settings)
|
||||||
|
|
||||||
if wpid:
|
if wpid:
|
||||||
for w in wpid if isinstance(wpid, tuple) else (wpid, ):
|
for w in wpid if isinstance(wpid, tuple) else (wpid, ):
|
||||||
|
|
@ -183,8 +184,10 @@ _D('Wireless Touch Keyboard K400', protocol=2.0, wpid=('400E', '4024'),
|
||||||
)
|
)
|
||||||
_D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D',
|
_D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D',
|
||||||
settings=[
|
settings=[
|
||||||
_FS.new_fn_swap()
|
_FS.new_fn_swap(),
|
||||||
],
|
_FS.reprogrammable_keys(),
|
||||||
|
_FS.disable_keyboard_keys(),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
_D('Wireless Keyboard K520', protocol=1.0, wpid='2011',
|
_D('Wireless Keyboard K520', protocol=1.0, wpid='2011',
|
||||||
registers=(_R.battery_status, ),
|
registers=(_R.battery_status, ),
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ FEATURE = _NamedInts(
|
||||||
LOCK_KEY_STATE=0x4220,
|
LOCK_KEY_STATE=0x4220,
|
||||||
SOLAR_DASHBOARD=0x4301,
|
SOLAR_DASHBOARD=0x4301,
|
||||||
KEYBOARD_LAYOUT=0x4520,
|
KEYBOARD_LAYOUT=0x4520,
|
||||||
KEYBOARD_DISABLE=0x4521,
|
KEYBOARD_DISABLE_KEYS=0x4521,
|
||||||
KEYBOARD_DISABLE_BY_USAGE=0x4522,
|
KEYBOARD_DISABLE_BY_USAGE=0x4522,
|
||||||
DUALPLATFORM=0x4530,
|
DUALPLATFORM=0x4530,
|
||||||
MULTIPLATFORM=0x4531,
|
MULTIPLATFORM=0x4531,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ from .common import (
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A)
|
KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A, multiple_toggle=0x10)
|
||||||
|
|
||||||
class Setting(object):
|
class Setting(object):
|
||||||
"""A setting descriptor.
|
"""A setting descriptor.
|
||||||
|
|
@ -290,6 +290,120 @@ class Settings(Setting):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class BitFieldSetting(Setting):
|
||||||
|
"""A setting descriptor for a set of choices represented by one bit each, being a map from options to booleans.
|
||||||
|
Needs to be instantiated for each specific device."""
|
||||||
|
|
||||||
|
def read(self, cached=True):
|
||||||
|
assert hasattr(self, '_value')
|
||||||
|
assert hasattr(self, '_device')
|
||||||
|
|
||||||
|
if _log.isEnabledFor(_DEBUG):
|
||||||
|
_log.debug("%s: settings read %r from %s", self.name, self._value, self._device)
|
||||||
|
|
||||||
|
if self._value is None and getattr(self._device,'persister',None):
|
||||||
|
# We haven't read a value from the device yet,
|
||||||
|
# maybe we have something in the configuration.
|
||||||
|
self._value = self._device.persister.get(self.name)
|
||||||
|
|
||||||
|
if cached and self._value is not None:
|
||||||
|
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||||
|
# If this is a new device (or a new setting for an old device),
|
||||||
|
# make sure to save its current value for the next time.
|
||||||
|
self._device.persister[self.name] = self._value
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
if self._device.online:
|
||||||
|
reply_map = dict()
|
||||||
|
reply = self._rw.read(self._device)
|
||||||
|
if reply:
|
||||||
|
# keys are ints, because that is what the device uses,
|
||||||
|
# encoded into strings because JSON requires strings as keys
|
||||||
|
reply_map = self._validator.validate_read(reply)
|
||||||
|
self._value = reply_map
|
||||||
|
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||||
|
# Don't update the persister if it already has a value,
|
||||||
|
# otherwise the first read might overwrite the value we wanted.
|
||||||
|
self._device.persister[self.name] = self._value
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def read_key(self, key, cached=True):
|
||||||
|
assert hasattr(self, '_value')
|
||||||
|
assert hasattr(self, '_device')
|
||||||
|
assert key is not None
|
||||||
|
|
||||||
|
if _log.isEnabledFor(_DEBUG):
|
||||||
|
_log.debug("%s: settings read %r key %r from %s", self.name, self._value, key, self._device)
|
||||||
|
|
||||||
|
if self._value is None and getattr(self._device,'persister',None):
|
||||||
|
self._value = self._device.persister.get(self.name)
|
||||||
|
|
||||||
|
if cached and self._value is not None:
|
||||||
|
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||||
|
self._device.persister[self.name] = self._value
|
||||||
|
return self._value[str(int(key))]
|
||||||
|
|
||||||
|
if self._device.online:
|
||||||
|
reply = self._rw.read(self._device, key)
|
||||||
|
if reply:
|
||||||
|
self._value = self._validator.validate_read(reply)
|
||||||
|
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||||
|
self._device.persister[self.name] = self._value
|
||||||
|
return self._value[str(int(key))]
|
||||||
|
|
||||||
|
def write(self, map):
|
||||||
|
assert hasattr(self, '_value')
|
||||||
|
assert hasattr(self, '_device')
|
||||||
|
assert map is not None
|
||||||
|
|
||||||
|
if _log.isEnabledFor(_DEBUG):
|
||||||
|
_log.debug("%s: settings write %r to %s", self.name, map, self._device)
|
||||||
|
|
||||||
|
if self._device.online:
|
||||||
|
# Remember the value we're trying to set, even if the write fails.
|
||||||
|
# This way even if the device is offline or some other error occurs,
|
||||||
|
# the last value we've tried to write is remembered in the configuration.
|
||||||
|
self._value = map
|
||||||
|
if self._device.persister:
|
||||||
|
self._device.persister[self.name] = map
|
||||||
|
data_bytes = self._validator.prepare_write(self._value)
|
||||||
|
if data_bytes is not None:
|
||||||
|
if _log.isEnabledFor(_DEBUG):
|
||||||
|
_log.debug("%s: settings prepare map write(%s) => %r", self.name, self._value, data_bytes)
|
||||||
|
reply = self._rw.write(self._device, data_bytes)
|
||||||
|
if not reply:
|
||||||
|
return None
|
||||||
|
return map
|
||||||
|
|
||||||
|
def write_key_value(self, key, value):
|
||||||
|
assert hasattr(self, '_value')
|
||||||
|
assert hasattr(self, '_device')
|
||||||
|
assert key is not None
|
||||||
|
assert value is not None
|
||||||
|
|
||||||
|
if _log.isEnabledFor(_DEBUG):
|
||||||
|
_log.debug("%s: settings write key %r value %r to %s", self.name, key, value, self._device)
|
||||||
|
|
||||||
|
if self._device.online:
|
||||||
|
# Remember the value we're trying to set, even if the write fails.
|
||||||
|
# This way even if the device is offline or some other error occurs,
|
||||||
|
# the last value we've tried to write is remembered in the configuration.
|
||||||
|
value = bool(value)
|
||||||
|
self._value[str(key)] = value
|
||||||
|
if self._device.persister:
|
||||||
|
self._device.persister[self.name] = self._value
|
||||||
|
|
||||||
|
data_bytes = self._validator.prepare_write(self._value)
|
||||||
|
if data_bytes is not None:
|
||||||
|
if _log.isEnabledFor(_DEBUG):
|
||||||
|
_log.debug("%s: settings prepare key value write(%s,%s) => %r", self.name, key, str(value), data_bytes)
|
||||||
|
reply = self._rw.write(self._device, data_bytes)
|
||||||
|
if not reply:
|
||||||
|
# tell whomever is calling that the write failed
|
||||||
|
return None
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
#
|
#
|
||||||
# read/write low-level operators
|
# read/write low-level operators
|
||||||
#
|
#
|
||||||
|
|
@ -469,6 +583,38 @@ class BooleanValidator(object):
|
||||||
return to_write
|
return to_write
|
||||||
|
|
||||||
|
|
||||||
|
class BitFieldValidator(object):
|
||||||
|
__slots__ = ('byte_count', 'options')
|
||||||
|
|
||||||
|
kind = KIND.multiple_toggle
|
||||||
|
|
||||||
|
def __init__(self, options, byte_count=None):
|
||||||
|
assert(isinstance(options, list))
|
||||||
|
self.options = options
|
||||||
|
self.byte_count = (max(x.bit_length() for x in options) + 7) // 8
|
||||||
|
if byte_count:
|
||||||
|
assert(isinstance(byte_count, int) and byte_count >= self.byte_count)
|
||||||
|
self.byte_count = byte_count
|
||||||
|
|
||||||
|
def validate_read(self, reply_bytes):
|
||||||
|
r = _bytes2int(reply_bytes[:self.byte_count])
|
||||||
|
value = {str(int(k)) : False for k in self.options}
|
||||||
|
m = 1
|
||||||
|
for i in range(8 * self.byte_count):
|
||||||
|
if m in self.options:
|
||||||
|
value[str(int(m))] = bool(r & m)
|
||||||
|
m <<= 1
|
||||||
|
return value
|
||||||
|
|
||||||
|
def prepare_write(self, new_value):
|
||||||
|
assert(isinstance(new_value, dict))
|
||||||
|
w = 0
|
||||||
|
for k, v in new_value.items():
|
||||||
|
if v:
|
||||||
|
w |= int(k)
|
||||||
|
return _int2bytes(w, self.byte_count)
|
||||||
|
|
||||||
|
|
||||||
class ChoicesValidator(object):
|
class ChoicesValidator(object):
|
||||||
__slots__ = ('choices', 'flag', '_bytes_count', 'needs_current_value')
|
__slots__ = ('choices', 'flag', '_bytes_count', 'needs_current_value')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,13 @@ from .common import (
|
||||||
from .settings import (
|
from .settings import (
|
||||||
KIND as _KIND,
|
KIND as _KIND,
|
||||||
Setting as _Setting,
|
Setting as _Setting,
|
||||||
|
BitFieldSetting as _BitFieldSetting,
|
||||||
Settings as _Settings,
|
Settings as _Settings,
|
||||||
RegisterRW as _RegisterRW,
|
RegisterRW as _RegisterRW,
|
||||||
FeatureRW as _FeatureRW,
|
FeatureRW as _FeatureRW,
|
||||||
FeatureRWMap as _FeatureRWMap,
|
FeatureRWMap as _FeatureRWMap,
|
||||||
BooleanValidator as _BooleanV,
|
BooleanValidator as _BooleanV,
|
||||||
|
BitFieldValidator as _BitFieldV,
|
||||||
ChoicesValidator as _ChoicesV,
|
ChoicesValidator as _ChoicesV,
|
||||||
ChoicesMapValidator as _ChoicesMapV,
|
ChoicesMapValidator as _ChoicesMapV,
|
||||||
RangeValidator as _RangeV,
|
RangeValidator as _RangeV,
|
||||||
|
|
@ -86,6 +88,30 @@ def feature_toggle(name, feature,
|
||||||
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||||
return _Setting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
|
return _Setting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
|
||||||
|
|
||||||
|
def feature_bitfield_toggle(name, feature, options,
|
||||||
|
read_function_id=_FeatureRW.default_read_fnid,
|
||||||
|
write_function_id=_FeatureRW.default_write_fnid,
|
||||||
|
label=None, description=None, device_kind=None):
|
||||||
|
assert options
|
||||||
|
validator = _BitFieldV(options)
|
||||||
|
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||||
|
return _BitFieldSetting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
|
||||||
|
|
||||||
|
def feature_bitfield_toggle_dynamic(name, feature, options_callback,
|
||||||
|
read_function_id=_FeatureRW.default_read_fnid,
|
||||||
|
write_function_id=_FeatureRW.default_write_fnid,
|
||||||
|
label=None, description=None, device_kind=None):
|
||||||
|
def instantiate(device):
|
||||||
|
options = options_callback(device)
|
||||||
|
setting = feature_bitfield_toggle(name, feature, options,
|
||||||
|
read_function_id=read_function_id,
|
||||||
|
write_function_id=write_function_id,
|
||||||
|
label=label,
|
||||||
|
description=description, device_kind=device_kind)
|
||||||
|
return setting(device)
|
||||||
|
instantiate._rw_kind = _FeatureRW.kind
|
||||||
|
return instantiate
|
||||||
|
|
||||||
def feature_choices(name, feature, choices,
|
def feature_choices(name, feature, choices,
|
||||||
read_function_id, write_function_id,
|
read_function_id, write_function_id,
|
||||||
bytes_count=None,
|
bytes_count=None,
|
||||||
|
|
@ -108,6 +134,7 @@ def feature_choices_dynamic(name, feature, choices_callback,
|
||||||
bytes_count=bytes_count,
|
bytes_count=bytes_count,
|
||||||
label=label, description=description, device_kind=device_kind)
|
label=label, description=description, device_kind=device_kind)
|
||||||
return setting(device)
|
return setting(device)
|
||||||
|
instantiate._rw_kind = _FeatureRW.kind
|
||||||
return instantiate
|
return instantiate
|
||||||
|
|
||||||
# maintain a mapping from keys (NamedInts) to one of a list of choices (NamedInts), default is first one
|
# maintain a mapping from keys (NamedInts) to one of a list of choices (NamedInts), default is first one
|
||||||
|
|
@ -136,6 +163,7 @@ def feature_map_choices_dynamic(name, feature, choices_callback,
|
||||||
key_bytes_count=key_bytes_count, skip_bytes_count=skip_bytes_count, value_bytes_count=value_bytes_count,
|
key_bytes_count=key_bytes_count, skip_bytes_count=skip_bytes_count, value_bytes_count=value_bytes_count,
|
||||||
label=label, description=description, device_kind=device_kind, extra_default=extra_default)
|
label=label, description=description, device_kind=device_kind, extra_default=extra_default)
|
||||||
return setting(device)
|
return setting(device)
|
||||||
|
instantiate._rw_kind = _FeatureRWMap.kind
|
||||||
return instantiate
|
return instantiate
|
||||||
|
|
||||||
def feature_range(name, feature, min_value, max_value,
|
def feature_range(name, feature, min_value, max_value,
|
||||||
|
|
@ -175,6 +203,7 @@ _FN_SWAP = ('fn-swap', _("Swap Fx function"),
|
||||||
+ '\n\n' +
|
+ '\n\n' +
|
||||||
_("When unset, the F1..F12 keys will activate their standard function,\n"
|
_("When unset, the F1..F12 keys will activate their standard function,\n"
|
||||||
"and you must hold the FN key to activate their special function."))
|
"and you must hold the FN key to activate their special function."))
|
||||||
|
_DISABLE_KEYS = ('disable-keyboard-keys', _("Disable keys"), _("Disable specific keyboard keys."))
|
||||||
_HAND_DETECTION = ('hand-detection', _("Hand Detection"),
|
_HAND_DETECTION = ('hand-detection', _("Hand Detection"),
|
||||||
_("Turn on illumination when the hands hover over the keyboard."))
|
_("Turn on illumination when the hands hover over the keyboard."))
|
||||||
_BACKLIGHT = ('backlight', _("Backlight"),
|
_BACKLIGHT = ('backlight', _("Backlight"),
|
||||||
|
|
@ -246,6 +275,7 @@ def _feature_lowres_smooth_scroll():
|
||||||
return feature_toggle(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL,
|
return feature_toggle(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL,
|
||||||
label=_LOW_RES_SCROLL[1], description=_LOW_RES_SCROLL[2],
|
label=_LOW_RES_SCROLL[1], description=_LOW_RES_SCROLL[2],
|
||||||
device_kind=(_DK.mouse, _DK.trackball))
|
device_kind=(_DK.mouse, _DK.trackball))
|
||||||
|
|
||||||
def _feature_hires_smooth_invert():
|
def _feature_hires_smooth_invert():
|
||||||
return feature_toggle(_HIRES_INV[0], _F.HIRES_WHEEL,
|
return feature_toggle(_HIRES_INV[0], _F.HIRES_WHEEL,
|
||||||
read_function_id=0x10,
|
read_function_id=0x10,
|
||||||
|
|
@ -379,6 +409,18 @@ def _feature_reprogrammable_keys():
|
||||||
label=_REPROGRAMMABLE_KEYS[1], description=_REPROGRAMMABLE_KEYS[2],
|
label=_REPROGRAMMABLE_KEYS[1], description=_REPROGRAMMABLE_KEYS[2],
|
||||||
device_kind=(_DK.keyboard,), extra_default=0)
|
device_kind=(_DK.keyboard,), extra_default=0)
|
||||||
|
|
||||||
|
|
||||||
|
def _feature_disable_keyboard_keys_key_list(device):
|
||||||
|
mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS)[0]
|
||||||
|
options = [_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)]
|
||||||
|
return options
|
||||||
|
|
||||||
|
def _feature_disable_keyboard_keys():
|
||||||
|
return feature_bitfield_toggle_dynamic(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS,
|
||||||
|
_feature_disable_keyboard_keys_key_list,
|
||||||
|
read_function_id=0x10, write_function_id=0x20,
|
||||||
|
label=_DISABLE_KEYS[1], description=_DISABLE_KEYS[2], device_kind=(_DK.keyboard,))
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
@ -401,6 +443,7 @@ _SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [
|
||||||
'typing_illumination',
|
'typing_illumination',
|
||||||
'smart_shift',
|
'smart_shift',
|
||||||
'reprogrammable_keys',
|
'reprogrammable_keys',
|
||||||
|
'disable_keyboard_keys',
|
||||||
])
|
])
|
||||||
del namedtuple
|
del namedtuple
|
||||||
|
|
||||||
|
|
@ -421,6 +464,7 @@ RegisterSettings = _SETTINGS_LIST(
|
||||||
typing_illumination=None,
|
typing_illumination=None,
|
||||||
smart_shift=None,
|
smart_shift=None,
|
||||||
reprogrammable_keys=None,
|
reprogrammable_keys=None,
|
||||||
|
disable_keyboard_keys=None,
|
||||||
)
|
)
|
||||||
FeatureSettings = _SETTINGS_LIST(
|
FeatureSettings = _SETTINGS_LIST(
|
||||||
fn_swap=_feature_fn_swap,
|
fn_swap=_feature_fn_swap,
|
||||||
|
|
@ -439,6 +483,7 @@ FeatureSettings = _SETTINGS_LIST(
|
||||||
typing_illumination=None,
|
typing_illumination=None,
|
||||||
smart_shift=_feature_smart_shift,
|
smart_shift=_feature_smart_shift,
|
||||||
reprogrammable_keys=_feature_reprogrammable_keys,
|
reprogrammable_keys=_feature_reprogrammable_keys,
|
||||||
|
disable_keyboard_keys=_feature_disable_keyboard_keys,
|
||||||
)
|
)
|
||||||
|
|
||||||
del _SETTINGS_LIST
|
del _SETTINGS_LIST
|
||||||
|
|
@ -479,6 +524,7 @@ def check_feature_settings(device, already_known):
|
||||||
if detected:
|
if detected:
|
||||||
already_known.append(detected)
|
already_known.append(detected)
|
||||||
except Exception as reason:
|
except Exception as reason:
|
||||||
|
raise reason
|
||||||
_log.error("check_feature[%s] inconsistent feature %s", featureId, reason)
|
_log.error("check_feature[%s] inconsistent feature %s", featureId, reason)
|
||||||
|
|
||||||
check_feature(_HI_RES_SCROLL[0], _F.HI_RES_SCROLLING)
|
check_feature(_HI_RES_SCROLL[0], _F.HI_RES_SCROLLING)
|
||||||
|
|
@ -493,4 +539,5 @@ def check_feature_settings(device, already_known):
|
||||||
check_feature(_SMART_SHIFT[0], _F.SMART_SHIFT)
|
check_feature(_SMART_SHIFT[0], _F.SMART_SHIFT)
|
||||||
check_feature(_BACKLIGHT[0], _F.BACKLIGHT2)
|
check_feature(_BACKLIGHT[0], _F.BACKLIGHT2)
|
||||||
check_feature(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4)
|
check_feature(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4)
|
||||||
|
check_feature(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS, 'disable_keyboard_keys')
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -377,3 +377,12 @@ KEY_FLAG = _NamedInts(
|
||||||
is_FN=0x02,
|
is_FN=0x02,
|
||||||
mse=0x01
|
mse=0x01
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DISABLE = _NamedInts(
|
||||||
|
Caps_Lock=0x01,
|
||||||
|
Num_Lock=0x02,
|
||||||
|
Scroll_Lock=0x04,
|
||||||
|
Insert=0x08,
|
||||||
|
Win=0x10, # aka Super
|
||||||
|
)
|
||||||
|
DISABLE._fallback = lambda x: 'unknown:%02X' % x
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,20 @@ def _create_sbox(s):
|
||||||
elif s.kind == _SETTING_KIND.map_choice:
|
elif s.kind == _SETTING_KIND.map_choice:
|
||||||
control = _create_map_choice_control(s)
|
control = _create_map_choice_control(s)
|
||||||
sbox.pack_end(control, True, True, 0)
|
sbox.pack_end(control, True, True, 0)
|
||||||
|
elif s.kind == _SETTING_KIND.multiple_toggle:
|
||||||
|
# ugly temporary hack!
|
||||||
|
choices = {k : [False, True] for k in s._validator.options}
|
||||||
|
class X:
|
||||||
|
def __init__(self, obj, ext):
|
||||||
|
self.obj = obj
|
||||||
|
self.ext = ext
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
try:
|
||||||
|
return self.ext[attr]
|
||||||
|
except KeyError:
|
||||||
|
return getattr(self.obj, attr)
|
||||||
|
control = _create_map_choice_control(X(s, {'choices': choices}))
|
||||||
|
sbox.pack_end(control, True, True, 0)
|
||||||
else:
|
else:
|
||||||
raise Exception("NotImplemented")
|
raise Exception("NotImplemented")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue