diff --git a/owrx/controllers/settings.py b/owrx/controllers/settings.py index 38b6f65..b166530 100644 --- a/owrx/controllers/settings.py +++ b/owrx/controllers/settings.py @@ -12,14 +12,13 @@ from owrx.form import ( Option, ServicesCheckboxInput, Js8ProfileCheckboxInput, - ReceiverKeysConverter, - WfmTauValues, MultiCheckboxInput, - OptionalConverter, - AprsBeaconSymbols, - AprsAntennaDirections, - Q65ModeMatrix, ) +from owrx.form.converter import OptionalConverter +from owrx.form.receiverid import ReceiverKeysConverter +from owrx.form.aprs import AprsBeaconSymbols, AprsAntennaDirections +from owrx.form.wfm import WfmTauValues +from owrx.form.wsjt import Q65ModeMatrix from urllib.parse import quote from owrx.wsjt import Fst4Profile, Fst4wProfile import json diff --git a/owrx/form/__init__.py b/owrx/form/__init__.py index 92fad73..5323cc0 100644 --- a/owrx/form/__init__.py +++ b/owrx/form/__init__.py @@ -1,41 +1,10 @@ from abc import ABC, abstractmethod from owrx.modes import Modes from owrx.config import Config -from owrx.wsjt import Q65Mode, Q65Interval +from owrx.form.converter import Converter, NullConverter, IntConverter, FloatConverter, EnumConverter from enum import Enum -class Converter(ABC): - @abstractmethod - def convert_to_form(self, value): - pass - - @abstractmethod - def convert_from_form(self, value): - pass - - -class NullConverter(Converter): - def convert_to_form(self, value): - return value - - def convert_from_form(self, value): - return value - - -class OptionalConverter(Converter): - """ - Maps None to an empty string, and reverse - useful for optional fields - """ - - def convert_to_form(self, value): - return "" if value is None else value - - def convert_from_form(self, value): - return value if value else None - - class Input(ABC): def __init__(self, id, label, infotext=None, converter: Converter = None): self.id = id @@ -84,14 +53,6 @@ class TextInput(Input): ) -class IntConverter(Converter): - def convert_to_form(self, value): - return str(value) - - def convert_from_form(self, value): - return int(value) - - class NumberInput(Input): def __init__(self, id, label, infotext=None, append="", converter: Converter = None): super().__init__(id, label, infotext, converter=converter) @@ -128,14 +89,6 @@ class NumberInput(Input): ) -class FloatConverter(Converter): - def convert_to_form(self, value): - return str(value) - - def convert_from_form(self, value): - return float(value) - - class FloatInput(NumberInput): def __init__(self, id, label, infotext=None, converter: Converter = None): super().__init__(id, label, infotext, converter=converter) @@ -185,15 +138,6 @@ class TextAreaInput(Input): ) -class ReceiverKeysConverter(Converter): - def convert_to_form(self, value): - return "\n".join(value) - - def convert_from_form(self, value): - # \r\n or \n? this should work with both. - return [v.strip("\r ") for v in value.split("\n")] - - class CheckboxInput(Input): def __init__(self, id, label, checkboxText, infotext=None): super().__init__(id, label, infotext=infotext) @@ -317,125 +261,6 @@ class DropdownInput(Input): return "".join(options) -class Q65ModeConverter(Converter): - def convert_to_form(self, value): - pass - - def convert_from_form(self, value): - pass - - -class Q65ModeMatrix(Input): - def checkbox_id(self, mode, interval): - return "{0}-{1}-{2}".format(self.id, mode.value, interval.value) - - def render_checkbox(self, mode: Q65Mode, interval: Q65Interval, value): - return """ -
- - -
- """.format( - classes=self.input_classes(), - id=self.checkbox_id(mode, interval), - checked="checked" if "{}{}".format(mode.name, interval.value) in value else "", - checkboxText="Mode {} interval {}s".format(mode.name, interval.value), - disabled="" if interval.is_available(mode) else "disabled", - ) - - def render_input(self, value): - checkboxes = "".join( - self.render_checkbox(mode, interval, value) for interval in Q65Interval for mode in Q65Mode - ) - return """ -
- {checkboxes} -
- """.format( - checkboxes=checkboxes - ) - - def input_classes(self): - return " ".join(["form-check", "form-control-sm"]) - - def parse(self, data): - def in_response(mode, interval): - boxid = self.checkbox_id(mode, interval) - return boxid in data and data[boxid][0] == "on" - - return { - self.id: [ - "{}{}".format(mode.name, interval.value) - for interval in Q65Interval - for mode in Q65Mode - if in_response(mode, interval) - ], - } - - class DropdownEnum(Enum): def toOption(self): return Option(self.name, str(self)) - - -class EnumConverter(Converter): - def __init__(self, enumCls): - self.enumCls = enumCls - - def convert_to_form(self, value): - return None if value is None else self.enumCls(value).name - - def convert_from_form(self, value): - return self.enumCls[value].value - - -class WfmTauValues(DropdownEnum): - TAU_50_MICRO = (50e-6, "most regions") - TAU_75_MICRO = (75e-6, "Americas and South Korea") - - def __new__(cls, *args, **kwargs): - value, description = args - obj = object.__new__(cls) - obj._value_ = value - obj.description = description - return obj - - def __str__(self): - return "{}µs ({})".format(int(self.value * 1e6), self.description) - - -class AprsBeaconSymbols(DropdownEnum): - BEACON_RECEIVE_ONLY = ("R&", "Receive only IGate") - BEACON_HF_GATEWAY = ("/&", "HF Gateway") - BEACON_IGATE_GENERIC = ("I&", "Igate Generic (please use more specific overlay)") - BEACON_PSKMAIL = ("P&", "PSKmail node") - BEACON_TX_1 = ("T&", "TX IGate with path set to 1 hop") - BEACON_WIRES_X = ("W&", "Wires-X") - BEACON_TX_2 = ("2&", "TX IGate with path set to 2 hops") - - def __new__(cls, *args, **kwargs): - value, description = args - obj = object.__new__(cls) - obj._value_ = value - obj.description = description - return obj - - def __str__(self): - return "{description} ({symbol})".format(description=self.description, symbol=self.value) - - -class AprsAntennaDirections(DropdownEnum): - DIRECTION_OMNI = None - DIRECTION_N = "N" - DIRECTION_NE = "NE" - DIRECTION_E = "E" - DIRECTION_SE = "SE" - DIRECTION_S = "S" - DIRECTION_SW = "SW" - DIRECTION_W = "W" - DIRECTION_NW = "NW" - - def __str__(self): - return "omnidirectional" if self.value is None else self.value diff --git a/owrx/form/aprs.py b/owrx/form/aprs.py new file mode 100644 index 0000000..e56b019 --- /dev/null +++ b/owrx/form/aprs.py @@ -0,0 +1,36 @@ +from owrx.form import DropdownEnum + + +class AprsBeaconSymbols(DropdownEnum): + BEACON_RECEIVE_ONLY = ("R&", "Receive only IGate") + BEACON_HF_GATEWAY = ("/&", "HF Gateway") + BEACON_IGATE_GENERIC = ("I&", "Igate Generic (please use more specific overlay)") + BEACON_PSKMAIL = ("P&", "PSKmail node") + BEACON_TX_1 = ("T&", "TX IGate with path set to 1 hop") + BEACON_WIRES_X = ("W&", "Wires-X") + BEACON_TX_2 = ("2&", "TX IGate with path set to 2 hops") + + def __new__(cls, *args, **kwargs): + value, description = args + obj = object.__new__(cls) + obj._value_ = value + obj.description = description + return obj + + def __str__(self): + return "{description} ({symbol})".format(description=self.description, symbol=self.value) + + +class AprsAntennaDirections(DropdownEnum): + DIRECTION_OMNI = None + DIRECTION_N = "N" + DIRECTION_NE = "NE" + DIRECTION_E = "E" + DIRECTION_SE = "SE" + DIRECTION_S = "S" + DIRECTION_SW = "SW" + DIRECTION_W = "W" + DIRECTION_NW = "NW" + + def __str__(self): + return "omnidirectional" if self.value is None else self.value diff --git a/owrx/form/converter.py b/owrx/form/converter.py new file mode 100644 index 0000000..7739591 --- /dev/null +++ b/owrx/form/converter.py @@ -0,0 +1,61 @@ +from abc import ABC, abstractmethod + + +class Converter(ABC): + @abstractmethod + def convert_to_form(self, value): + pass + + @abstractmethod + def convert_from_form(self, value): + pass + + +class NullConverter(Converter): + def convert_to_form(self, value): + return value + + def convert_from_form(self, value): + return value + + +class OptionalConverter(Converter): + """ + Maps None to an empty string, and reverse + useful for optional fields + """ + + def convert_to_form(self, value): + return "" if value is None else value + + def convert_from_form(self, value): + return value if value else None + + +class IntConverter(Converter): + def convert_to_form(self, value): + return str(value) + + def convert_from_form(self, value): + return int(value) + + +class FloatConverter(Converter): + def convert_to_form(self, value): + return str(value) + + def convert_from_form(self, value): + return float(value) + + +class EnumConverter(Converter): + def __init__(self, enumCls): + self.enumCls = enumCls + + def convert_to_form(self, value): + return None if value is None else self.enumCls(value).name + + def convert_from_form(self, value): + return self.enumCls[value].value + + diff --git a/owrx/form/gfx.py b/owrx/form/gfx.py new file mode 100644 index 0000000..3712b87 --- /dev/null +++ b/owrx/form/gfx.py @@ -0,0 +1,7 @@ +from owrx.form import Input + + +class AvatarInput(Input): + def render_input(self, value): + pass + diff --git a/owrx/form/receiverid.py b/owrx/form/receiverid.py new file mode 100644 index 0000000..139bd82 --- /dev/null +++ b/owrx/form/receiverid.py @@ -0,0 +1,10 @@ +from owrx.form.converter import Converter + + +class ReceiverKeysConverter(Converter): + def convert_to_form(self, value): + return "\n".join(value) + + def convert_from_form(self, value): + # \r\n or \n? this should work with both. + return [v.strip("\r ") for v in value.split("\n")] diff --git a/owrx/form/wfm.py b/owrx/form/wfm.py new file mode 100644 index 0000000..1bdb7d7 --- /dev/null +++ b/owrx/form/wfm.py @@ -0,0 +1,16 @@ +from owrx.form import DropdownEnum + + +class WfmTauValues(DropdownEnum): + TAU_50_MICRO = (50e-6, "most regions") + TAU_75_MICRO = (75e-6, "Americas and South Korea") + + def __new__(cls, *args, **kwargs): + value, description = args + obj = object.__new__(cls) + obj._value_ = value + obj.description = description + return obj + + def __str__(self): + return "{}µs ({})".format(int(self.value * 1e6), self.description) diff --git a/owrx/form/wsjt.py b/owrx/form/wsjt.py new file mode 100644 index 0000000..1c77a3c --- /dev/null +++ b/owrx/form/wsjt.py @@ -0,0 +1,52 @@ +from owrx.form import Input +from owrx.wsjt import Q65Mode, Q65Interval + + +class Q65ModeMatrix(Input): + def checkbox_id(self, mode, interval): + return "{0}-{1}-{2}".format(self.id, mode.value, interval.value) + + def render_checkbox(self, mode: Q65Mode, interval: Q65Interval, value): + return """ +
+ + +
+ """.format( + classes=self.input_classes(), + id=self.checkbox_id(mode, interval), + checked="checked" if "{}{}".format(mode.name, interval.value) in value else "", + checkboxText="Mode {} interval {}s".format(mode.name, interval.value), + disabled="" if interval.is_available(mode) else "disabled", + ) + + def render_input(self, value): + checkboxes = "".join( + self.render_checkbox(mode, interval, value) for interval in Q65Interval for mode in Q65Mode + ) + return """ +
+ {checkboxes} +
+ """.format( + checkboxes=checkboxes + ) + + def input_classes(self): + return " ".join(["form-check", "form-control-sm"]) + + def parse(self, data): + def in_response(mode, interval): + boxid = self.checkbox_id(mode, interval) + return boxid in data and data[boxid][0] == "on" + + return { + self.id: [ + "{}{}".format(mode.name, interval.value) + for interval in Q65Interval + for mode in Q65Mode + if in_response(mode, interval) + ], + }