diff --git a/lib/logitech_receiver/common.py b/lib/logitech_receiver/common.py index f4c700d9..f37a4740 100644 --- a/lib/logitech_receiver/common.py +++ b/lib/logitech_receiver/common.py @@ -99,7 +99,7 @@ class NamedInts: if the value already exists in the set (int or string), ValueError will be raised. """ - __slots__ = ('__dict__', '_values', '_indexed', '_fallback') + __slots__ = ('__dict__', '_values', '_indexed', '_fallback', '_is_sorted') def __init__(self, **kwargs): def _readable_name(n): @@ -110,7 +110,9 @@ class NamedInts: # print (repr(kwargs)) values = {k: NamedInt(v, _readable_name(k)) for (k, v) in kwargs.items()} self.__dict__ = values - self._values = sorted(list(values.values())) + self._is_sorted = False + self._values = list(values.values()) + self._sort_values() self._indexed = {int(v): v for v in self._values} # assert len(values) == len(self._indexed) # "(%d) %r\n=> (%d) %r" % (len(values), values, len(self._indexed), self._indexed) @@ -137,14 +139,20 @@ class NamedInts: if unknown_bits: yield 'unknown:%06X' % unknown_bits + def _sort_values(self): + self._values = sorted(self._values) + self._is_sorted = True + def __getitem__(self, index): if isinstance(index, int): if index in self._indexed: return self._indexed[int(index)] - if self._fallback and isinstance(index, int): + if self._fallback: value = NamedInt(index, self._fallback(index)) self._indexed[index] = value - self._values = sorted(self._values + [value]) + self._values.append(value) + self._is_sorted = False + self._sort_values() return value elif is_string(index): @@ -153,21 +161,24 @@ class NamedInts: return (next((x for x in self._values if str(x) == index), None)) elif isinstance(index, slice): + values = self._values if self._is_sorted else sorted(self._values) + if index.start is None and index.stop is None: - return self._values[:] + return values[:] - v_start = int(self._values[0]) if index.start is None else int(index.start) - v_stop = (self._values[-1] + 1) if index.stop is None else int(index.stop) + v_start = int(values[0]) if index.start is None else int(index.start) + v_stop = (values[-1] + 1) if index.stop is None else int(index.stop) - if v_start > v_stop or v_start > self._values[-1] or v_stop <= self._values[0]: + if v_start > v_stop or v_start > values[-1] or v_stop <= values[0]: return [] - if v_start <= self._values[0] and v_stop > self._values[-1]: - return self._values[:] + if v_start <= values[0] and v_stop > values[-1]: + return values[:] start_index = 0 - stop_index = len(self._values) - for i, value in enumerate(self._values): + stop_index = len(values) + + for i, value in enumerate(values): if value < v_start: start_index = i + 1 elif index.stop is None: @@ -176,7 +187,7 @@ class NamedInts: stop_index = i break - return self._values[start_index:stop_index] + return values[start_index:stop_index] def __setitem__(self, index, name): assert isinstance(index, int), type(index) @@ -193,7 +204,9 @@ class NamedInts: if int(value) in self._indexed: raise ValueError('%d (%s) already known' % (int(value), value)) - self._values = sorted(self._values + [value]) + self._values.append(value) + self._is_sorted = False + self._sort_values() self.__dict__[str(value)] = value self._indexed[int(value)] = value @@ -216,6 +229,15 @@ class NamedInts: return NamedInts(**self.__dict__, **other.__dict__) +class UnsortedNamedInts(NamedInts): + def _sort_values(self): + pass + + def __or__(self, other): + c = UnsortedNamedInts if isinstance(other, UnsortedNamedInts) else NamedInts + return c(**self.__dict__, **other.__dict__) + + def strhex(x): assert x is not None """Produce a hex-string representation of a sequence of bytes.""" diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index da9c91cc..2bb0acc1 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -31,6 +31,7 @@ from .common import FirmwareInfo as _FirmwareInfo from .common import KwException as _KwException from .common import NamedInt as _NamedInt from .common import NamedInts as _NamedInts +from .common import UnsortedNamedInts as _UnsortedNamedInts from .common import bytes2int as _bytes2int from .common import pack as _pack from .common import unpack as _unpack @@ -438,18 +439,18 @@ class ReprogrammableKeyV4(ReprogrammableKey): return _NamedInt(self._mapped_to, task) @property - def remappable_to(self) -> List[_NamedInt]: + def remappable_to(self) -> _NamedInts: self._device.keys._ensure_all_keys_queried() - ret = [] + ret = _UnsortedNamedInts() if self.group_mask != []: # only keys with a non-zero gmask are remappable - ret = [self.default_task] # it should always be possible to map the key to itself + ret[self.default_task] = self.default_task # it should always be possible to map the key to itself for g in self.group_mask: g = special_keys.CID_GROUP[str(g)] for tgt_cid in self._device.keys.group_cids[g]: tgt_task = str(special_keys.TASK[self._device.keys.cid_to_tid[tgt_cid]]) tgt_task = _NamedInt(tgt_cid, tgt_task) if tgt_task != self.default_task: # don't put itself in twice - ret.append(tgt_task) + ret[tgt_task] = tgt_task return ret @property diff --git a/lib/solaar/ui/diversion_rules.py b/lib/solaar/ui/diversion_rules.py index c9192e32..4e43dc55 100644 --- a/lib/solaar/ui/diversion_rules.py +++ b/lib/solaar/ui/diversion_rules.py @@ -23,7 +23,7 @@ from shlex import quote as shlex_quote from gi.repository import Gdk, GObject, Gtk from logitech_receiver import diversion as _DIV -from logitech_receiver.common import NamedInt, NamedInts +from logitech_receiver.common import NamedInt, UnsortedNamedInts from logitech_receiver.diversion import XK_KEYS as _XK_KEYS from logitech_receiver.diversion import Key as _Key from logitech_receiver.diversion import buttons as _buttons @@ -1655,10 +1655,16 @@ class SetUI(ActionUI): Only one label per number is kept. """ if isinstance(setting, type) and issubclass(setting, _Setting): - return (getattr(setting, 'choices_universe', None) - or NamedInts()) | (getattr(setting, 'choices_extra', None) or NamedInts()) + choices = UnsortedNamedInts() + universe = getattr(setting, 'choices_universe', None) + if universe: + choices |= universe + extra = getattr(setting, 'choices_extra', None) + if extra: + choices |= extra + return choices settings = cls.ALL_SETTINGS.get(setting, []) - choices = NamedInts() + choices = UnsortedNamedInts() for s in settings: choices |= cls._all_choices(s) return choices @@ -1670,10 +1676,11 @@ class SetUI(ActionUI): val_class = setting.validator_class if setting else None kind = val_class.kind if val_class else None if kind in cls.MULTIPLE: - keys = NamedInts() + keys = UnsortedNamedInts() for s in settings: - keys |= getattr(s, 'choices_universe' if kind == _SKIND.multiple_toggle else 'keys_universe', - None) or NamedInts() + universe = getattr(s, 'choices_universe' if kind == _SKIND.multiple_toggle else 'keys_universe', None) + if universe: + keys |= universe # only one key per number is used else: keys = None