From 588d7ae5333cc21e2a6ba39f2a301f0fdffceb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius?= Date: Thu, 10 Mar 2022 20:03:52 -0300 Subject: [PATCH] ui: add support to TestBytes --- lib/logitech_receiver/diversion.py | 2 +- lib/solaar/ui/diversion_rules.py | 123 ++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py index 3f7f4f55..c6c63e34 100644 --- a/lib/logitech_receiver/diversion.py +++ b/lib/logitech_receiver/diversion.py @@ -588,7 +588,7 @@ class TestBytes(Condition): return self.function(feature, notification.address, notification.data) def data(self): - return {'TestBytes': str(self.test)} + return {'TestBytes': self.test[:]} class MouseGesture(Condition): diff --git a/lib/solaar/ui/diversion_rules.py b/lib/solaar/ui/diversion_rules.py index 6f8e6876..24d354fd 100644 --- a/lib/solaar/ui/diversion_rules.py +++ b/lib/solaar/ui/diversion_rules.py @@ -18,7 +18,7 @@ import string import threading -from collections import defaultdict +from collections import defaultdict, namedtuple from contextlib import contextmanager as contextlib_contextmanager from copy import copy from dataclasses import dataclass, field @@ -512,11 +512,12 @@ class DiversionDialog: [ (_('Feature'), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]), (_('Process'), _DIV.Process, ''), - (_('MouseProcess'), _DIV.MouseProcess, ''), + (_('Mouse process'), _DIV.MouseProcess, ''), (_('Report'), _DIV.Report, 0), (_('Modifiers'), _DIV.Modifiers, []), (_('Key'), _DIV.Key, ''), (_('Test'), _DIV.Test, next(iter(_DIV.TESTS))), + (_('Test bytes'), _DIV.TestBytes, [0, 1, 0]), (_('Mouse Gesture'), _DIV.MouseGesture, ''), ] ], @@ -1394,6 +1395,123 @@ class TestUI(ConditionUI): return str(component.test) +_TestBytesElement = namedtuple('TestBytesElement', ['id', 'label', 'min', 'max']) +_TestBytesMode = namedtuple('TestBytesMode', ['label', 'elements', 'label_fn']) + + +class TestBytesUI(ConditionUI): + + CLASS = _DIV.TestBytes + + _common_elements = [ + _TestBytesElement('begin', _('begin (inclusive)'), 0, 16), + _TestBytesElement('end', _('end (exclusive)'), 0, 16) + ] + + _global_min = -2**31 + _global_max = 2**31 - 1 + + _modes = { + 'range': + _TestBytesMode( + _('range'), + _common_elements + [ + _TestBytesElement('minimum', _('minimum'), _global_min, _global_max), # uint32 + _TestBytesElement('maximum', _('maximum'), _global_min, _global_max), + ], + lambda e: _('bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d' % {str(i): v + for i, v in enumerate(e)}) + ), + 'mask': + _TestBytesMode( + _('mask'), _common_elements + [_TestBytesElement('mask', _('mask'), _global_min, _global_max)], + lambda e: _('bytes %(0)d to %(1)d, mask %(2)d' % {str(i): v + for i, v in enumerate(e)}) + ) + } + + def create_widgets(self): + self.fields = {} + self.field_labels = {} + self.widgets = {} + col = 0 + mode_col = 2 + self.mode_field = Gtk.ComboBox.new_with_model(Gtk.ListStore(str, str)) + mode_renderer = Gtk.CellRendererText() + self.mode_field.set_id_column(0) + self.mode_field.pack_start(mode_renderer, True) + self.mode_field.add_attribute(mode_renderer, 'text', 1) + self.widgets[self.mode_field] = (mode_col, 1, 1, 1) + mode_label = Gtk.Label(_('type'), margin_top=20) + self.widgets[mode_label] = (mode_col, 0, 1, 1) + for mode_id, mode in TestBytesUI._modes.items(): + self.mode_field.get_model().append([mode_id, mode.label]) + for element in mode.elements: + if element.id not in self.fields: + field = Gtk.SpinButton.new_with_range(element.min, element.max, 1) + field.set_value(0) + field.set_size_request(150, 0) + field.connect('value-changed', self._on_update) + label = Gtk.Label(element.label, margin_top=20) + self.fields[element.id] = field + self.field_labels[element.id] = label + self.widgets[label] = (col, 0, 1, 1) + self.widgets[field] = (col, 1, 1, 1) + col += 1 if col != mode_col - 1 else 2 + self.mode_field.connect('changed', lambda cb: (self._on_update(), self._only_mode(cb.get_active_id()))) + self.mode_field.set_active_id('range') + + def show(self, component, editable): + super().show(component, editable) + + with self.ignore_changes(): + mode_id = {3: 'mask', 4: 'range'}.get(len(component.test), None) + self._only_mode(mode_id) + if not mode_id: + return + self.mode_field.set_active_id(mode_id) + if mode_id: + mode = TestBytesUI._modes[mode_id] + for i, element in enumerate(mode.elements): + self.fields[element.id].set_value(component.test[i]) + + def collect_value(self): + mode_id = self.mode_field.get_active_id() + return [self.fields[element.id].get_value_as_int() for element in TestBytesUI._modes[mode_id].elements] + + def _only_mode(self, mode_id): + if not mode_id: + return + keep = {element.id for element in TestBytesUI._modes[mode_id].elements} + for element_id, f in self.fields.items(): + visible = element_id in keep + f.set_visible(visible) + self.field_labels[element_id].set_visible(visible) + + def _on_update(self, *args): + super()._on_update(*args) + if not self.component: + return + begin, end, *etc = self.component.test + icon = 'dialog-warning' if end <= begin else '' + self.fields['end'].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + if len(self.component.test) == 4: + *etc, minimum, maximum = self.component.test + icon = 'dialog-warning' if maximum < minimum else '' + self.fields['maximum'].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + @classmethod + def left_label(cls, component): + return _('Test bytes') + + @classmethod + def right_label(cls, component): + mode_id = {3: 'mask', 4: 'range'}.get(len(component.test), None) + if not mode_id: + return str(component.test) + return TestBytesUI._modes[mode_id].label_fn(component.test) + + class MouseGestureUI(ConditionUI): CLASS = _DIV.MouseGesture @@ -2234,6 +2352,7 @@ COMPONENT_UI = { _DIV.Modifiers: ModifiersUI, _DIV.Key: KeyUI, _DIV.Test: TestUI, + _DIV.TestBytes: TestBytesUI, _DIV.MouseGesture: MouseGestureUI, _DIV.KeyPress: KeyPressUI, _DIV.MouseScroll: MouseScrollUI,