mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-04 23:45:26 +00:00
SymField rewrite (#1444)
* First WIP symfield * Cleanup widget code * Rebase and format * Fix 'to_integer' bug, fix siggen UI. * to_string_hex fix, unit tests for string code * Pass instance in callback * Fix on_change callbacks * Fix keyfob (not active) * to_byte_array, coaster tx cleanup * Add to_byte_array tests * Changes in ui_numbers * Fix ui_encoders * Format * Fix modemsetup view's symfields * Remove header * Format
This commit is contained in:
parent
70e0f2913f
commit
af424aa5f8
@ -71,6 +71,7 @@ POCSAGSettingsView::POCSAGSettingsView(
|
||||
check_hide_bad.set_value(settings_.hide_bad_data);
|
||||
check_hide_addr_only.set_value(settings_.hide_addr_only);
|
||||
check_ignore.set_value(settings_.enable_ignore);
|
||||
|
||||
field_ignore.set_value(settings_.address_to_ignore);
|
||||
|
||||
button_save.on_select = [this, &nav](Button&) {
|
||||
@ -81,7 +82,7 @@ POCSAGSettingsView::POCSAGSettingsView(
|
||||
settings_.hide_bad_data = check_hide_bad.value();
|
||||
settings_.hide_addr_only = check_hide_addr_only.value();
|
||||
settings_.enable_ignore = check_ignore.value();
|
||||
settings_.address_to_ignore = field_ignore.value();
|
||||
settings_.address_to_ignore = field_ignore.to_integer();
|
||||
|
||||
nav.pop();
|
||||
};
|
||||
@ -155,7 +156,7 @@ POCSAGAppView::~POCSAGAppView() {
|
||||
|
||||
// Save pmem settings.
|
||||
pmem::set_pocsag_ignore_address(settings_.address_to_ignore);
|
||||
pmem::set_pocsag_last_address(pocsag_state.address); // For POCSAG TX.
|
||||
pmem::set_pocsag_last_address(last_address); // For POCSAG TX.
|
||||
}
|
||||
|
||||
void POCSAGAppView::refresh_ui() {
|
||||
|
@ -171,12 +171,11 @@ class POCSAGSettingsView : public View {
|
||||
22,
|
||||
"Enable Ignored Address"};
|
||||
|
||||
NumberField field_ignore{
|
||||
SymField field_ignore{
|
||||
{7 * 8, 13 * 16 + 8},
|
||||
7,
|
||||
{0, 9999999},
|
||||
1,
|
||||
'0'};
|
||||
SymField::Type::Dec,
|
||||
true /*explicit_edit*/};
|
||||
|
||||
Button button_save{
|
||||
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
|
||||
@ -223,7 +222,7 @@ class POCSAGAppView : public View {
|
||||
void on_packet(const POCSAGPacketMessage* message);
|
||||
void on_stats(const POCSAGStatsMessage* stats);
|
||||
|
||||
uint32_t last_address = 0xFFFFFFFF;
|
||||
uint32_t last_address = 0;
|
||||
pocsag::EccContainer ecc{};
|
||||
pocsag::POCSAGState pocsag_state{&ecc};
|
||||
POCSAGLogger logger{};
|
||||
|
@ -186,13 +186,12 @@ ADSBSquawkView::ADSBSquawkView(
|
||||
&field_squawk});
|
||||
}
|
||||
|
||||
void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
||||
void ADSBSquawkView::collect_frames(const uint32_t /*ICAO_address*/, std::vector<ADSBFrame>& frame_list) {
|
||||
if (!enabled) return;
|
||||
|
||||
ADSBFrame temp_frame;
|
||||
(void)ICAO_address;
|
||||
|
||||
encode_frame_squawk(temp_frame, field_squawk.concatenate_4_octal_u16());
|
||||
encode_frame_squawk(temp_frame, field_squawk.to_integer());
|
||||
|
||||
frame_list.emplace_back(temp_frame);
|
||||
}
|
||||
@ -277,7 +276,7 @@ ADSBTxView::~ADSBTxView() {
|
||||
}
|
||||
|
||||
void ADSBTxView::generate_frames() {
|
||||
const uint32_t ICAO_address = sym_icao.value_hex_u64();
|
||||
const uint32_t ICAO_address = sym_icao.to_integer();
|
||||
|
||||
frames.clear();
|
||||
|
||||
|
@ -135,7 +135,7 @@ class ADSBSquawkView : public OptionTabView {
|
||||
SymField field_squawk{
|
||||
{10 * 8, 2 * 16},
|
||||
4,
|
||||
SymField::SYMFIELD_OCT};
|
||||
SymField::Type::Oct};
|
||||
};
|
||||
|
||||
class ADSBTXThread {
|
||||
@ -235,7 +235,7 @@ class ADSBTxView : public View {
|
||||
SymField sym_icao{
|
||||
{10 * 8, 4 * 8},
|
||||
6,
|
||||
SymField::SYMFIELD_HEX};
|
||||
SymField::Type::Hex};
|
||||
|
||||
Text text_frame{
|
||||
{1 * 8, 29 * 8, 14 * 8, 16},
|
||||
|
@ -48,9 +48,10 @@ APRSTXView::~APRSTXView() {
|
||||
}
|
||||
|
||||
void APRSTXView::start_tx() {
|
||||
// TODO: Clean up this API to take string_views to avoid allocations.
|
||||
make_aprs_frame(
|
||||
sym_source.value_string().c_str(), num_ssid_source.value(),
|
||||
sym_dest.value_string().c_str(), num_ssid_dest.value(),
|
||||
sym_source.to_string().c_str(), num_ssid_source.value(),
|
||||
sym_dest.to_string().c_str(), num_ssid_dest.value(),
|
||||
payload);
|
||||
|
||||
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
|
||||
|
@ -68,7 +68,8 @@ class APRSTXView : public View {
|
||||
SymField sym_source{
|
||||
{7 * 8, 1 * 16},
|
||||
6,
|
||||
SymField::SYMFIELD_ALPHANUM};
|
||||
SymField::Type::Alpha};
|
||||
|
||||
NumberField num_ssid_source{
|
||||
{19 * 8, 1 * 16},
|
||||
2,
|
||||
@ -79,7 +80,7 @@ class APRSTXView : public View {
|
||||
SymField sym_dest{
|
||||
{7 * 8, 2 * 16},
|
||||
6,
|
||||
SymField::SYMFIELD_ALPHANUM};
|
||||
SymField::Type::Alpha};
|
||||
|
||||
NumberField num_ssid_dest{
|
||||
{19 * 8, 2 * 16},
|
||||
|
@ -42,12 +42,12 @@ CoasterPagerView::~CoasterPagerView() {
|
||||
}
|
||||
|
||||
void CoasterPagerView::generate_frame() {
|
||||
uint8_t frame[19];
|
||||
uint32_t c;
|
||||
constexpr uint8_t frame_bytes = 19;
|
||||
uint8_t frame[frame_bytes];
|
||||
|
||||
// Preamble (8 bytes)
|
||||
for (c = 0; c < 8; c++)
|
||||
frame[c] = 0x55; // Isn't this 0xAA ?
|
||||
for (uint8_t c = 0; c < 8; c++)
|
||||
frame[c] = 0x55;
|
||||
|
||||
// Sync word
|
||||
frame[8] = 0x2D;
|
||||
@ -57,11 +57,11 @@ void CoasterPagerView::generate_frame() {
|
||||
frame[10] = 8;
|
||||
|
||||
// Data
|
||||
for (c = 0; c < 8; c++)
|
||||
frame[c + 11] = (sym_data.get_sym(c * 2) << 4) | sym_data.get_sym(c * 2 + 1);
|
||||
auto data_bytes = to_byte_array(sym_data.to_integer());
|
||||
memcpy(&frame[11], data_bytes.data(), data_bytes.size());
|
||||
|
||||
// Copy for baseband
|
||||
memcpy(shared_memory.bb_data.data, frame, 19);
|
||||
memcpy(shared_memory.bb_data.data, frame, frame_bytes);
|
||||
}
|
||||
|
||||
void CoasterPagerView::start_tx() {
|
||||
@ -72,12 +72,7 @@ void CoasterPagerView::start_tx() {
|
||||
baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
|
||||
}
|
||||
|
||||
void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
(void)progress;
|
||||
|
||||
uint16_t address = 0;
|
||||
uint32_t c;
|
||||
|
||||
void CoasterPagerView::on_tx_progress(const uint32_t /*progress*/, const bool done) {
|
||||
if (done) {
|
||||
if (tx_mode == SINGLE) {
|
||||
transmitter_model.disable();
|
||||
@ -85,18 +80,12 @@ void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done)
|
||||
tx_view.set_transmitting(false);
|
||||
} else if (tx_mode == SCAN) {
|
||||
// Increment address
|
||||
|
||||
for (c = 0; c < 4; c++) {
|
||||
address <<= 4;
|
||||
address |= sym_data.get_sym(12 + c);
|
||||
}
|
||||
uint64_t data = sym_data.to_integer();
|
||||
uint16_t address = data & 0xFFFF;
|
||||
|
||||
address++;
|
||||
|
||||
for (c = 0; c < 4; c++) {
|
||||
sym_data.set_sym(15 - c, address & 0x0F);
|
||||
address >>= 4;
|
||||
}
|
||||
data = (data & 0xFFFFFFFFFFFF0000) + address;
|
||||
sym_data.set_value(data);
|
||||
|
||||
start_tx();
|
||||
}
|
||||
@ -104,9 +93,6 @@ void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done)
|
||||
}
|
||||
|
||||
CoasterPagerView::CoasterPagerView(NavigationView& nav) {
|
||||
const uint8_t data_init[8] = {0x44, 0x01, 0x3B, 0x30, 0x30, 0x30, 0x34, 0xBC};
|
||||
uint32_t c;
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_fsktx);
|
||||
|
||||
add_children({&labels,
|
||||
@ -115,9 +101,7 @@ CoasterPagerView::CoasterPagerView(NavigationView& nav) {
|
||||
&text_message,
|
||||
&tx_view});
|
||||
|
||||
// Bytes to nibbles
|
||||
for (c = 0; c < 16; c++)
|
||||
sym_data.set_sym(c, (data_init[c >> 1] >> ((c & 1) ? 0 : 4)) & 0x0F);
|
||||
sym_data.set_value(0x44013B30303034BC);
|
||||
|
||||
checkbox_scan.set_value(false);
|
||||
|
||||
|
@ -68,17 +68,19 @@ class CoasterPagerView : public View {
|
||||
|
||||
SymField sym_data{
|
||||
{7 * 8, 8 * 8},
|
||||
16, // 14 ? 12 ?
|
||||
SymField::SYMFIELD_HEX};
|
||||
16,
|
||||
SymField::Type::Hex};
|
||||
|
||||
Checkbox checkbox_scan{
|
||||
{10 * 8, 14 * 8},
|
||||
4,
|
||||
"Scan"};
|
||||
|
||||
/*ProgressBar progressbar {
|
||||
{ 5 * 8, 12 * 16, 20 * 8, 16 },
|
||||
};*/
|
||||
/*
|
||||
ProgressBar progressbar {
|
||||
{ 5 * 8, 12 * 16, 20 * 8, 16 }};
|
||||
*/
|
||||
|
||||
Text text_message{
|
||||
{5 * 8, 13 * 16, 20 * 8, 16},
|
||||
""};
|
||||
|
@ -49,7 +49,6 @@ EncodersConfigView::EncodersConfigView(
|
||||
&field_clk_step,
|
||||
&field_frameduration,
|
||||
&field_frameduration_step,
|
||||
&symfield_word,
|
||||
&text_format,
|
||||
&waveform});
|
||||
|
||||
@ -64,10 +63,6 @@ EncodersConfigView::EncodersConfigView(
|
||||
options_enctype.set_options(enc_options);
|
||||
options_enctype.set_selected_index(0);
|
||||
|
||||
symfield_word.on_change = [this]() {
|
||||
generate_frame();
|
||||
};
|
||||
|
||||
// Selecting input clock changes symbol and word duration
|
||||
field_clk.on_change = [this](int32_t value) {
|
||||
// value is in kHz, new_value is in us
|
||||
@ -98,33 +93,46 @@ void EncodersConfigView::focus() {
|
||||
}
|
||||
|
||||
void EncodersConfigView::on_type_change(size_t index) {
|
||||
std::string format_string = "";
|
||||
size_t word_length;
|
||||
char symbol_type;
|
||||
// Remove existing SymField controls.
|
||||
for (auto& symfield : symfields_word)
|
||||
remove_child(symfield.get());
|
||||
symfields_word.clear();
|
||||
|
||||
encoder_def = &encoder_defs[index];
|
||||
|
||||
field_clk.set_value(encoder_def->default_speed / 1000);
|
||||
field_repeat_min.set_value(encoder_def->repeat_min);
|
||||
|
||||
// SymField setup
|
||||
word_length = encoder_def->word_length;
|
||||
symfield_word.set_length(word_length);
|
||||
size_t n = 0, i = 0;
|
||||
while (n < word_length) {
|
||||
symbol_type = encoder_def->word_format[i++];
|
||||
if (symbol_type == 'A') {
|
||||
symfield_word.set_symbol_list(n++, encoder_def->address_symbols);
|
||||
format_string += 'A';
|
||||
} else if (symbol_type == 'D') {
|
||||
symfield_word.set_symbol_list(n++, encoder_def->data_symbols);
|
||||
format_string += 'D';
|
||||
// Add new SymFields.
|
||||
Point pos{2 * 8, 9 * 8};
|
||||
std::string format_string;
|
||||
uint8_t word_length = encoder_def->word_length;
|
||||
auto on_change_handler = [this](SymField&) {
|
||||
generate_frame();
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < word_length; i++) {
|
||||
auto symbol_type = encoder_def->word_format[i];
|
||||
symfields_word.push_back(std::make_unique<SymField>(pos, 1));
|
||||
auto& symfield = symfields_word.back();
|
||||
symfield->on_change = on_change_handler;
|
||||
|
||||
switch (symbol_type) {
|
||||
case 'A':
|
||||
symfield->set_symbol_list(encoder_def->address_symbols);
|
||||
format_string += 'A';
|
||||
break;
|
||||
case 'D':
|
||||
symfield->set_symbol_list(encoder_def->data_symbols);
|
||||
format_string += 'D';
|
||||
break;
|
||||
}
|
||||
|
||||
add_child(symfield.get());
|
||||
pos += Point{8, 0};
|
||||
}
|
||||
|
||||
// Ugly :( Pad to erase
|
||||
format_string.append(24 - format_string.size(), ' ');
|
||||
|
||||
text_format.set(format_string);
|
||||
|
||||
generate_frame();
|
||||
@ -146,17 +154,18 @@ void EncodersConfigView::draw_waveform() {
|
||||
}
|
||||
|
||||
void EncodersConfigView::generate_frame() {
|
||||
size_t i = 0;
|
||||
|
||||
frame_fragments.clear();
|
||||
|
||||
size_t i = 0;
|
||||
for (auto c : encoder_def->word_format) {
|
||||
if (c == 'S')
|
||||
frame_fragments += encoder_def->sync;
|
||||
else if (!c)
|
||||
else if (c == '\0')
|
||||
break;
|
||||
else
|
||||
frame_fragments += encoder_def->bit_format[symfield_word.get_sym(i++)];
|
||||
else {
|
||||
auto offset = symfields_word[i++]->get_offset(0);
|
||||
frame_fragments += encoder_def->bit_format[offset];
|
||||
}
|
||||
}
|
||||
|
||||
draw_waveform();
|
||||
|
@ -29,6 +29,9 @@
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
using namespace encoders;
|
||||
|
||||
namespace ui {
|
||||
@ -113,10 +116,7 @@ class EncodersConfigView : public View {
|
||||
{"100", 100},
|
||||
{"1000", 1000}}};
|
||||
|
||||
SymField symfield_word{
|
||||
{2 * 8, 9 * 8},
|
||||
20,
|
||||
SymField::SYMFIELD_DEF};
|
||||
std::vector<std::unique_ptr<SymField>> symfields_word{};
|
||||
|
||||
Text text_format{
|
||||
{2 * 8, 11 * 8, 24 * 8, 16},
|
||||
|
@ -97,12 +97,12 @@ size_t KeyfobView::generate_frame() {
|
||||
uint64_t payload;
|
||||
|
||||
// Symfield word to frame
|
||||
payload = field_payload_a.value_hex_u64();
|
||||
payload = field_payload_a.to_integer();
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
frame[4 - i] = payload & 0xFF;
|
||||
payload >>= 8;
|
||||
}
|
||||
payload = field_payload_b.value_hex_u64();
|
||||
payload = field_payload_b.to_integer();
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
frame[9 - i] = payload & 0xFF;
|
||||
payload >>= 8;
|
||||
@ -136,9 +136,6 @@ void KeyfobView::focus() {
|
||||
}
|
||||
|
||||
KeyfobView::~KeyfobView() {
|
||||
// save app settings
|
||||
settings.save("tx_keyfob", &app_settings);
|
||||
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@ -167,12 +164,12 @@ void KeyfobView::on_make_change(size_t index) {
|
||||
// DEBUG
|
||||
void KeyfobView::update_symfields() {
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
field_payload_a.set_sym(i << 1, frame[i] >> 4);
|
||||
field_payload_a.set_sym((i << 1) + 1, frame[i] & 0x0F);
|
||||
field_payload_a.set_offset(i << 1, frame[i] >> 4);
|
||||
field_payload_a.set_offset((i << 1) + 1, frame[i] & 0x0F);
|
||||
}
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
field_payload_b.set_sym(i << 1, frame[5 + i] >> 4);
|
||||
field_payload_b.set_sym((i << 1) + 1, frame[5 + i] & 0x0F);
|
||||
field_payload_b.set_offset(i << 1, frame[5 + i] >> 4);
|
||||
field_payload_b.set_offset((i << 1) + 1, frame[5 + i] & 0x0F);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,13 +209,6 @@ KeyfobView::KeyfobView(
|
||||
&progressbar,
|
||||
&tx_view});
|
||||
|
||||
// load app settings
|
||||
auto rc = settings.load("tx_keyfob", &app_settings);
|
||||
if (rc == SETTINGS_OK) {
|
||||
transmitter_model.set_rf_amp(app_settings.tx_amp);
|
||||
transmitter_model.set_tx_gain(app_settings.tx_gain);
|
||||
}
|
||||
|
||||
frame[0] = 0x55;
|
||||
update_symfields();
|
||||
|
||||
|
@ -48,7 +48,7 @@ class KeyfobView : public View {
|
||||
OOK_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_keyfob", , app_settings::Mode::TX};
|
||||
"tx_keyfob", app_settings::Mode::TX};
|
||||
|
||||
// 1013210ns / bit
|
||||
static constexpr uint32_t subaru_samples_per_bit = (OOK_SAMPLERATE * 0.00101321);
|
||||
@ -98,11 +98,11 @@ class KeyfobView : public View {
|
||||
SymField field_payload_a{
|
||||
{2 * 8, 5 * 16},
|
||||
10,
|
||||
SymField::SYMFIELD_HEX};
|
||||
SymField::Type::Hex};
|
||||
SymField field_payload_b{
|
||||
{13 * 8, 5 * 16},
|
||||
10,
|
||||
SymField::SYMFIELD_HEX};
|
||||
SymField::Type::Hex};
|
||||
|
||||
Text text_status{
|
||||
{2 * 8, 13 * 16, 128, 16},
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "ui_modemsetup.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
@ -41,15 +40,20 @@ ModemSetupView::ModemSetupView(
|
||||
using options_t = std::vector<option_t>;
|
||||
options_t modem_options;
|
||||
|
||||
add_children({&labels,
|
||||
&field_baudrate,
|
||||
&field_mark,
|
||||
&field_space,
|
||||
&field_repeat,
|
||||
&options_modem,
|
||||
&button_set_modem,
|
||||
&sym_format,
|
||||
&button_save});
|
||||
add_children({
|
||||
&labels,
|
||||
&field_baudrate,
|
||||
&field_mark,
|
||||
&field_space,
|
||||
&field_repeat,
|
||||
&options_modem,
|
||||
&button_set_modem,
|
||||
&sym_format_data,
|
||||
&sym_format_parity,
|
||||
&sym_format_stop,
|
||||
&sym_format_msb,
|
||||
&button_save,
|
||||
});
|
||||
|
||||
// Only list AFSK modems for now
|
||||
for (size_t i = 0; i < MODEM_DEF_COUNT; i++) {
|
||||
@ -59,15 +63,10 @@ ModemSetupView::ModemSetupView(
|
||||
options_modem.set_options(modem_options);
|
||||
options_modem.set_selected_index(0);
|
||||
|
||||
sym_format.set_symbol_list(0, "6789"); // Data bits
|
||||
sym_format.set_symbol_list(1, "NEo"); // Parity
|
||||
sym_format.set_symbol_list(2, "012"); // Stop bits
|
||||
sym_format.set_symbol_list(3, "ML"); // MSB/LSB first
|
||||
|
||||
sym_format.set_sym(0, persistent_memory::serial_format().data_bits - 6);
|
||||
sym_format.set_sym(1, persistent_memory::serial_format().parity);
|
||||
sym_format.set_sym(2, persistent_memory::serial_format().stop_bits);
|
||||
sym_format.set_sym(3, persistent_memory::serial_format().bit_order);
|
||||
sym_format_data.set_offset(0, persistent_memory::serial_format().data_bits - 6);
|
||||
sym_format_parity.set_offset(0, persistent_memory::serial_format().parity);
|
||||
sym_format_stop.set_offset(0, persistent_memory::serial_format().stop_bits);
|
||||
sym_format_msb.set_offset(0, persistent_memory::serial_format().bit_order);
|
||||
|
||||
field_mark.set_value(persistent_memory::afsk_mark_freq());
|
||||
field_space.set_value(persistent_memory::afsk_space_freq());
|
||||
@ -84,18 +83,17 @@ ModemSetupView::ModemSetupView(
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&) {
|
||||
serial_format_t serial_format;
|
||||
|
||||
persistent_memory::set_afsk_mark(field_mark.value());
|
||||
persistent_memory::set_afsk_space(field_space.value());
|
||||
|
||||
persistent_memory::set_modem_baudrate(field_baudrate.value());
|
||||
persistent_memory::set_modem_repeat(field_repeat.value());
|
||||
|
||||
serial_format.data_bits = sym_format.get_sym(0) + 6;
|
||||
serial_format.parity = (parity_enum)sym_format.get_sym(1);
|
||||
serial_format.stop_bits = sym_format.get_sym(2);
|
||||
serial_format.bit_order = (order_enum)sym_format.get_sym(3);
|
||||
serial_format_t serial_format{};
|
||||
serial_format.data_bits = sym_format_data.get_offset(0) + 6;
|
||||
serial_format.parity = (parity_enum)sym_format_parity.get_offset(0);
|
||||
serial_format.stop_bits = sym_format_stop.get_offset(0);
|
||||
serial_format.bit_order = (order_enum)sym_format_msb.get_offset(0);
|
||||
|
||||
persistent_memory::set_serial_format(serial_format);
|
||||
|
||||
|
@ -79,10 +79,25 @@ class ModemSetupView : public View {
|
||||
7,
|
||||
{}};
|
||||
|
||||
SymField sym_format{
|
||||
SymField sym_format_data{
|
||||
{16 * 8, 22 * 8},
|
||||
4,
|
||||
SymField::SYMFIELD_DEF};
|
||||
1,
|
||||
"6789"};
|
||||
|
||||
SymField sym_format_parity{
|
||||
{17 * 8, 22 * 8},
|
||||
1,
|
||||
"NEo"};
|
||||
|
||||
SymField sym_format_stop{
|
||||
{18 * 8, 22 * 8},
|
||||
1,
|
||||
"012"};
|
||||
|
||||
SymField sym_format_msb{
|
||||
{19 * 8, 22 * 8},
|
||||
1,
|
||||
"ML"};
|
||||
|
||||
Button button_set_modem{
|
||||
{23 * 8, 6 * 8 - 4, 6 * 8, 24},
|
||||
|
@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "ui_numbers.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
@ -75,7 +76,7 @@ void NumbersStationView::prepare_audio() {
|
||||
return;
|
||||
}
|
||||
|
||||
code = symfield_code.get_sym(code_index);
|
||||
code = symfield_code.get_offset(code_index);
|
||||
|
||||
if (code >= 10) {
|
||||
memset(audio_buffer, 0, 1024);
|
||||
@ -144,7 +145,7 @@ void NumbersStationView::on_tick_second() {
|
||||
armed_blink = not armed_blink;
|
||||
|
||||
if (armed_blink)
|
||||
check_armed.set_style(&style_red);
|
||||
check_armed.set_style(&Styles::red);
|
||||
else
|
||||
check_armed.set_style(&style());
|
||||
|
||||
@ -152,14 +153,12 @@ void NumbersStationView::on_tick_second() {
|
||||
}
|
||||
|
||||
void NumbersStationView::on_voice_changed(size_t index) {
|
||||
std::string code_list = "";
|
||||
std::string code_list;
|
||||
|
||||
for (const auto& wavs : voices[index].available_wavs)
|
||||
code_list += wavs.code;
|
||||
|
||||
for (uint32_t c = 0; c < 25; c++)
|
||||
symfield_code.set_symbol_list(c, code_list);
|
||||
|
||||
symfield_code.set_symbol_list(code_list);
|
||||
current_voice = &voices[index];
|
||||
}
|
||||
|
||||
@ -265,17 +264,17 @@ NumbersStationView::NumbersStationView(
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
symfield_code.set_sym(0, 10);
|
||||
symfield_code.set_sym(1, 3);
|
||||
symfield_code.set_sym(2, 4);
|
||||
symfield_code.set_sym(3, 11);
|
||||
symfield_code.set_sym(4, 6);
|
||||
symfield_code.set_sym(5, 1);
|
||||
symfield_code.set_sym(6, 9);
|
||||
symfield_code.set_sym(7, 7);
|
||||
symfield_code.set_sym(8, 8);
|
||||
symfield_code.set_sym(9, 0);
|
||||
symfield_code.set_sym(10, 12); // End
|
||||
symfield_code.set_offset(0, 10);
|
||||
symfield_code.set_offset(1, 3);
|
||||
symfield_code.set_offset(2, 4);
|
||||
symfield_code.set_offset(3, 11);
|
||||
symfield_code.set_offset(4, 6);
|
||||
symfield_code.set_offset(5, 1);
|
||||
symfield_code.set_offset(6, 9);
|
||||
symfield_code.set_offset(7, 7);
|
||||
symfield_code.set_offset(8, 8);
|
||||
symfield_code.set_offset(9, 0);
|
||||
symfield_code.set_offset(10, 12); // End
|
||||
|
||||
/*
|
||||
rtc::RTC datetime;
|
||||
|
@ -142,16 +142,19 @@ class NumbersStationView : public View {
|
||||
SymField symfield_code{
|
||||
{1 * 8, 10 * 8},
|
||||
25,
|
||||
SymField::SYMFIELD_DEF};
|
||||
SymField::Type::Custom};
|
||||
|
||||
Checkbox check_armed{
|
||||
{2 * 8, 13 * 16},
|
||||
5,
|
||||
"Armed"};
|
||||
/*Button button_tx_now {
|
||||
{ 18 * 8, 13 * 16, 10 * 8, 32 },
|
||||
"TX now"
|
||||
};*/
|
||||
|
||||
/*
|
||||
Button button_tx_now {
|
||||
{ 18 * 8, 13 * 16, 10 * 8, 32 },
|
||||
"TX now"};
|
||||
*/
|
||||
|
||||
Button button_exit{
|
||||
{21 * 8, 16 * 16, 64, 32},
|
||||
"Exit"};
|
||||
|
@ -56,7 +56,7 @@ bool POCSAGTXView::start_tx() {
|
||||
pocsag::BitRate bitrate;
|
||||
std::vector<uint32_t> codewords;
|
||||
|
||||
address = field_address.value_dec_u32();
|
||||
address = field_address.to_integer();
|
||||
if (address > 0x1FFFFFU) {
|
||||
nav_.display_modal("Bad address", "Address must be less\nthan 2097152.");
|
||||
return false;
|
||||
@ -137,12 +137,7 @@ POCSAGTXView::POCSAGTXView(
|
||||
options_bitrate.set_selected_index(1); // 1200bps
|
||||
options_type.set_selected_index(0); // Address only
|
||||
|
||||
// TODO: set_value for whole symfield
|
||||
uint32_t reload_address = persistent_memory::pocsag_last_address();
|
||||
for (uint32_t c = 0; c < 7; c++) {
|
||||
field_address.set_sym(6 - c, reload_address % 10);
|
||||
reload_address /= 10;
|
||||
}
|
||||
field_address.set_value(persistent_memory::pocsag_last_address());
|
||||
|
||||
options_type.on_change = [this](size_t, int32_t i) {
|
||||
if (i == 2)
|
||||
|
@ -94,8 +94,7 @@ class POCSAGTXView : public View {
|
||||
|
||||
SymField field_address{
|
||||
{11 * 8, 6 * 8},
|
||||
7,
|
||||
SymField::SYMFIELD_DEC};
|
||||
7};
|
||||
|
||||
OptionsField options_type{
|
||||
{11 * 8, 8 * 8},
|
||||
|
@ -164,7 +164,7 @@ RDSView::~RDSView() {
|
||||
}
|
||||
|
||||
void RDSView::start_tx() {
|
||||
rds_flags.PI_code = sym_pi_code.value_hex_u64();
|
||||
rds_flags.PI_code = sym_pi_code.to_integer();
|
||||
rds_flags.PTY = options_pty.selected_index_value();
|
||||
rds_flags.DI = view_PSN.mono_stereo ? 1 : 0;
|
||||
rds_flags.TP = check_TP.value();
|
||||
@ -212,12 +212,9 @@ RDSView::RDSView(
|
||||
|
||||
check_TP.set_value(true);
|
||||
|
||||
sym_pi_code.set_sym(0, 0xF);
|
||||
sym_pi_code.set_sym(1, 0x3);
|
||||
sym_pi_code.set_sym(2, 0xE);
|
||||
sym_pi_code.set_sym(3, 0x0);
|
||||
sym_pi_code.on_change = [this]() {
|
||||
rds_flags.PI_code = sym_pi_code.value_hex_u64();
|
||||
sym_pi_code.set_value(0xF3E0);
|
||||
sym_pi_code.on_change = [this](SymField&) {
|
||||
rds_flags.PI_code = sym_pi_code.to_integer();
|
||||
};
|
||||
|
||||
options_pty.set_selected_index(0); // None
|
||||
|
@ -287,7 +287,7 @@ class RDSView : public View {
|
||||
SymField sym_pi_code{
|
||||
{13 * 8, 28 + 16},
|
||||
4,
|
||||
SymField::SYMFIELD_HEX};
|
||||
SymField::Type::Hex};
|
||||
|
||||
/*OptionsField options_coverage {
|
||||
{ 17 * 8, 32 + 8 },
|
||||
|
@ -50,7 +50,7 @@ void SigGenView::update_config() {
|
||||
}
|
||||
|
||||
void SigGenView::update_tone() {
|
||||
baseband::set_siggen_tone(symfield_tone.value_dec_u32());
|
||||
baseband::set_siggen_tone(symfield_tone.to_integer());
|
||||
}
|
||||
|
||||
void SigGenView::start_tx() {
|
||||
@ -97,8 +97,8 @@ SigGenView::SigGenView(
|
||||
|
||||
field_stop.set_value(1);
|
||||
|
||||
symfield_tone.set_sym(1, 1); // Default: 1000 Hz
|
||||
symfield_tone.on_change = [this]() {
|
||||
symfield_tone.set_value(1000); // Default: 1000 Hz
|
||||
symfield_tone.on_change = [this](SymField&) {
|
||||
if (auto_update)
|
||||
update_tone();
|
||||
};
|
||||
|
@ -92,9 +92,8 @@ class SigGenView : public View {
|
||||
""};
|
||||
|
||||
SymField symfield_tone{
|
||||
{13 * 8, 7 * 8},
|
||||
5,
|
||||
SymField::SYMFIELD_DEC};
|
||||
{12 * 8, 7 * 8},
|
||||
5};
|
||||
|
||||
Button button_update{
|
||||
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
/* This takes a pointer to the end of a buffer
|
||||
* and fills it backwards towards the front.
|
||||
* The return value 'q' is a pointer to the start.
|
||||
@ -230,29 +232,31 @@ std::string to_string_time_ms(const uint32_t ms) {
|
||||
return final_str;
|
||||
}
|
||||
|
||||
static void to_string_hex_internal(char* p, const uint64_t n, const int32_t l) {
|
||||
const uint32_t d = n & 0xf;
|
||||
p[l] = (d > 9) ? (d + 55) : (d + 48);
|
||||
if (l > 0) {
|
||||
to_string_hex_internal(p, n >> 4, l - 1);
|
||||
}
|
||||
static char* to_string_hex_internal(char* ptr, uint64_t value, uint8_t length) {
|
||||
if (length == 0)
|
||||
return ptr;
|
||||
|
||||
*(--ptr) = uint_to_char(value & 0xF, 16);
|
||||
return to_string_hex_internal(ptr, value >> 4, length - 1);
|
||||
}
|
||||
|
||||
std::string to_string_hex(const uint64_t n, int32_t l) {
|
||||
char p[32];
|
||||
std::string to_string_hex(uint64_t value, int32_t length) {
|
||||
constexpr uint8_t buffer_length = 33;
|
||||
char buffer[buffer_length];
|
||||
|
||||
l = std::min<int32_t>(l, 31);
|
||||
to_string_hex_internal(p, n, l - 1);
|
||||
p[l] = 0;
|
||||
return p;
|
||||
char* ptr = &buffer[buffer_length - 1];
|
||||
*ptr = '\0';
|
||||
|
||||
length = std::min<uint8_t>(buffer_length - 1, length);
|
||||
return to_string_hex_internal(ptr, value, length);
|
||||
}
|
||||
|
||||
std::string to_string_hex_array(uint8_t* const array, const int32_t l) {
|
||||
std::string str_return = "";
|
||||
uint8_t bytes;
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length) {
|
||||
std::string str_return;
|
||||
str_return.reserve(length);
|
||||
|
||||
for (bytes = 0; bytes < l; bytes++)
|
||||
str_return += to_string_hex(array[bytes], 2);
|
||||
for (uint8_t i = 0; i < length; i++)
|
||||
str_return += to_string_hex(array[i], 2);
|
||||
|
||||
return str_return;
|
||||
}
|
||||
@ -364,3 +368,26 @@ std::string trimr(std::string_view str) {
|
||||
std::string truncate(std::string_view str, size_t length) {
|
||||
return std::string{str.length() <= length ? str : str.substr(0, length)};
|
||||
}
|
||||
|
||||
uint8_t char_to_uint(char c, uint8_t radix) {
|
||||
uint8_t v = 0;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
v = c - '0';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
v = c - 'A' + 10; // A is dec: 10
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
v = c - 'a' + 10; // A is dec: 10
|
||||
|
||||
return v < radix ? v : 0;
|
||||
}
|
||||
|
||||
char uint_to_char(uint8_t val, uint8_t radix) {
|
||||
if (val >= radix)
|
||||
return 0;
|
||||
|
||||
if (val < 10)
|
||||
return '0' + val;
|
||||
else
|
||||
return 'A' + val - 10; // A is dec: 10
|
||||
}
|
||||
|
@ -50,14 +50,19 @@ char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length)
|
||||
std::string to_string_dec_int(int64_t n);
|
||||
std::string to_string_dec_uint(uint64_t n);
|
||||
|
||||
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
|
||||
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
||||
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
||||
std::string to_string_decimal(float decimal, int8_t precision);
|
||||
|
||||
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
||||
std::string to_string_hex_array(uint8_t* const array, const int32_t l = 0);
|
||||
std::string to_string_hex(uint64_t n, int32_t length);
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length);
|
||||
|
||||
/* Helper to select length based on type size. */
|
||||
template <typename T>
|
||||
std::string to_string_hex(T n) {
|
||||
return to_string_hex(n, sizeof(T) * 2); // Two digits/byte.
|
||||
}
|
||||
|
||||
std::string to_string_freq(const uint64_t f);
|
||||
std::string to_string_short_freq(const uint64_t f);
|
||||
@ -78,4 +83,12 @@ std::string trim(std::string_view str); // Remove whitespace at ends.
|
||||
std::string trimr(std::string_view str); // Remove trailing spaces
|
||||
std::string truncate(std::string_view, size_t length);
|
||||
|
||||
/* Gets the int value for a character given the radix.
|
||||
* e.g. '5' => 5, 'D' => 13. Out of bounds => 0. */
|
||||
uint8_t char_to_uint(char c, uint8_t radix = 10);
|
||||
|
||||
/* Gets the int value for a character given the radix.
|
||||
* e.g. 5 => '5', 13 => 'D'. Out of bounds => '\0'. */
|
||||
char uint_to_char(uint8_t val, uint8_t radix = 10);
|
||||
|
||||
#endif /*__STRING_FORMAT_H__*/
|
||||
|
@ -37,6 +37,29 @@ struct Style {
|
||||
Style invert() const;
|
||||
};
|
||||
|
||||
/* Sometimes mutation is just the more readable thing... */
|
||||
struct MutableStyle {
|
||||
const Font* font;
|
||||
Color background;
|
||||
Color foreground;
|
||||
|
||||
MutableStyle(const Style& s)
|
||||
: font{&s.font},
|
||||
background{s.background},
|
||||
foreground{s.foreground} {}
|
||||
|
||||
void invert() {
|
||||
std::swap(background, foreground);
|
||||
}
|
||||
|
||||
operator Style() const {
|
||||
return {
|
||||
.font = *font,
|
||||
.background = background,
|
||||
.foreground = foreground};
|
||||
}
|
||||
};
|
||||
|
||||
class Widget;
|
||||
|
||||
class Painter {
|
||||
|
@ -1813,7 +1813,7 @@ void NumberField::set_value(int32_t new_value, bool trigger_change) {
|
||||
new_value = range.second + new_value + 1;
|
||||
}
|
||||
|
||||
new_value = clip_value(new_value);
|
||||
new_value = clip(new_value, range.first, range.second);
|
||||
|
||||
if (new_value != value()) {
|
||||
value_ = new_value;
|
||||
@ -1868,169 +1868,182 @@ bool NumberField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t NumberField::clip_value(int32_t value) {
|
||||
if (value > range.second) {
|
||||
value = range.second;
|
||||
}
|
||||
if (value < range.first) {
|
||||
value = range.first;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/* SymField **************************************************************/
|
||||
|
||||
SymField::SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
symfield_type type)
|
||||
: Widget{{parent_pos, {static_cast<ui::Dim>(8 * length), 16}}},
|
||||
length_{length},
|
||||
type_{type} {
|
||||
uint32_t c;
|
||||
Type type,
|
||||
bool explicit_edits)
|
||||
: Widget{{parent_pos, {char_width * (int)length, 16}}},
|
||||
type_{type},
|
||||
explicit_edits_{explicit_edits} {
|
||||
if (length == 0)
|
||||
length = 1;
|
||||
|
||||
// Auto-init
|
||||
if (type == SYMFIELD_OCT) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, "01234567");
|
||||
} else if (type == SYMFIELD_DEC) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, "0123456789");
|
||||
} else if (type == SYMFIELD_HEX) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, "0123456789ABCDEF");
|
||||
} else if (type == SYMFIELD_ALPHANUM) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
selected_ = length - 1;
|
||||
value_.resize(length);
|
||||
|
||||
switch (type) {
|
||||
case Type::Oct:
|
||||
set_symbol_list("01234567");
|
||||
break;
|
||||
|
||||
case Type::Dec:
|
||||
set_symbol_list("0123456789");
|
||||
break;
|
||||
|
||||
case Type::Hex:
|
||||
set_symbol_list("0123456789ABCDEF");
|
||||
break;
|
||||
|
||||
case Type::Alpha:
|
||||
set_symbol_list(" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
break;
|
||||
|
||||
default:
|
||||
set_symbol_list("01");
|
||||
break;
|
||||
}
|
||||
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
uint16_t SymField::concatenate_4_octal_u16() {
|
||||
// input array 4 octal digits , return 12 bits, same order, trippled A4-A2-A1|B4-B2-B1|C4-C2-C1|D4-D2-D1
|
||||
uint32_t mul = 1;
|
||||
uint16_t v = 0;
|
||||
|
||||
if (type_ == SYMFIELD_OCT) {
|
||||
for (uint32_t c = 0; c < length_; c++) {
|
||||
v += values_[(length_ - 1 - c)] * mul;
|
||||
mul *= 8; // shift 3 bits to the right every new octal squawk digit
|
||||
}
|
||||
return v;
|
||||
} else
|
||||
return 0;
|
||||
SymField::SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
std::string symbol_list,
|
||||
bool explicit_edits)
|
||||
: SymField{parent_pos, length, Type::Custom, explicit_edits} {
|
||||
set_symbol_list(std::move(symbol_list));
|
||||
}
|
||||
|
||||
uint32_t SymField::value_dec_u32() {
|
||||
uint32_t mul = 1, v = 0;
|
||||
|
||||
if (type_ == SYMFIELD_DEC) {
|
||||
for (uint32_t c = 0; c < length_; c++) {
|
||||
v += values_[(length_ - 1 - c)] * mul;
|
||||
mul *= 10;
|
||||
}
|
||||
return v;
|
||||
} else
|
||||
char SymField::get_symbol(size_t index) const {
|
||||
if (index >= value_.length())
|
||||
return 0;
|
||||
|
||||
return value_[index];
|
||||
}
|
||||
|
||||
uint64_t SymField::value_hex_u64() {
|
||||
void SymField::set_symbol(size_t index, char symbol) {
|
||||
if (index >= value_.length())
|
||||
return;
|
||||
|
||||
set_symbol_internal(index, ensure_valid(symbol));
|
||||
}
|
||||
|
||||
size_t SymField::get_offset(size_t index) const {
|
||||
if (index >= value_.length())
|
||||
return 0;
|
||||
|
||||
// NB: Linear search - symbol lists are small.
|
||||
return symbols_.find(value_[index]);
|
||||
}
|
||||
|
||||
void SymField::set_offset(size_t index, size_t offset) {
|
||||
if (index >= value_.length() || offset >= symbols_.length())
|
||||
return;
|
||||
|
||||
set_symbol_internal(index, symbols_[offset]);
|
||||
}
|
||||
|
||||
void SymField::set_symbol_list(std::string symbol_list) {
|
||||
if (symbol_list.length() == 0)
|
||||
return;
|
||||
|
||||
symbols_ = std::move(symbol_list);
|
||||
ensure_all_symbols();
|
||||
}
|
||||
|
||||
void SymField::set_value(uint64_t value) {
|
||||
auto v = value;
|
||||
uint8_t radix = get_radix();
|
||||
|
||||
for (int i = value_.length() - 1; i >= 0; --i) {
|
||||
uint8_t temp = v % radix;
|
||||
value_[i] = uint_to_char(temp, radix);
|
||||
v /= radix;
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_value(std::string_view value) {
|
||||
// Is new value too long?
|
||||
// TODO: Truncate instead? Which end?
|
||||
if (value.length() > value_.length())
|
||||
return;
|
||||
|
||||
// Right-align string in field.
|
||||
auto left_padding = value_.length() - value.length();
|
||||
value_ = std::string(static_cast<size_t>(left_padding), '\0') + std::string{value};
|
||||
ensure_all_symbols();
|
||||
}
|
||||
|
||||
uint64_t SymField::to_integer() const {
|
||||
uint64_t v = 0;
|
||||
uint64_t mul = 1;
|
||||
uint8_t radix = get_radix();
|
||||
|
||||
if (type_ != SYMFIELD_DEF) {
|
||||
for (uint32_t c = 0; c < length_; c++)
|
||||
v += (uint64_t)(values_[c]) << (4 * (length_ - 1 - c));
|
||||
return v;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string SymField::value_string() {
|
||||
std::string return_string{""};
|
||||
|
||||
if (type_ == SYMFIELD_ALPHANUM) {
|
||||
for (uint32_t c = 0; c < length_; c++) {
|
||||
return_string += symbol_list_[0][values_[c]];
|
||||
}
|
||||
for (int i = value_.length() - 1; i >= 0; --i) {
|
||||
auto temp = char_to_uint(value_[i], radix);
|
||||
v += temp * mul;
|
||||
mul *= radix;
|
||||
}
|
||||
|
||||
return return_string;
|
||||
return v;
|
||||
}
|
||||
|
||||
uint32_t SymField::get_sym(const uint32_t index) {
|
||||
if (index >= length_) return 0;
|
||||
|
||||
return values_[index];
|
||||
}
|
||||
|
||||
void SymField::set_sym(const uint32_t index, const uint32_t new_value) {
|
||||
if (index >= length_) return;
|
||||
|
||||
uint32_t clipped_value = clip_value(index, new_value);
|
||||
|
||||
if (clipped_value != values_[index]) {
|
||||
values_[index] = clipped_value;
|
||||
if (on_change) {
|
||||
on_change();
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_length(const uint32_t new_length) {
|
||||
if ((new_length <= 32) && (new_length != length_)) {
|
||||
prev_length_ = length_;
|
||||
length_ = new_length;
|
||||
|
||||
// Clip eventual garbage from previous shorter word
|
||||
for (size_t n = 0; n < length_; n++)
|
||||
set_sym(n, values_[n]);
|
||||
|
||||
erase_prev_ = true;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_symbol_list(const uint32_t index, const std::string symbol_list) {
|
||||
if (index >= length_) return;
|
||||
|
||||
symbol_list_[index] = symbol_list;
|
||||
|
||||
// Re-clip symbol's value
|
||||
set_sym(index, values_[index]);
|
||||
const std::string& SymField::to_string() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
void SymField::paint(Painter& painter) {
|
||||
Point pt_draw = screen_pos();
|
||||
Point p = screen_pos();
|
||||
|
||||
if (erase_prev_) {
|
||||
painter.fill_rectangle({pt_draw, {(int)prev_length_ * 8, 16}}, Color::black());
|
||||
erase_prev_ = false;
|
||||
}
|
||||
for (size_t n = 0; n < value_.length(); n++) {
|
||||
auto c = value_[n];
|
||||
MutableStyle paint_style{style()};
|
||||
|
||||
for (size_t n = 0; n < length_; n++) {
|
||||
const auto text = symbol_list_[n].substr(values_[n], 1);
|
||||
// Only highlight while focused.
|
||||
if (has_focus()) {
|
||||
if (explicit_edits_) {
|
||||
// Invert the whole field on focus if explicit edits is enabled.
|
||||
paint_style.invert();
|
||||
} else if (n == selected_) {
|
||||
// Otherwise only highlight the selected symbol.
|
||||
paint_style.invert();
|
||||
}
|
||||
|
||||
const auto paint_style = (has_focus() && (n == selected_)) ? style().invert() : style();
|
||||
if (editing_ && n == selected_) {
|
||||
// Use 'bg_blue' style to indicate in editing mode.
|
||||
paint_style.foreground = Color::white();
|
||||
paint_style.background = Color::blue();
|
||||
}
|
||||
}
|
||||
|
||||
painter.draw_string(
|
||||
pt_draw,
|
||||
paint_style,
|
||||
text);
|
||||
|
||||
pt_draw += {8, 0};
|
||||
painter.draw_char(p, paint_style, c);
|
||||
p += {8, 0};
|
||||
}
|
||||
}
|
||||
|
||||
bool SymField::on_key(const KeyEvent key) {
|
||||
bool SymField::on_key(KeyEvent key) {
|
||||
// If explicit edits are enabled, only Select is handled when not in edit mode.
|
||||
if (explicit_edits_ && !editing_) {
|
||||
switch (key) {
|
||||
case KeyEvent::Select:
|
||||
editing_ = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case KeyEvent::Select:
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
editing_ = !editing_;
|
||||
set_dirty();
|
||||
return true;
|
||||
|
||||
case KeyEvent::Left:
|
||||
if (selected_ > 0) {
|
||||
@ -2041,13 +2054,27 @@ bool SymField::on_key(const KeyEvent key) {
|
||||
break;
|
||||
|
||||
case KeyEvent::Right:
|
||||
if (selected_ < (length_ - 1)) {
|
||||
if (selected_ < (value_.length() - 1)) {
|
||||
selected_++;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent::Up:
|
||||
if (editing_) {
|
||||
on_encoder(1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent::Down:
|
||||
if (editing_) {
|
||||
on_encoder(-1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -2055,29 +2082,66 @@ bool SymField::on_key(const KeyEvent key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_value = (int)values_[selected_] + delta;
|
||||
bool SymField::on_encoder(EncoderEvent delta) {
|
||||
if (explicit_edits_ && !editing_)
|
||||
return false;
|
||||
|
||||
if (new_value >= 0)
|
||||
set_sym(selected_, values_[selected_] + delta);
|
||||
// TODO: Wrapping or carrying might be nice.
|
||||
int offset = get_offset(selected_) + delta;
|
||||
|
||||
offset = clip<int>(offset, 0, symbols_.length() - 1);
|
||||
set_offset(selected_, offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymField::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start) {
|
||||
bool SymField::on_touch(TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t SymField::clip_value(const uint32_t index, const uint32_t value) {
|
||||
size_t symbol_count = symbol_list_[index].length() - 1;
|
||||
char SymField::ensure_valid(char symbol) const {
|
||||
// NB: Linear search - symbol lists are small.
|
||||
auto pos = symbols_.find(symbol);
|
||||
return pos != std::string::npos ? symbol : symbols_[0];
|
||||
}
|
||||
|
||||
if (value > symbol_count)
|
||||
return symbol_count;
|
||||
else
|
||||
return value;
|
||||
void SymField::ensure_all_symbols() {
|
||||
auto temp = value_;
|
||||
|
||||
for (auto& c : value_)
|
||||
c = ensure_valid(c);
|
||||
|
||||
if (temp != value_) {
|
||||
if (on_change)
|
||||
on_change(*this);
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_symbol_internal(size_t index, char symbol) {
|
||||
if (value_[index] == symbol)
|
||||
return;
|
||||
|
||||
value_[index] = symbol;
|
||||
if (on_change)
|
||||
on_change(*this);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
uint8_t SymField::get_radix() const {
|
||||
switch (type_) {
|
||||
case Type::Oct:
|
||||
return 8;
|
||||
case Type::Dec:
|
||||
return 10;
|
||||
case Type::Hex:
|
||||
return 16;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Waveform **************************************************************/
|
||||
|
@ -751,52 +751,93 @@ class NumberField : public Widget {
|
||||
const char fill_char;
|
||||
int32_t value_{0};
|
||||
bool can_loop{};
|
||||
|
||||
int32_t clip_value(int32_t value);
|
||||
};
|
||||
|
||||
/* A widget that allows for character-by-character editing of its value. */
|
||||
class SymField : public Widget {
|
||||
public:
|
||||
std::function<void(SymField&)> on_select{};
|
||||
std::function<void()> on_change{};
|
||||
std::function<void(SymField&)> on_change{};
|
||||
|
||||
enum symfield_type {
|
||||
SYMFIELD_OCT,
|
||||
SYMFIELD_DEC,
|
||||
SYMFIELD_HEX,
|
||||
SYMFIELD_ALPHANUM,
|
||||
SYMFIELD_DEF // User DEFined
|
||||
enum class Type {
|
||||
Custom,
|
||||
Oct,
|
||||
Dec,
|
||||
Hex,
|
||||
Alpha,
|
||||
};
|
||||
|
||||
SymField(Point parent_pos, size_t length, symfield_type type);
|
||||
/* When "explicit_edits" is true, the field must be selected before it can
|
||||
* be modified. This means that "slots" are not individually highlighted.
|
||||
* This makes navigation on Views with SymFields easier because the
|
||||
* whole control can be skipped over instead of one "slot" at a time. */
|
||||
|
||||
SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
Type type = Type::Dec,
|
||||
bool explicit_edits = false);
|
||||
|
||||
SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
std::string symbol_list,
|
||||
bool explicit_edits = false);
|
||||
|
||||
SymField(const SymField&) = delete;
|
||||
SymField(SymField&&) = delete;
|
||||
|
||||
uint32_t get_sym(const uint32_t index);
|
||||
void set_sym(const uint32_t index, const uint32_t new_value);
|
||||
void set_length(const uint32_t new_length);
|
||||
void set_symbol_list(const uint32_t index, const std::string symbol_list);
|
||||
uint16_t concatenate_4_octal_u16();
|
||||
uint32_t value_dec_u32();
|
||||
uint64_t value_hex_u64();
|
||||
std::string value_string();
|
||||
/* Gets the symbol (character) at the specified index. */
|
||||
char get_symbol(size_t index) const;
|
||||
|
||||
/* Sets the symbol (character) at the specified index. */
|
||||
void set_symbol(size_t index, char symbol);
|
||||
|
||||
/* Gets the symbol's offset in the symbol list at the specified index. */
|
||||
size_t get_offset(size_t index) const;
|
||||
|
||||
/* Sets the symbol's offset in the symbol list at the specified index. */
|
||||
void set_offset(size_t index, size_t offset);
|
||||
|
||||
/* Sets the list of allowable symbols for the field. */
|
||||
void set_symbol_list(std::string symbol_list);
|
||||
|
||||
/* Sets the integer value of the field. Only works for OCT/DEC/HEX. */
|
||||
void set_value(uint64_t value);
|
||||
|
||||
/* Sets the string value of the field. Characters that are not in
|
||||
* the symbol list will be set to the first symbol in the symbol list. */
|
||||
void set_value(std::string_view value);
|
||||
|
||||
/* Gets the integer value of the field for OCT/DEC/HEX. */
|
||||
uint64_t to_integer() const;
|
||||
|
||||
/* Gets the string value of the field. */
|
||||
const std::string& to_string() const;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
|
||||
private:
|
||||
std::string symbol_list_[32] = {"01"}; // Failsafe init
|
||||
uint32_t values_[32] = {0};
|
||||
uint32_t selected_ = 0;
|
||||
size_t length_, prev_length_ = 0;
|
||||
bool erase_prev_ = false;
|
||||
symfield_type type_{};
|
||||
/* Ensure the specified symbol is in the symbol list. */
|
||||
char ensure_valid(char symbol) const;
|
||||
|
||||
int32_t clip_value(const uint32_t index, const uint32_t value);
|
||||
/* Ensures all the symbols are valid. */
|
||||
void ensure_all_symbols();
|
||||
|
||||
/* Sets without validation and fires on_change if necessary. */
|
||||
void set_symbol_internal(size_t index, char symbol);
|
||||
|
||||
/* Gets the radix for the current Type. */
|
||||
uint8_t get_radix() const;
|
||||
|
||||
std::string symbols_{};
|
||||
std::string value_{};
|
||||
Type type_ = Type::Custom;
|
||||
size_t selected_ = 0;
|
||||
bool editing_ = false;
|
||||
bool explicit_edits_ = true;
|
||||
};
|
||||
|
||||
class Waveform : public Widget {
|
||||
|
@ -22,12 +22,13 @@
|
||||
#ifndef __UTILITY_H__
|
||||
#define __UTILITY_H__
|
||||
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#define LOCATE_IN_RAM __attribute__((section(".ramtext")))
|
||||
|
||||
@ -141,6 +142,16 @@ constexpr std::enable_if_t<is_flags_type_v<TEnum>, bool> flags_enabled(TEnum val
|
||||
return (i_value & i_flags) == i_flags;
|
||||
}
|
||||
|
||||
// TODO: Constrain to integrals?
|
||||
/* Converts an integer into a byte array. */
|
||||
template <typename T, size_t N = sizeof(T)>
|
||||
constexpr std::array<uint8_t, N> to_byte_array(T value) {
|
||||
std::array<uint8_t, N> bytes{};
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
bytes[i] = (value >> ((N - i - 1) * 8)) & 0xFF;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* Returns value constrained to min and max. */
|
||||
template <class T>
|
||||
constexpr const T& clip(const T& value, const T& minimum, const T& maximum) {
|
||||
|
@ -83,6 +83,54 @@ TEST_CASE("to_string_rounded_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 9), "1234.567891");
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_hex returns correct value.") {
|
||||
CHECK_EQ(to_string_hex(0x0, 1), "0");
|
||||
CHECK_EQ(to_string_hex(0x0, 4), "0000");
|
||||
CHECK_EQ(to_string_hex(0x1, 1), "1");
|
||||
CHECK_EQ(to_string_hex(0xA, 1), "A");
|
||||
CHECK_EQ(to_string_hex(0xA0, 2), "A0");
|
||||
CHECK_EQ(to_string_hex(0xA1, 2), "A1");
|
||||
CHECK_EQ(to_string_hex(0xA1FE, 2), "FE"); // NB: Truncates front.
|
||||
CHECK_EQ(to_string_hex(0x12345678, 8), "12345678");
|
||||
CHECK_EQ(to_string_hex(0x9ABCDEF0, 8), "9ABCDEF0");
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_hex returns correct value based on type.") {
|
||||
CHECK_EQ(to_string_hex<uint8_t>(0xA), "0A");
|
||||
CHECK_EQ(to_string_hex<uint8_t>(0xFE), "FE");
|
||||
CHECK_EQ(to_string_hex<uint16_t>(0x00FE), "00FE");
|
||||
CHECK_EQ(to_string_hex<uint16_t>(0xCAFE), "CAFE");
|
||||
CHECK_EQ(to_string_hex<uint32_t>(0xCAFE), "0000CAFE");
|
||||
CHECK_EQ(to_string_hex<uint32_t>(0xCAFEBEEF), "CAFEBEEF");
|
||||
CHECK_EQ(to_string_hex<uint64_t>(0xCAFEBEEF), "00000000CAFEBEEF");
|
||||
CHECK_EQ(to_string_hex<uint64_t>(0xC0FFEE00CAFEBABE), "C0FFEE00CAFEBABE");
|
||||
}
|
||||
|
||||
TEST_CASE("char_to_uint returns correct value.") {
|
||||
CHECK_EQ(char_to_uint('0', 10), 0);
|
||||
CHECK_EQ(char_to_uint('5', 10), 5);
|
||||
CHECK_EQ(char_to_uint('8', 8), 0); // Bad value given radix.
|
||||
CHECK_EQ(char_to_uint('8', 10), 8);
|
||||
CHECK_EQ(char_to_uint('8', 16), 8);
|
||||
CHECK_EQ(char_to_uint('A', 10), 0); // Bad value given radix.
|
||||
CHECK_EQ(char_to_uint('A', 16), 0xA);
|
||||
CHECK_EQ(char_to_uint('a', 16), 0xA); // Lowercase.
|
||||
CHECK_EQ(char_to_uint('f', 16), 0xF); // Lowercase.
|
||||
CHECK_EQ(char_to_uint('G', 16), 0); // Bad value given radix.
|
||||
}
|
||||
|
||||
TEST_CASE("uint_to_char returns correct value.") {
|
||||
CHECK_EQ(uint_to_char(0, 10), '0');
|
||||
CHECK_EQ(uint_to_char(5, 10), '5');
|
||||
CHECK_EQ(uint_to_char(8, 8), '\0'); // Bad value given radix.
|
||||
CHECK_EQ(uint_to_char(8, 10), '8');
|
||||
CHECK_EQ(uint_to_char(8, 16), '8');
|
||||
CHECK_EQ(uint_to_char(10, 10), '\0'); // Bad value given radix.
|
||||
CHECK_EQ(uint_to_char(10, 16), 'A');
|
||||
CHECK_EQ(uint_to_char(15, 16), 'F');
|
||||
CHECK_EQ(uint_to_char(16, 16), '\0'); // Bad value given radix.
|
||||
}
|
||||
|
||||
TEST_CASE("trim removes whitespace.") {
|
||||
CHECK(trim(" foo\n") == "foo");
|
||||
}
|
||||
|
@ -71,4 +71,22 @@ TEST_CASE("ms_duration should return duration.") {
|
||||
|
||||
TEST_CASE("ms_duration not fault when passed zero.") {
|
||||
CHECK_EQ(ms_duration(0, 0, 0), 0);
|
||||
}
|
||||
|
||||
TEST_CASE("to_byte_array returns correct size and values.") {
|
||||
auto arr1 = to_byte_array<uint8_t>(0xAB);
|
||||
REQUIRE_EQ(std::size(arr1), 1);
|
||||
CHECK_EQ(arr1[0], 0xAB);
|
||||
|
||||
auto arr2 = to_byte_array<uint16_t>(0xABCD);
|
||||
REQUIRE_EQ(std::size(arr2), 2);
|
||||
CHECK_EQ(arr2[0], 0xAB);
|
||||
CHECK_EQ(arr2[1], 0xCD);
|
||||
|
||||
auto arr4 = to_byte_array<uint32_t>(0xABCD1234);
|
||||
REQUIRE_EQ(std::size(arr4), 4);
|
||||
CHECK_EQ(arr4[0], 0xAB);
|
||||
CHECK_EQ(arr4[1], 0xCD);
|
||||
CHECK_EQ(arr4[2], 0x12);
|
||||
CHECK_EQ(arr4[3], 0x34);
|
||||
}
|
Loading…
Reference in New Issue
Block a user