From b50ac31a385405705fcb987bcc81936013d3b82d Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 4 Oct 2023 14:54:56 -0700 Subject: [PATCH] Add color-picker setting support --- chirp/settings.py | 54 +++++++++++++++++++++++++++++++++++++ chirp/wxui/common.py | 12 ++++++++- tests/unit/test_settings.py | 15 +++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/chirp/settings.py b/chirp/settings.py index 9d9cfe44a..b9a1930d2 100644 --- a/chirp/settings.py +++ b/chirp/settings.py @@ -118,6 +118,60 @@ def get_step(self): return self._step +class RadioSettingValueRGB(RadioSettingValue): + """A color setting, stored as (r, g, b)""" + def __init__(self, current): + super().__init__() + self.set_value(current) + + @staticmethod + def from_bits(value, rbits, gbits, bbits): + outvals = [] + for bits in [rbits, gbits, bbits]: + outval = value & (2 ** bits - 1) + # Map into 8-bpc space + outvals.insert(0, round(outval / (2 ** bits - 1) * 255)) + value >>= bits + return RadioSettingValueRGB(tuple(outvals)) + + @staticmethod + def from_rgb24(value): + """Return a RadioSettingValueRGB initialized from an rgb24 value""" + return RadioSettingValueRGB.from_bits(value, 8, 8, 8) + + @staticmethod + def from_rgb16(value): + """Return a RadioSettingValueRGB initialized from an rgb16 value""" + return RadioSettingValueRGB.from_bits(value, 5, 6, 5) + + def set_value(self, value): + self._r, self._g, self._b = value + + def get_value(self): + """Returns (r, g, b)""" + return self._r, self._g, self._b + + def get_bits(self, rbits, gbits, bbits): + """Return color as a bitpacked integer. + + Pass the number of bits for the r, g, and b channels packed + sequentially into an integer to be returned. + """ + outval = 0 + for bits, val in zip([rbits, gbits, bbits], + [self._r, self._g, self._b]): + outval = (outval << bits) | round((2 ** bits - 1) * val / 255) + return outval + + def get_rgb24(self): + """Return an integer value for 24-bit color""" + return self.get_bits(8, 8, 8) + + def get_rgb16(self): + """Return an integer value for 16-bit color""" + return self.get_bits(5, 6, 5) + + class RadioSettingValueFloat(RadioSettingValue): """A floating-point setting""" diff --git a/chirp/wxui/common.py b/chirp/wxui/common.py index eb3c17779..972a7e7ae 100644 --- a/chirp/wxui/common.py +++ b/chirp/wxui/common.py @@ -388,6 +388,8 @@ def __init__(self, settinggroup, *a, **k): editor = self._get_editor_bool(element, value) elif isinstance(value, settings.RadioSettingValueString): editor = self._get_editor_str(element, value) + elif isinstance(value, settings.RadioSettingValueRGB): + editor = self._get_editor_rgb(element, value) else: LOG.warning('Unsupported setting type %r' % value) editor = None @@ -510,6 +512,11 @@ def ValidateValue(self, text, info): setting.get_name(), value=str(value)) + def _get_editor_rgb(self, setting, value): + return wx.propgrid.ColourProperty(setting.get_shortname(), + setting.get_name(), + wx.Colour(*value.get_value())) + def get_setting_values(self): """Return a dict of {name: (RadioSetting, newvalue)}""" values = {} @@ -517,7 +524,10 @@ def get_setting_values(self): if prop.IsCategory(): continue basename = prop.GetName().split(INDEX_CHAR)[0] - if isinstance(prop, wx.propgrid.EnumProperty): + if isinstance(prop, wx.propgrid.ColourProperty): + c = prop.GetValue() + value = c.red, c.green, c.blue + elif isinstance(prop, wx.propgrid.EnumProperty): value = self._choices[basename][prop.GetValue()] else: value = prop.GetValue() diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 108fb161b..6ce0a707a 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -83,6 +83,21 @@ def test_radio_setting_value_string(self): self._set_and_test(value, "a", "abc", "abdef") self._set_and_catch(value, "", "abcdefg") + def test_radio_setting_value_rgb(self): + value = settings.RadioSettingValueRGB((0, 0, 0)) + self.assertEqual((0, 0, 0), value.get_value()) + self.assertEqual(0, value.get_rgb16()) + self.assertEqual(0, value.get_rgb24()) + + # 0x9c9a2f == 0x9cc6 + value.set_value((0x9C, 0x9A, 0x2F)) + self.assertEqual(0x9C9A2F, value.get_rgb24()) + self.assertEqual(0x9CC6, value.get_rgb16()) + + value = settings.RadioSettingValueRGB.from_rgb16(0x9CC6) + # Rounding error on the B value + self.assertEqual(0x9C9A31, value.get_rgb24()) + def test_validate_callback(self): class TestException(Exception): pass