Compare commits

...

29 Commits

Author SHA1 Message Date
jLynx d326ec831c 1.7.2 (#1132)
* Update version.txt

* Update past_version.txt
2023-06-08 17:38:48 +12:00
dc2dc faac5b45c6 Update POCSAG.TXT (#1127)
Added Dapnet frequency for Hams Germany
2023-06-08 06:13:11 +02:00
dc2dc 5e930a7717 Update HAM_2M_BAND.TXT (#1128)
wrong Frequency for Ham Band 2 Meters
2023-06-08 06:12:08 +02:00
dc2dc aff5e22843 Update RADIOSONDE.TXT (#1129)
https://radiosondy.info/
added frequencies and added regions
2023-06-08 06:10:36 +02:00
Mark Thompson 344a8dc0a0 Fix crash when empty FreqMan files (#1113) and Fix missing Comma when editing text (#1125) (#1131)
* Fix crash issues with empty FreqMan files
* Added ability to delete empty files
* Fix missing Comma character when editing text #1125
2023-06-08 05:46:07 +02:00
gullradriel 7576432e44 Fixing file too big,setting VHF as hamradio entry since it's scanning a tx and a rx (#1124)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-08 10:53:25 +12:00
E.T 3ddc753c88 Pmem reset (#1115)
* reset config when holding down select durring boot

it still need to be tripple checked if adc or control init really doesnt use any pmem

* one less magic number :)

* dual key action left+right for pmem reset

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-08 10:52:29 +12:00
E.T d3a7414e51 pmem debug (#1112)
* pmem debug

* remove std::to_string

* fix review findings

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-07 22:32:44 +02:00
gullradriel 1406c81e03 fix for save freq and category position (#1120)
* fix for save freq and category position
* applying conversation fixes, and save keep freqs when changing category
2023-06-07 18:05:12 +02:00
Kyle Reed 0f28fefc82 Consolidate styles (#1116)
* Consolidate styles into a Styles class and clean up code

* Format

* Add style_bg_dark_grey for nav bar

* Fix bugs found in PR

* Rename styles

* Add bg_white style

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2023-06-07 08:33:32 -07:00
E.T 496b124baf fix missmatched deletes in spectrum painter (#1121) 2023-06-07 15:41:30 +02:00
gullradriel 5a5445e980 Dsp fatfs fix (#1118)
* fixing overflow by one in ../firmware/baseband/dsp_goertzel.cpp by expanding the original array size
* fix underflow by -1 if seq is not stopping the loop
2023-06-07 11:25:35 +02:00
gullradriel eecdd3acda fixing edit freq,edit desc, delete freq (#1117)
* fixing edit freq,edit desc, delete freq
* fixing scrolling issues
* fixing indent
2023-06-07 10:17:55 +02:00
gullradriel c66df8c807 add back correct highlight to list (#1114) 2023-06-07 10:17:29 +02:00
Mark Thompson f2a7617b1b WFM bandwidth widget dial direction (clockwise==increasing) & OptionsField widget change (circular list) (#1109)
* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Clang formatting attempt

* OptionsField wrapping - for your consideration

Perhaps if OptionsWidget supported wrapping, then it would be easier to dial through all available options without having to know which direction to turn the dial.
2023-06-06 18:46:08 +02:00
Kyle Reed af448cf444 Persist audio (#1110)
* Add credits
* Persist audio, add volume to apps
* Remove comment
* Hack around apparent alignment problem
2023-06-05 20:09:50 +02:00
E.T 9c39061590 Touch fix bl timeout (#1108)
* Touch debug from kallanreed

* Revert "init commit for fix touchscreen bad corner (#1071)"

This reverts commit 53fcdedb88.

* increase touch sensitivity by lowering the threshold

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-04 22:41:55 +02:00
Kyle Reed 28319652c1 Add AudioVolumeField -- cleanup (#1107)
* WIP Adding AudioVolumeField

* Fix build break

* Add Bernd to about

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-04 12:56:46 -07:00
gullradriel 7e8a139732 moved pmem setting restore before top bar is loaded (#1105)
* moved pmem setting restore before top bar is loaded
* added a check pmem from sdcard function, replaced occurences, added a save to file on back button
* Added defines for flag file and dump file, changed test func name to should_use_sdcard_for_pmem, used file_exists, removed save and load pmem parameter
* changed a text to a more comprehesible one
2023-06-04 21:25:25 +02:00
Mark Thompson e3e179e380 Increase text brightness for aged flights in ADS-B RX (#1106) 2023-06-04 16:56:53 +02:00
Mark Thompson 4f768140c1 Disallow Focus positioning to Empty lists #1097 (#1104)
* Disallow Focus positioning to Empty lists #1097

* Disallow Focus positioning to Empty lists #1097
2023-06-03 23:01:53 -07:00
Kyle Reed e5728b3501 Fileman default open and Screenshot viewer (#1102)
* WIP expensive way

* Add default file handling to fileman

* Remove screenshot_reader and tests

* Read data in chunks

* Format

* Fix error text position

* PR feedback

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-03 19:26:39 -07:00
gullradriel f66f438487 Recon/FreqMan set limit in load_freqman_file_ex (#1098)
* changed a hardcoded value by a define, changed the order for the database clean so it happens first, changed load_freqman_file_ex so it can take a max num of freqs as a parameter with a default to FREQMAN_MAX_PER_FILE
* added a swap and a set_db to clear as much as possible before the loading of a category
* added a define for Recon max per files
* remove unneeded comment
2023-06-03 22:42:34 +02:00
E.T 2fd3bf0136 Sd over usb macos (#1096)
* sd over usb works at least up to intel + big sur, not yet on m1 + ventura
* style
2023-06-02 12:20:11 +02:00
gullradriel c3264f0d15 set tx and rx before launching mic app from recon (#1094)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-02 17:53:15 +12:00
Kyle Reed 8d7fdeb633 Add edit support for Notepad (#1093)
* WIP file editing

* WIP file editing

* Add "on_pop" handler to navigation.

* WIP Editing

* WIP for draft

* Fix mock and unit tests,  support +newline at end.

* Clean up Painter API and use string_view

* Fix optional rvalue functions

* Fix Result 'take' to be more standard

* FileWrapper stack buffer reads

* Grasping at straws

* Nit

* Move set_on_pop impl to cpp

* Workaround "Open" when file not dirty.

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-01 15:45:55 -07:00
gullradriel 69011754c9 simplifications, better precision, less sleeps (#1091)
* simplifications, better precision, less sleeps
* fixing glitches
* fixed res freeze
* correct frequency picking for marker, yay !
* added comments into the code in hard parts
* took out unneeded sleep
Special thanks to users vmakeev, tel, f1ghy, u-foka for their resilience in testing the bunch of bins I've produced to fix the thing :-)
2023-06-01 13:03:32 +02:00
Kyle Reed e50d8dc148 Move file_wrapper and make testable. (#1085)
* WIP Move file_wrapper and make testable.

* More tests, get text_editor compiling

* Back to working

* Run formatter

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-28 08:44:21 -07:00
gullradriel 23c24355ab Yellow coloration for truncated freqman files (#1086) 2023-05-28 17:09:46 +02:00
118 changed files with 2887 additions and 1711 deletions
+1 -1
View File
@@ -1 +1 @@
v1.7.0
v1.7.1
+1 -1
View File
@@ -1 +1 @@
v1.7.1
v1.7.2
+2
View File
@@ -221,6 +221,7 @@ set(CPPSRC
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_styles.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
ui/ui_transmitter.cpp
@@ -260,6 +261,7 @@ set(CPPSRC
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_sstvtx.cpp
apps/ui_ss_viewer.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp
+8 -16
View File
@@ -42,6 +42,8 @@ namespace ui {
/* AMOptionsView *********************************************************/
static const Style& style_options_group = Styles::bg_blue;
AMOptionsView::AMOptionsView(
const Rect parent_rect,
const Style* const style)
@@ -54,8 +56,8 @@ AMOptionsView::AMOptionsView(
});
freqman_set_bandwidth_option(AM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.am_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.am_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_am_configuration(n);
};
}
@@ -74,8 +76,8 @@ NBFMOptionsView::NBFMOptionsView(
&field_squelch});
freqman_set_bandwidth_option(NFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_nbfm_configuration(n);
};
@@ -99,8 +101,8 @@ WFMOptionsView::WFMOptionsView(
});
freqman_set_bandwidth_option(WFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.wfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.wfm_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_wfm_configuration(n);
};
}
@@ -198,11 +200,6 @@ AnalogAudioView::AnalogAudioView(
this->on_show_options_modulation();
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
@@ -391,11 +388,6 @@ void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) {
persistent_memory::set_correction_ppb(v * 1000);
}
void AnalogAudioView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
audio::output::mute();
record_view.stop();
+3 -15
View File
@@ -28,18 +28,12 @@
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
#include "ui_record_view.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "tone_key.hpp"
namespace ui {
constexpr Style style_options_group{
.font = font::fixed_8x16,
.background = Color::blue(),
.foreground = Color::white(),
};
class AMOptionsView : public View {
public:
AMOptionsView(const Rect parent_rect, const Style* const style);
@@ -207,13 +201,8 @@ class AnalogAudioView : public View {
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
}};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Text text_ctcss{
{19 * 8, 1 * 16, 11 * 8, 1 * 16},
@@ -239,7 +228,6 @@ class AnalogAudioView : public View {
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_headphone_volume_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
+2 -10
View File
@@ -41,6 +41,8 @@ namespace ui {
/* AnalogTvView *******************************************************/
static const Style& style_options_group_new = Styles::bg_blue;
AnalogTvView::AnalogTvView(
NavigationView& nav)
: nav_(nav) {
@@ -100,11 +102,6 @@ AnalogTvView::AnalogTvView(
this->on_show_options_modulation();
};
field_volume.set_value(0);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
tv.on_select = [this](int32_t offset) {
field_frequency.set_value(receiver_model.tuning_frequency() + offset);
};
@@ -225,11 +222,6 @@ void AnalogTvView::on_reference_ppm_correction_changed(int32_t v) {
persistent_memory::set_correction_ppb(v * 1000);
}
void AnalogTvView::on_headphone_volume_changed(int32_t v) {
(void)v; // avoid warning
// tv::TVView::set_headphone_volume(this,v);
}
void AnalogTvView::update_modulation(const ReceiverModel::Mode modulation) {
audio::output::mute();
+3 -15
View File
@@ -29,19 +29,13 @@
#include "ui_receiver.hpp"
#include "ui_tv.hpp"
#include "ui_record_view.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "tone_key.hpp"
namespace ui {
constexpr Style style_options_group_new{
.font = font::fixed_8x16,
.background = Color::blue(),
.foreground = Color::white(),
};
class AnalogTvView : public View {
public:
AnalogTvView(NavigationView& nav);
@@ -98,13 +92,8 @@ class AnalogTvView : public View {
{"TV ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
}};
NumberField field_volume{
{27 * 8, 0 * 16},
3,
{0, 255},
1,
' ',
};
AudioVolumeField field_volume{
{27 * 8, 0 * 16}};
std::unique_ptr<Widget> options_widget{};
@@ -118,7 +107,6 @@ class AnalogTvView : public View {
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_headphone_volume_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
-1
View File
@@ -24,7 +24,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rfm69.hpp"
#include "message.hpp"
+15 -37
View File
@@ -79,18 +79,21 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
update_freq(f);
};
// load app settings
// load app settings TODO: needed?
auto rc = settings.load("rx_pocsag", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
field_frequency.set_value(app_settings.rx_frequency);
} else
} else {
field_lna.set_value(receiver_model.lna());
field_vga.set_value(receiver_model.vga());
field_rf_amp.set_value(receiver_model.rf_amp());
field_frequency.set_value(receiver_model.tuning_frequency());
}
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
@@ -105,22 +108,10 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
};
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
check_ignore.set_value(ignore);
check_ignore.on_select = [this](Checkbox&, bool v) {
ignore = v;
};
// TODO app setting instead?
ignore_address = persistent_memory::pocsag_ignore_address();
// TODO is this common enough for a helper?
for (size_t c = 0; c < 7; c++) {
sym_ignore.set_sym(6 - c, ignore_address % 10);
ignore_address /= 10;
@@ -150,20 +141,6 @@ POCSAGAppView::~POCSAGAppView() {
baseband::shutdown();
}
void POCSAGAppView::focus() {
field_frequency.focus();
}
void POCSAGAppView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
// Useless ?
void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
}
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
std::string alphanum_text = "";
@@ -172,14 +149,14 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
else {
pocsag_decode_batch(message->packet, &pocsag_state);
if ((ignore) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
if ((ignore()) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
// Ignore (inform, but no log)
// console.write("\n\x1B\x03" + to_string_time(message->packet.timestamp()) +
// " Ignored address " + to_string_dec_uint(pocsag_state.address));
return;
}
// Too many errors for reliable decode
if ((ignore) && (pocsag_state.errors >= 3)) {
if ((ignore()) && (pocsag_state.errors >= 3)) {
return;
}
@@ -199,7 +176,7 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
console.write(console_info);
if (logger && logging) {
if (logger && logging()) {
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
@@ -218,15 +195,16 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
console.write(pocsag_state.output);
}
if (logger && logging)
if (logger && logging())
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Alpha: " + pocsag_state.output);
}
}
// TODO: make setting.
// Log raw data whatever it contains
if (logger && logging)
if (logger && logging())
logger->log_raw_data(message->packet, target_frequency());
}
+8 -15
View File
@@ -52,20 +52,18 @@ class POCSAGAppView : public View {
POCSAGAppView(NavigationView& nav);
~POCSAGAppView();
void set_parent_rect(const Rect new_parent_rect) override;
void focus() override;
std::string title() const override { return "POCSAG RX"; };
private:
static constexpr uint32_t initial_target_frequency = 466175000;
bool logging() const { return check_log.value(); };
bool ignore() const { return check_ignore.value(); };
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
bool logging{false};
bool ignore{false};
uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state{};
@@ -88,13 +86,9 @@ class POCSAGAppView : public View {
FrequencyField field_frequency{
{0 * 8, 0 * 8},
};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_ignore{
{0 * 8, 21},
@@ -105,10 +99,11 @@ class POCSAGAppView : public View {
{13 * 8, 25},
7,
SymField::SYMFIELD_DEC};
Checkbox check_log{
{240 - 8 * 8, 21},
3,
"LOG",
"Log",
false};
Console console{
@@ -122,8 +117,6 @@ class POCSAGAppView : public View {
void on_packet(const POCSAGPacketMessage* message);
void on_headphone_volume_changed(int32_t v);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
-1
View File
@@ -25,7 +25,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstdint>
@@ -33,6 +33,8 @@ void AboutView::update() {
console.writeln("heurist1,intoxsick,ckuethe");
console.writeln("notpike,jLynx,zigad");
console.writeln("MichalLeonBorsuk,jimilinuxguy");
console.writeln("kallanreed,bernd-herzog");
console.writeln("NotherNgineer,zxkmm,u-foka");
console.writeln("");
break;
@@ -3,7 +3,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstdint>
+1 -1
View File
@@ -53,7 +53,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
target_color = Color::light_grey();
} else {
aged_color = 0x08;
target_color = Color::dark_grey();
target_color = Color::grey();
}
std::string entry_string = "\x1B";
-1
View File
@@ -24,7 +24,6 @@
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "string_format.hpp"
#include "file.hpp"
+1
View File
@@ -55,6 +55,7 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&check_log,
&text_debug,
+3
View File
@@ -79,6 +79,9 @@ class AFSKRxView : public View {
{21 * 8, 5, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
FrequencyField field_frequency{
{0 * 8, 0 * 16},
};
+1
View File
@@ -86,6 +86,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&options_region,
&field_frequency,
&record_view,
+3
View File
@@ -207,6 +207,9 @@ class APRSRxView : public View {
{21 * 8, 5, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
OptionsField options_region{
{0 * 8, 0 * 8},
3,
-1
View File
@@ -24,7 +24,6 @@
#include "ui_textentry.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_transmitter.hpp"
#include "message.hpp"
-1
View File
@@ -25,7 +25,6 @@
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "bht.hpp"
#include "bitmap.hpp"
@@ -24,7 +24,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "message.hpp"
#include "transmitter_model.hpp"
+49 -1
View File
@@ -253,7 +253,7 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
void ControlsSwitchesWidget::paint(Painter& painter) {
const auto pos = screen_pos();
const std::array<Rect, 8> button_rects{{
const std::array<Rect, 9> button_rects{{
{64, 32, 16, 16}, // Right
{0, 32, 16, 16}, // Left
{32, 64, 16, 16}, // Down
@@ -262,12 +262,16 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
{16, 96, 16, 16}, // Encoder phase 0
{48, 96, 16, 16}, // Encoder phase 1
{96, 0, 16, 16}, // Dfu
{96, 64, 16, 16}, // Touch
}};
for (const auto r : button_rects) {
painter.fill_rectangle(r + pos, Color::blue());
}
if (get_touch_frame().touch)
painter.fill_rectangle(button_rects[8] + pos, Color::yellow());
const std::array<Rect, 8> raw_rects{{
{64 + 1, 32 + 1, 16 - 2, 16 - 2}, // Right
{0 + 1, 32 + 1, 16 - 2, 16 - 2}, // Left
@@ -377,10 +381,54 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Pmem", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
});
set_max_rows(2); // allow wider buttons
}
/* DebugPmemView *********************************************************/
DebugPmemView::DebugPmemView(NavigationView& nav)
: data{*reinterpret_cast<pmem_data*>(memory::map::backup_ram.base())}, registers_widget(RegistersWidgetConfig{page_size, 8}, std::bind(&DebugPmemView::registers_widget_feed, this, std::placeholders::_1)) {
static_assert(sizeof(pmem_data) == memory::map::backup_ram.size());
add_children({&text_page, &registers_widget, &text_checksum, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size()) + " CRC: " + to_string_hex(data.check_value, 8));
button_ok.on_select = [&nav](Button&) {
nav.pop();
};
update();
}
bool DebugPmemView::on_encoder(const EncoderEvent delta) {
page = std::max(0l, std::min((int32_t)page_max, page + delta));
update();
return true;
}
void DebugPmemView::focus() {
button_ok.focus();
}
void DebugPmemView::update() {
text_page.set(to_string_hex(page_size * page, 2) + "+");
registers_widget.update();
}
uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
if (page_size * page + register_number >= memory::map::backup_ram.size()) {
return 0xff;
}
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
}
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
std::string debug_text;
+36
View File
@@ -30,6 +30,7 @@
#include "rffc507x.hpp"
#include "portapack.hpp"
#include "memory_map.hpp"
#include <functional>
#include <utility>
@@ -263,6 +264,41 @@ class DebugControlsView : public View {
"Done"};
};
class DebugPmemView : public View {
public:
DebugPmemView(NavigationView& nav);
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
std::string title() const override { return "Pmem"; }
private:
struct pmem_data {
uint32_t regfile[63];
uint32_t check_value;
};
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
static constexpr uint8_t page_max{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size - 1};
int32_t page{0};
const pmem_data& data;
Text text_page{{16, 16, 208, 16}};
RegistersWidget registers_widget;
Text text_checksum{{16, 240, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
"OK",
};
void update();
uint32_t registers_widget_feed(const size_t register_number);
};
/*class DebugLCRView : public View {
public:
DebugLCRView(NavigationView& nav, std::string lcrstring);
-11
View File
@@ -195,17 +195,6 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
/*const Style style_address {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_data {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};*/
Rect view_rect = {0, 4 * 8, 240, 168};
EncodersConfigView view_config{nav_, view_rect};
+84 -59
View File
@@ -26,6 +26,7 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
@@ -67,15 +68,16 @@ bool iequal(
// Inserts the entry into the entry list sorted directories first then by file name.
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory)
return true;
else if (!lhs.is_directory && rhs.is_directory)
return false;
else
return lhs.path < rhs.path;
});
auto it = std::lower_bound(
std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory)
return true;
else if (!lhs.is_directory && rhs.is_directory)
return false;
else
return lhs.path < rhs.path;
});
entries.insert(it, std::move(entry));
}
@@ -398,6 +400,18 @@ void FileSaveView::refresh_widgets() {
*/
/* FileManagerView ***********************************************************/
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
button_new_file.hidden(v);
set_dirty();
}
void FileManagerView::on_rename() {
auto& entry = get_selected_entry();
name_buffer = entry.path.filename().string();
@@ -407,44 +421,47 @@ void FileManagerView::on_rename() {
pos != name_buffer.npos && !entry.is_directory)
cursor_pos = pos;
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
text_prompt(
nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
});
if (!has_partner)
reload_current();
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
});
if (!has_partner)
reload_current();
});
}
void FileManagerView::on_delete() {
auto name = get_selected_entry().path.filename().string();
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
});
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
});
if (!has_partner)
reload_current();
}
});
if (!has_partner)
reload_current();
}
});
}
void FileManagerView::on_new_dir() {
@@ -455,14 +472,6 @@ void FileManagerView::on_new_dir() {
});
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
void FileManagerView::on_paste() {
// TODO: handle partner file. Need to fix nav stack first.
auto new_name = get_unique_filename(current_path, clipboard_path.filename());
@@ -483,23 +492,37 @@ void FileManagerView::on_paste() {
reload_current();
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
bool FileManagerView::handle_file_open() {
if (!selected_is_valid())
return false;
auto path = get_selected_full_path();
auto ext = path.extension();
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
nav_.push<TextEditorView>(path);
return true;
} else if (iequal(u".PNG", ext)) {
nav_.push<ScreenshotViewer>(path);
return true;
}
return false;
}
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
}
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
button_new_file.hidden(v);
set_dirty();
}
FileManagerView::FileManagerView(
NavigationView& nav)
: FileManBaseView(nav, "") {
@@ -537,6 +560,8 @@ FileManagerView::FileManagerView(
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else if (key == KeyEvent::Select && handle_file_open()) {
return;
} else {
button_rename.focus();
}
+2
View File
@@ -213,6 +213,8 @@ class FileManagerView : public FileManBaseView {
void on_new_dir();
void on_new_file();
bool handle_file_open();
// True if the selected entry is a real file item.
bool selected_is_valid() const;
+74 -83
View File
@@ -29,29 +29,21 @@ using namespace portapack;
namespace ui {
static int32_t last_category_id{0};
static int32_t current_category_id = 0;
FreqManBaseView::FreqManBaseView(
NavigationView& nav)
: nav_(nav) {
file_list = get_freqman_files();
add_children({&label_category,
add_children({&options_category,
&label_category,
&button_exit});
if (file_list.size()) {
add_child(&options_category);
populate_categories();
} else
error_ = ERROR_NOFILES;
// initialize
change_category(last_category_id);
// Default function
on_change_category = [this](int32_t category_id) {
refresh_list();
options_category.on_change = [this](size_t category_id, int32_t) {
change_category(category_id);
};
options_category.set_selected_index(current_category_id);
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
@@ -70,8 +62,39 @@ void FreqManBaseView::focus() {
}
}
void FreqManBaseView::populate_categories() {
void FreqManBaseView::get_freqman_files() {
std::vector<std::string>().swap(file_list);
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
for (auto file : files) {
std::string file_name = file.stem().string();
// don't propose tmp / hidden files in freqman's list
if (file_name.length() && file_name[0] != '.') {
file_list.emplace_back(file_name);
}
}
};
void FreqManBaseView::change_category(int32_t category_id) {
current_category_id = category_id;
if (file_list.empty()) return;
std::vector<freqman_entry>().swap(database);
if (!load_freqman_file(file_list[categories[category_id].second], database)) {
error_ = ERROR_ACCESS;
}
menu_view.set_db(database);
text_empty.hidden(!database.empty());
menu_view.set_dirty();
set_dirty();
}
void FreqManBaseView::refresh_list() {
categories.clear();
get_freqman_files();
for (size_t n = 0; n < file_list.size(); n++)
categories.emplace_back(std::make_pair(file_list[n].substr(0, 14), n));
@@ -82,53 +105,13 @@ void FreqManBaseView::populate_categories() {
});
options_category.set_options(categories);
options_category.set_selected_index(last_category_id);
options_category.on_change = [this](size_t category_id, int32_t) {
if (on_change_category)
on_change_category(category_id);
};
}
void FreqManBaseView::change_category(int32_t category_id) {
if (!file_list.size()) return;
last_category_id = current_category_id = category_id;
if (!load_freqman_file(file_list[categories[current_category_id].second], database))
error_ = ERROR_ACCESS;
else {
menu_view.set_db(database);
refresh_list();
}
}
void FreqManBaseView::refresh_list() {
if (!database.size()) {
if (on_refresh_widgets)
on_refresh_widgets(true);
} else {
if (on_refresh_widgets)
on_refresh_widgets(false);
}
if ((unsigned)current_category_id >= categories.size())
current_category_id = categories.size() - 1;
}
void FrequencySaveView::save_current_file() {
if (database.size() > FREQMAN_MAX_PER_FILE) {
nav_.display_modal(
"Error", "Too many entries, maximum is\n" FREQMAN_MAX_PER_FILE_STR ". Trim list ?",
YESNO,
[this](bool choice) {
if (choice) {
database.resize(FREQMAN_MAX_PER_FILE);
save_freqman_file(file_list[categories[current_category_id].second], database);
}
nav_.pop();
});
} else {
save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.pop();
}
save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.pop();
}
void FrequencySaveView::on_save_name() {
@@ -152,11 +135,11 @@ FrequencySaveView::FrequencySaveView(
// Todo: add back ?
/*for (size_t n = 0; n < database.size(); n++) {
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
add_children({&labels,
&big_display,
@@ -172,6 +155,11 @@ FrequencySaveView::FrequencySaveView(
button_save_timestamp.on_select = [this, &nav](Button&) {
on_save_timestamp();
};
options_category.on_change = [this, value](size_t category_id, int32_t) {
change_category(category_id);
big_display.set(value);
};
}
void FrequencyLoadView::refresh_widgets(const bool v) {
@@ -194,9 +182,6 @@ FrequencyLoadView::FrequencyLoadView(
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
change_category(last_category_id);
refresh_list();
menu_view.on_select = [&nav, this](FreqManUIList&) {
nav_.pop();
@@ -220,14 +205,14 @@ FrequencyLoadView::FrequencyLoadView(
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
database[menu_view.get_index()].frequency_a = f;
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
change_category(current_category_id);
}
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
database[menu_view.get_index()].description = buffer;
refresh_list();
save_freqman_file(file_list[categories[current_category_id].second], database);
change_category(current_category_id);
});
}
@@ -235,23 +220,30 @@ void FrequencyManagerView::on_new_category(NavigationView& nav) {
text_prompt(nav, desc_buffer, 12, [this](std::string& buffer) {
File freqman_file;
create_freqman_file(buffer, freqman_file);
refresh_list();
change_category(current_category_id);
});
populate_categories();
refresh_list();
}
void FrequencyManagerView::on_delete() {
database.erase(database.begin() + menu_view.get_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
if (database.empty()) {
delete_freqman_file(file_list[categories[current_category_id].second]);
refresh_list();
} else {
database.erase(database.begin() + menu_view.get_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
}
change_category(current_category_id);
}
void FrequencyManagerView::refresh_widgets(const bool v) {
button_edit_freq.hidden(v);
button_edit_desc.hidden(v);
button_delete.hidden(v);
menu_view.hidden(v);
text_empty.hidden(!v);
menu_view.hidden(v);
menu_view.set_dirty();
labels.hidden(v);
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
set_dirty();
}
@@ -275,9 +267,6 @@ FrequencyManagerView::FrequencyManagerView(
&button_edit_desc,
&button_delete});
change_category(last_category_id);
refresh_list();
menu_view.on_select = [this](FreqManUIList&) {
button_edit_freq.focus();
};
@@ -288,6 +277,9 @@ FrequencyManagerView::FrequencyManagerView(
};
button_edit_freq.on_select = [this, &nav](Button&) {
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.get_index()].frequency_a);
new_view->on_changed = [this](rf::Frequency f) {
on_edit_freq(f);
@@ -295,16 +287,15 @@ FrequencyManagerView::FrequencyManagerView(
};
button_edit_desc.on_select = [this, &nav](Button&) {
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
desc_buffer = database[menu_view.get_index()].description;
on_edit_desc(nav);
};
button_delete.on_select = [this, &nav](Button&) {
nav.push<ModalMessageView>("Confirm", "Are you sure ?", YESNO,
[this](bool choice) {
if (choice)
on_delete();
});
on_delete();
};
}
+6 -6
View File
@@ -46,17 +46,15 @@ class FreqManBaseView : public View {
NavigationView& nav_;
freqman_error error_{NO_ERROR};
options_t categories{};
std::function<void(int32_t category_id)> on_change_category{nullptr};
std::function<void(void)> on_select_frequency{nullptr};
std::function<void(bool)> on_refresh_widgets{nullptr};
std::vector<std::string> file_list{};
int32_t current_category_id{0};
void populate_categories();
void get_freqman_files();
void change_category(int32_t category_id);
void refresh_list();
freqman_db database{};
std::vector<std::string> file_list{};
Labels label_category{
{{0, 4}, "Category:", Color::light_grey()}};
@@ -75,8 +73,10 @@ class FreqManBaseView : public View {
};
Button button_exit{
{20 * 8, 34 * 8, 10 * 8, 4 * 8},
{16 * 8, 34 * 8, 14 * 8, 4 * 8},
"Exit"};
private:
};
class FrequencySaveView : public FreqManBaseView {
@@ -154,7 +154,7 @@ class FrequencyManagerView : public FreqManBaseView {
"Description"};
Button button_delete{
{18 * 8, 27 * 8, 12 * 8, 32},
{16 * 8, 29 * 8, 14 * 8, 32},
"Delete"};
};
-5
View File
@@ -36,8 +36,6 @@ void RangeView::focus() {
check_enabled.focus();
}
extern constexpr Style RangeView::style_info;
void RangeView::update_start(rf::Frequency f) {
// Change everything except max
frequency_range.min = f;
@@ -200,9 +198,6 @@ void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center,
jammer_channels[i].duration = 30720 * duration;
}
extern constexpr Style JammerView::style_val;
extern constexpr Style JammerView::style_cancel;
void JammerView::start_tx() {
uint32_t c, i = 0;
size_t num_channels;
+4 -16
View File
@@ -22,7 +22,7 @@
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "ui_navigation.hpp"
#include "ui_tabview.hpp"
#include "transmitter_model.hpp"
@@ -52,11 +52,7 @@ class RangeView : public View {
uint32_t width{};
rf::Frequency center{};
static constexpr Style style_info{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style& style_info = Styles::grey;
Labels labels{
{{2 * 8, 8 * 8 + 4}, "Start", Color::light_grey()},
@@ -117,16 +113,8 @@ class JammerView : public View {
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
static constexpr Style style_val{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
static constexpr Style style_cancel{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style& style_val = Styles::green;
const Style& style_cancel = Styles::red;
RangeView view_range_a{nav_};
RangeView view_range_b{nav_};
+12 -20
View File
@@ -68,9 +68,6 @@ LevelView::LevelView(NavigationView& nav)
rssi.set_vertical_rssi(true);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
change_mode(NFM_MODULATION); // Start on AM
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
@@ -135,7 +132,7 @@ LevelView::LevelView(NavigationView& nav)
audio::output::stop();
} else if (v == 1) {
audio::output::start();
this->on_headphone_volume_changed((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
} else {
}
};
@@ -154,13 +151,8 @@ LevelView::LevelView(NavigationView& nav)
// FILL STEP OPTIONS
freqman_set_modulation_option(field_mode);
freqman_set_step_option_short(step_mode);
freq_stats_rssi.set_style(&style_white);
freq_stats_db.set_style(&style_white);
}
void LevelView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
freq_stats_rssi.set_style(&Styles::white);
freq_stats_db.set_style(&Styles::white);
}
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
@@ -199,11 +191,11 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
case AM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw DSB (0) default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
@@ -211,22 +203,22 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
case NFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 16k (2) default
field_bw.set_selected_index(2);
field_bw.set_by_value(2);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) only/default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
+3 -51
View File
@@ -27,7 +27,7 @@
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
@@ -48,48 +48,6 @@ class LevelView : public View {
void focus() override;
const Style style_grey{
// level
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_white{
// level
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_yellow{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_blue{
// quick level, wait == 0
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
std::string title() const override { return "Level"; };
private:
@@ -117,13 +75,8 @@ class LevelView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
@@ -202,7 +155,6 @@ class LevelView : public View {
};
void handle_coded_squelch(const uint32_t value);
void on_headphone_volume_changed(int32_t v);
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
+103 -124
View File
@@ -46,18 +46,10 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
if (spectrum.db[bin - 120] > max_power)
max_power = spectrum.db[bin - 120];
}
} else if (mode == LOOKING_GLASS_FASTSCAN) {
} else {
// view is made in multiple pass, use original bin picking
// Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - 2 - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - 2 - 120 + bin];
} else {
if (spectrum.db[2 + bin - 120] > max_power)
max_power = spectrum.db[2 + bin - 120];
}
} else // if( mode == LOOKING_GLASS_SLOWSCAN )
{
// FAST mode: center 12 bins are ignored in fast mode , (DC spike is blanked) leftmost and rightmost 2 bins are ignored
// SLOW mode: leftmost 'offset' bins are ignored
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - offset - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - offset - 120 + bin];
@@ -68,13 +60,19 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
}
}
void GlassView::on_marker_change() {
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
marker = f_min + (8 * each_bin_size) + (marker_pixel_index * (looking_glass_range - 16 * each_bin_size)) / SCREEN_W;
} else // if( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
marker = f_min + (offset * each_bin_size) + (marker_pixel_index * looking_glass_range) / SCREEN_W;
}
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
} else
freq_at_pos = f_min - (offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
return freq_at_pos;
}
void GlassView::on_marker_change() {
marker = get_freq_from_bin_pos(marker_pixel_index);
button_marker.set_text(to_string_short_freq(marker));
PlotMarker(marker_pixel_index); // Refresh marker on screen
}
@@ -101,7 +99,7 @@ void GlassView::retune() {
// Tune rx for this new slice directly because the model
// saves to persistent memory which is slower.
radio::set_tuning_frequency(f_center);
chThdSleepMilliseconds(5);
chThdSleepMilliseconds(5); // stabilize freq
baseband::spectrum_streaming_start(); // Do the RX
}
@@ -146,12 +144,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
if (mode == LOOKING_GLASS_SINGLEPASS) {
max_freq_hold = f_min + (xpos * looking_glass_range) / SCREEN_W;
} else // if( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
max_freq_hold = f_min + (offset * each_bin_size) + (xpos * looking_glass_range) / SCREEN_W;
}
max_freq_hold = get_freq_from_bin_pos(xpos);
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
@@ -179,16 +172,12 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need SCREEN_W (240) of those bins
for (bin = offset; bin < bin_length + offset; bin++) {
for (bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (ignore_dc && bin == 119) {
uint8_t next_max_power = 0;
get_max_power(spectrum, 120, next_max_power);
bins_Hz_size += 12 * each_bin_size; // add the ignored DC spike to "pixel fulfilled bag of Hz"
max_power = (max_power + next_max_power) / 2;
}
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
{
if (min_color_power < max_power)
@@ -201,15 +190,21 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
if (!pixel_index) // Received indication that a waterfall line has been completed
{
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
f_center = f_center_ini;
retune();
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center = f_center_ini;
retune();
} else
baseband::spectrum_streaming_start();
return; // signal a new line
}
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
}
}
f_center += looking_glass_step;
retune();
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center += looking_glass_step;
retune();
} else
baseband::spectrum_streaming_start();
}
void GlassView::on_hide() {
@@ -223,46 +218,44 @@ void GlassView::on_show() {
}
void GlassView::on_range_changed() {
reset_live_view(false);
reset_live_view(true);
f_min = field_frequency_min.value();
f_max = field_frequency_max.value();
f_min = f_min * MHZ_DIV; // Transpose into full frequency realm
f_max = f_max * MHZ_DIV;
looking_glass_range = f_max - f_min;
if (looking_glass_range < LOOKING_GLASS_SLICE_WIDTH_MAX) {
mode = LOOKING_GLASS_SINGLEPASS;
} else {
mode = scan_type.selected_index_value();
}
if (mode == LOOKING_GLASS_SINGLEPASS) {
if (looking_glass_range <= LOOKING_GLASS_SLICE_WIDTH_MAX) {
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 0;
bin_length = SCREEN_W;
ignore_dc = 0;
} else if (mode == LOOKING_GLASS_FASTSCAN) {
// view is made in multiple pass, use original bin picking
offset = 0;
bin_length = SCREEN_W;
ignore_dc = 1;
} else // if( mode == LOOKING_GLASS_SLOWSCAN )
{
offset = 16;
bin_length = 80;
ignore_dc = 0;
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
if (mode == LOOKING_GLASS_SINGLEPASS) {
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth / 2;
each_bin_size = looking_glass_bandwidth / bin_length;
looking_glass_step = bin_length * each_bin_size;
} else { // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
looking_glass_sampling_rate = looking_glass_bandwidth;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else { // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
// view is made in multiple pass, use original bin picking
mode = scan_type.selected_index_value();
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
bin_length = SCREEN_W;
ignore_dc = 1;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 16;
bin_length = 80;
ignore_dc = 0;
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
looking_glass_step = (bin_length + (ignore_dc * 12)) * each_bin_size;
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
@@ -277,6 +270,7 @@ void GlassView::on_range_changed() {
pixel_index = 0; // reset pixel counter
max_power = 0; // reset save max power level
bins_Hz_size = 0; // reset amount of Hz filled up by pixels
//
on_marker_change();
// set the sample rate and bandwidth
@@ -284,9 +278,8 @@ void GlassView::on_range_changed() {
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth);
receiver_model.set_squelch_level(0);
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_sampling_rate, field_trigger.value());
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_bandwidth, field_trigger.value());
receiver_model.set_tuning_frequency(f_center); // tune rx for this slice
}
@@ -302,6 +295,42 @@ void GlassView::PlotMarker(uint8_t pos) {
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Color::red()); // Red marker bottom
}
void GlassView::clip_min(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v > 7200 - min_size) {
v = 7200 - min_size;
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size);
if (locked_range)
field_frequency_max.set_value(v + min_size);
else
field_frequency_min.set_value(v);
on_range_changed();
}
void GlassView::clip_max(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v < min_size) {
v = min_size;
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size);
if (locked_range)
field_frequency_min.set_value(v - min_size);
else
field_frequency_max.set_value(v);
on_range_changed();
}
GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
@@ -329,21 +358,7 @@ GlassView::GlassView(
load_Presets(); // Load available presets from TXT files (or default)
field_frequency_min.on_change = [this](int32_t v) {
reset_live_view(true);
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v > 7200 - min_size) {
v = 7200 - min_size;
field_frequency_min.set_value(v);
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size);
if (locked_range)
field_frequency_max.set_value(v + min_size);
on_range_changed();
clip_min(v);
};
field_frequency_min.set_value(presets_db[0].min); // Defaults to first preset
field_frequency_min.set_step(steps);
@@ -351,37 +366,12 @@ GlassView::GlassView(
field_frequency_min.on_select = [this, &nav](NumberField& field) {
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_min.value() * MHZ_DIV);
new_view->on_changed = [this, &field](rf::Frequency f) {
int32_t freq = f / MHZ_DIV;
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (freq > (7200 - min_size))
freq = 7200 - min_size;
field_frequency_min.set_value(freq);
if (field_frequency_max.value() < (freq + min_size))
field_frequency_max.set_value(freq + min_size);
on_range_changed();
clip_min(f / MHZ_DIV);
};
};
field_frequency_max.on_change = [this](int32_t v) {
reset_live_view(true);
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v < min_size) {
v = min_size;
field_frequency_max.set_value(v);
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size);
if (locked_range)
field_frequency_min.set_value(v - min_size);
on_range_changed();
clip_max(v);
};
field_frequency_max.set_value(presets_db[0].max); // Defaults to first preset
field_frequency_max.set_step(steps);
@@ -389,18 +379,7 @@ GlassView::GlassView(
field_frequency_max.on_select = [this, &nav](NumberField& field) {
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_max.value() * MHZ_DIV);
new_view->on_changed = [this, &field](rf::Frequency f) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
int32_t freq = f / MHZ_DIV;
if (freq < min_size)
freq = min_size;
field_frequency_max.set_value(freq);
if (field_frequency_min.value() > (freq - min_size))
field_frequency_min.set_value(freq - min_size);
on_range_changed();
clip_max(f / MHZ_DIV);
};
};
@@ -504,18 +483,18 @@ GlassView::GlassView(
};
field_trigger.on_change = [this](int32_t v) {
baseband::set_spectrum(looking_glass_sampling_rate, v);
baseband::set_spectrum(looking_glass_bandwidth, v);
};
field_trigger.set_value(32); // Defaults to 32, as normal triggering resolution
button_range.on_select = [this](Button&) {
if (locked_range) {
locked_range = false;
button_range.set_style(&style_white);
button_range.set_style(&Styles::white);
button_range.set_text(" " + to_string_dec_uint(search_span) + " ");
} else {
locked_range = true;
button_range.set_style(&style_red);
button_range.set_style(&Styles::red);
button_range.set_text(">" + to_string_dec_uint(search_span) + "<");
}
};
@@ -532,10 +511,10 @@ GlassView::GlassView(
};
display.scroll_set_area(109, 319);
baseband::set_spectrum(looking_glass_sampling_rate, field_trigger.value()); // trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute ,
// it keeps adding the output of the fft to the buffer until "trigger" number of calls are made,
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, field_trigger.value()); // trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute ,
// it keeps adding the output of the fft to the buffer until "trigger" number of calls are made,
// at which time it pushes the buffer up with channel_spectrum.feed
marker_pixel_index = 120;
on_range_changed();
@@ -31,6 +31,7 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "string_format.hpp"
#include "analog_audio_app.hpp"
#include "spectrum_color_lut.hpp"
@@ -74,22 +75,11 @@ class GlassView : public View {
std::string label{};
};
const Style style_white{
// free range
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_red{
// locked range
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
std::vector<preset_entry> presets_db{};
void clip_min(int32_t v);
void clip_max(int32_t v);
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
void on_marker_change();
int64_t next_mult_of(int64_t num, int64_t multiplier);
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
-10
View File
@@ -196,13 +196,6 @@ void MicTXView::rxaudio(bool is_on) {
}
}
void MicTXView::on_headphone_volume_changed(int32_t v) {
// if (rx_enabled) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
//}
}
void MicTXView::set_ptt_visibility(bool v) {
tx_button.hidden(!v);
}
@@ -526,9 +519,6 @@ MicTXView::MicTXView(
set_dirty(); // Refresh interface
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
field_rxbw.on_change = [this](size_t, int32_t v) {
if (!(enable_am || enable_usb || enable_lsb || enable_dsb || enable_wfm)) {
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
+2 -8
View File
@@ -76,7 +76,6 @@ class MicTXView : public View {
void configure_baseband();
void rxaudio(bool is_on);
void on_headphone_volume_changed(int32_t v);
void set_ptt_visibility(bool v);
@@ -279,13 +278,8 @@ class MicTXView : public View {
"F TX=RX",
false};
NumberField field_volume{
{9 * 8, (23 * 8) + 2},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{9 * 8, (23 * 8) + 2}};
OptionsField field_rxbw{
{19 * 8, (23 * 8) + 2},
-6
View File
@@ -27,7 +27,6 @@
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rtc_time.hpp"
#include "clock_manager.hpp"
#include "baseband_api.hpp"
@@ -63,11 +62,6 @@ class NumbersStationView : public View {
SIGNOFF
};
Style style_red{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red()};
typedef struct {
char code;
uint32_t index;
-1
View File
@@ -25,7 +25,6 @@
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "baseband_api.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
+1 -1
View File
@@ -146,7 +146,7 @@ void PlaylistView::toggle() {
track_number = 0;
playlist_db.clear();
playlist_masterdb.clear();
} else { // Thanks kallanreed for providing this logic!
} else {
total_tracks = 0;
track_number = 0;
playlist_db.clear();
+61 -67
View File
@@ -55,23 +55,23 @@ void ReconView::reset_indexes() {
void ReconView::colorize_waits() {
// colorize wait on match
if (wait == 0) {
field_wait.set_style(&style_blue);
field_wait.set_style(&Styles::blue);
} else if (wait >= 500) {
field_wait.set_style(&style_white);
field_wait.set_style(&Styles::white);
} else if (wait > -500 && wait < 500) {
field_wait.set_style(&style_red);
field_wait.set_style(&Styles::red);
} else if (wait <= -500) {
field_wait.set_style(&style_green);
field_wait.set_style(&Styles::green);
}
// colorize lock time if in SPARSE mode as in continuous the lock_wait time is disarmed at first lock count
if (recon_match_mode == RECON_MATCH_SPARSE) {
if ((recon_lock_duration / STATS_UPDATE_INTERVAL) <= recon_lock_nb_match) {
field_lock_wait.set_style(&style_yellow);
field_lock_wait.set_style(&Styles::yellow);
} else {
field_lock_wait.set_style(&style_white);
field_lock_wait.set_style(&Styles::white);
}
} else {
field_lock_wait.set_style(&style_white);
field_lock_wait.set_style(&Styles::white);
}
}
@@ -182,7 +182,6 @@ bool ReconView::recon_load_config_from_sd() {
squelch = -14;
recon_match_mode = RECON_MATCH_CONTINUOUS;
wait = RECON_DEF_WAIT_DURATION;
volume = 40;
return false;
}
@@ -221,11 +220,6 @@ bool ReconView::recon_load_config_from_sd() {
else
wait = RECON_DEF_WAIT_DURATION;
if (it > 7)
volume = strtoll(params[7].c_str(), nullptr, 10);
else
volume = 40;
return true;
}
@@ -244,13 +238,12 @@ bool ReconView::recon_save_config_to_sd() {
settings_file.write_line(to_string_dec_int(squelch));
settings_file.write_line(to_string_dec_uint(recon_match_mode));
settings_file.write_line(to_string_dec_int(wait));
settings_file.write_line(to_string_dec_int(volume));
return true;
}
void ReconView::audio_output_start() {
audio::output::start();
this->on_headphone_volume_changed((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
}
void ReconView::recon_redraw() {
@@ -270,17 +263,17 @@ void ReconView::recon_redraw() {
text_nb_locks.set(to_string_dec_uint(freq_lock) + "/" + to_string_dec_uint(recon_lock_nb_match));
if (freq_lock == 0) {
// NO FREQ LOCK, ONGOING STANDARD SCANNING
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
if (recon)
button_pause.set_text("<PAUSE>");
else
button_pause.set_text("<RESUME>");
} else if (freq_lock == 1 && recon_lock_nb_match != 1) {
// STARTING LOCK FREQ
big_display.set_style(&style_yellow);
big_display.set_style(&Styles::yellow);
button_pause.set_text("<SKPLCK>");
} else if (freq_lock >= recon_lock_nb_match) {
big_display.set_style(&style_green);
big_display.set_style(&Styles::green);
button_pause.set_text("<UNLOCK>");
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
@@ -438,7 +431,6 @@ ReconView::ReconView(NavigationView& nav)
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
field_volume.set_value(volume);
if (sd_card_mounted) {
// load auto common app settings
auto rc = settings.load("recon", &app_settings);
@@ -568,6 +560,19 @@ ReconView::ReconView(NavigationView& nav)
};
button_mic_app.on_select = [this](Button&) {
if (frequency_list.size() > 0 && current_index >= 0 && (unsigned)current_index < frequency_list.size()) {
if (frequency_list[current_index].type == HAMRADIO) {
// if it's a HAMRADIO entry, then frequency_a is the freq at which the repeater reveive, so we have to set it in transmit in mic app
transmitter_model.set_tuning_frequency(frequency_list[current_index].frequency_a);
// if it's a HAMRADIO entry, then frequency_b is the freq at which the repeater transmit, so we have to set it in receive in mic app
receiver_model.set_tuning_frequency(frequency_list[current_index].frequency_b);
} else {
// it's single or range so we us actual tuned frequency
transmitter_model.set_tuning_frequency(freq);
receiver_model.set_tuning_frequency(freq);
}
}
// there is no way yet to set modulation and bandwidth from Recon to MicApp
nav_.pop();
nav_.push<MicTXView>();
};
@@ -711,18 +716,18 @@ ReconView::ReconView(NavigationView& nav)
frequency_list.push_back(manual_freq_entry);
big_display.set_style(&style_white); // Back to white color
big_display.set_style(&Styles::white); // Back to white color
freq_stats.set_style(&style_white);
freq_stats.set_style(&Styles::white);
freq_stats.set("0/0/0");
text_cycle.set_text("1");
text_max.set("/1");
button_scanner_mode.set_style(&style_white);
button_scanner_mode.set_style(&Styles::white);
button_scanner_mode.set_text("MSEARCH");
file_name.set_style(&style_white);
file_name.set_style(&Styles::white);
file_name.set("MANUAL RANGE RECON");
desc_cycle.set_style(&style_white);
desc_cycle.set_style(&Styles::white);
last_entry.modulation = -1;
last_entry.bandwidth = -1;
@@ -762,12 +767,12 @@ ReconView::ReconView(NavigationView& nav)
recon_resume();
}
if (scanner_mode) {
file_name.set_style(&style_red);
button_scanner_mode.set_style(&style_red);
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
} else {
file_name.set_style(&style_blue);
button_scanner_mode.set_style(&style_blue);
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
};
@@ -787,12 +792,12 @@ ReconView::ReconView(NavigationView& nav)
manual_mode = false;
if (scanner_mode) {
scanner_mode = false;
button_scanner_mode.set_style(&style_blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
button_remove.set_text("DELETE");
} else {
scanner_mode = true;
button_scanner_mode.set_style(&style_red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
button_scanner_mode.set_text("REMOVE");
}
@@ -855,12 +860,8 @@ ReconView::ReconView(NavigationView& nav)
squelch = v;
};
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
// PRE-CONFIGURATION:
button_scanner_mode.set_style(&style_blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
file_name.set("=>");
@@ -873,8 +874,6 @@ ReconView::ReconView(NavigationView& nav)
field_lock_wait.set_value(recon_lock_duration);
colorize_waits();
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
// fill modulation and step options
freqman_set_modulation_option(field_mode);
freqman_set_step_option(step_mode);
@@ -908,31 +907,31 @@ void ReconView::frequency_file_load(bool stop_all_before) {
std::string file_input = input_file; // default recon mode
if (scanner_mode) {
file_input = output_file;
file_name.set_style(&style_red);
button_scanner_mode.set_style(&style_red);
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
} else {
file_name.set_style(&style_blue);
button_scanner_mode.set_style(&style_blue);
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
desc_cycle.set_style(&style_white);
if (!load_freqman_file_ex(file_input, frequency_list, load_freqs, load_ranges, load_hamradios)) {
file_name.set_style(&style_red);
desc_cycle.set_style(&style_red);
desc_cycle.set_style(&Styles::white);
if (!load_freqman_file_ex(file_input, frequency_list, load_freqs, load_ranges, load_hamradios, RECON_FREQMAN_MAX_PER_FILE)) {
file_name.set_style(&Styles::red);
desc_cycle.set_style(&Styles::red);
desc_cycle.set(" NO " + file_input + ".TXT FILE ...");
file_name.set("=> NO DATA");
} else {
file_name.set("=> " + file_input);
if (frequency_list.size() == 0) {
file_name.set_style(&style_red);
desc_cycle.set_style(&style_red);
file_name.set_style(&Styles::red);
desc_cycle.set_style(&Styles::red);
desc_cycle.set("/0 no entries in list");
file_name.set("BadOrEmpty " + file_input);
} else {
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&style_yellow);
desc_cycle.set_style(&style_yellow);
if (frequency_list.size() > RECON_FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
desc_cycle.set_style(&Styles::yellow);
}
}
}
@@ -1025,7 +1024,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
continuous_lock = false;
freq_lock = 0;
timer = recon_lock_duration;
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
}
if (freq_lock < recon_lock_nb_match) // LOCKING
{
@@ -1240,7 +1239,7 @@ void ReconView::recon_pause() {
audio_output_start();
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
button_pause.set_text("<RESUME>"); // PAUSED, show resume
}
@@ -1252,7 +1251,7 @@ void ReconView::recon_resume() {
audio::output::stop();
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
button_pause.set_text("<PAUSE>");
}
@@ -1288,11 +1287,6 @@ void ReconView::on_stepper_delta(int32_t v) {
timer = 0;
}
void ReconView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
size_t ReconView::change_mode(freqman_index_t new_mod) {
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
@@ -1304,11 +1298,11 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
case AM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw DSB (0) default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
@@ -1316,22 +1310,22 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
case NFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 16k (2) default
field_bw.set_selected_index(2);
field_bw.set_by_value(2);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
+4 -52
View File
@@ -27,7 +27,7 @@
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
@@ -44,6 +44,7 @@
namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
#define RECON_FREQMAN_MAX_PER_FILE 99 // maximum tested limit for load frequency from freqman to work
class ReconView : public View {
public:
@@ -52,48 +53,6 @@ class ReconView : public View {
void focus() override;
const Style style_grey{
// recon
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_white{
// recon
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_yellow{
// found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_blue{
// quick recon, wait == 0
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
std::string title() const override { return "Recon"; };
// void set_parent_rect(const Rect new_parent_rect) override;
@@ -111,7 +70,6 @@ class ReconView : public View {
void recon_resume();
void frequency_file_load(bool stop_all_before = false);
void on_statistics_update(const ChannelStatistics& statistics);
void on_headphone_volume_changed(int32_t v);
void on_index_delta(int32_t v);
void on_stepper_delta(int32_t v);
void colorize_waits();
@@ -149,7 +107,6 @@ class ReconView : public View {
bool scanner_mode{false};
bool manual_mode{false};
bool sd_card_mounted = false;
int32_t volume = 40;
int32_t stepper = 0;
int32_t index_stepper = 0;
int64_t freq = 0;
@@ -195,13 +152,8 @@ class ReconView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
@@ -60,7 +60,7 @@
//#define SCREEN_H 320
// recon settings nb params
#define RECON_SETTINGS_NB_PARAMS 8
#define RECON_SETTINGS_NB_PARAMS 7
namespace ui {
+18 -26
View File
@@ -179,16 +179,16 @@ void ScannerView::bigdisplay_update(int32_t v) {
switch (bigdisplay_current_color) {
case BDC_GREY:
big_display.set_style(&style_grey);
big_display.set_style(&Styles::grey);
break;
case BDC_YELLOW:
big_display.set_style(&style_yellow);
big_display.set_style(&Styles::yellow);
break;
case BDC_GREEN:
big_display.set_style(&style_green);
big_display.set_style(&Styles::green);
break;
case BDC_RED:
big_display.set_style(&style_red);
big_display.set_style(&Styles::red);
break;
default:
break;
@@ -254,10 +254,10 @@ void ScannerView::show_max_index() { // show total number of freqs to scan
text_current_index.set("---");
if (frequency_list.size() == FREQMAN_MAX_PER_FILE) {
text_max_index.set_style(&style_red);
text_max_index.set_style(&Styles::red);
text_max_index.set("/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE) + " (DB MAX!)");
} else {
text_max_index.set_style(&style_grey);
text_max_index.set_style(&Styles::grey);
text_max_index.set("/ " + to_string_dec_uint(frequency_list.size()));
}
}
@@ -558,9 +558,6 @@ ScannerView::ScannerView(
field_squelch.on_change = [this](int32_t v) { squelch = v; };
field_squelch.set_value(-10);
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
// LEARN FREQUENCIES
std::string scanner_txt = "SCANNER";
frequency_file_load(scanner_txt);
@@ -667,8 +664,8 @@ void ScannerView::update_squelch_while_paused(int32_t max_db) {
// Update audio & color based on signal level even if paused
if (++color_timer > 2) { // Counter to reduce color toggling when weak signal
if (max_db > squelch) {
audio::output::start(); // Re-enable audio when signal goes above squelch
on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause
audio::output::start(); // Re-enable audio when signal goes above squelch
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // quick fix to make sure WM8731S chips don't stay silent after pause
bigdisplay_update(BDC_GREEN);
} else {
audio::output::stop(); // Silence audio when signal drops below squelch
@@ -730,7 +727,7 @@ void ScannerView::scan_pause() {
scan_thread->set_scanning(false); // WE STOP SCANNING
}
audio::output::start();
on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // quick fix to make sure WM8731S chips don't stay silent after pause
}
void ScannerView::scan_resume() {
@@ -749,11 +746,6 @@ void ScannerView::user_resume() {
userpause = false; // Resume scanning
}
void ScannerView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a scan_thread->stop(); After this do a start_scan_thread()
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
@@ -767,9 +759,9 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
field_bw.set_selected_index(0);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
field_bw.set_by_value(0);
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
@@ -777,9 +769,9 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
field_bw.set_selected_index(2);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
field_bw.set_by_value(2);
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
@@ -787,9 +779,9 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
field_bw.set_selected_index(0);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
field_bw.set_by_value(0);
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(2000000);
break;
+3 -37
View File
@@ -23,7 +23,7 @@
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
@@ -87,34 +87,6 @@ class ScannerView : public View {
void focus() override;
const Style style_grey{
// scanning
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_yellow{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::dark_yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
std::string title() const override { return "Scanner"; };
std::vector<rf::Frequency> frequency_list{};
std::vector<string> description_list{};
@@ -134,7 +106,6 @@ class ScannerView : public View {
void bigdisplay_update(int32_t);
void update_squelch_while_paused(int32_t max_db);
void on_statistics_update(const ChannelStatistics& statistics);
void on_headphone_volume_changed(int32_t v);
void handle_retune(int64_t freq, uint32_t freq_idx);
jammer::jammer_range_t frequency_range{false, 0, 0}; // perfect for manual scan task too...
@@ -182,13 +153,8 @@ class ScannerView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
+7 -7
View File
@@ -117,7 +117,7 @@ void SearchView::do_detection() {
recent_entries_view.set_dirty();
text_infos.set("Locked ! ");
big_display.set_style(&style_locked);
big_display.set_style(&Styles::green);
locked = true;
locked_bin = bin_max;
@@ -152,7 +152,7 @@ void SearchView::do_detection() {
recent_entries_view.set_dirty();
text_infos.set("Listening");
big_display.set_style(&style_grey);
big_display.set_style(&Styles::grey);
}
}
}
@@ -362,11 +362,11 @@ SearchView::SearchView(
nav.push<FrequencyKeypadView>(entry.frequency);
};
text_mean.set_style(&style_grey);
text_slices.set_style(&style_grey);
text_rate.set_style(&style_grey);
progress_timers.set_style(&style_grey);
big_display.set_style(&style_grey);
text_mean.set_style(&Styles::grey);
text_slices.set_style(&Styles::grey);
text_rate.set_style(&Styles::grey);
progress_timers.set_style(&Styles::grey);
big_display.set_style(&Styles::grey);
check_snap.set_value(true);
options_snap.set_selected_index(1); // 12.5kHz
+1 -14
View File
@@ -25,7 +25,7 @@
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "recent_entries.hpp"
namespace ui {
@@ -90,19 +90,6 @@ class SearchView : public View {
private:
NavigationView& nav_;
const Style style_grey{
// For informations and lost signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_locked{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
struct slice_t {
rf::Frequency center_frequency;
uint8_t max_power;
+11 -22
View File
@@ -36,7 +36,7 @@ using portapack::receiver_model;
using namespace portapack;
#include "string_format.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "cpld_update.hpp"
#include "freqman.hpp"
@@ -129,9 +129,9 @@ SetRadioView::SetRadioView(
value_source.set(source_name);
value_source_frequency.set(to_string_dec_uint(reference.frequency / 1000000, 2) + "." + to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
label_source.set_style(&style_text);
value_source.set_style(&style_text);
value_source_frequency.set_style(&style_text);
label_source.set_style(&Styles::light_grey);
value_source.set_style(&Styles::light_grey);
value_source_frequency.set_style(&Styles::light_grey);
add_children({
&label_source,
@@ -169,7 +169,7 @@ SetRadioView::SetRadioView(
};
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
value_freq_step.set_style(&style_text);
value_freq_step.set_style(&Styles::light_grey);
field_clkout_freq.on_select = [this](NumberField&) {
freq_step_khz++;
@@ -446,27 +446,16 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
add_children({&text_pmem_about,
&text_pmem_informations,
&text_pmem_status,
&check_load_mem_at_startup,
&check_use_sdcard_for_pmem,
&button_save_mem_to_file,
&button_load_mem_from_file,
&button_load_mem_defaults,
&button_return});
bool load_mem_at_startup = false;
File pmem_flag_file_handle;
std::string folder = "SETTINGS";
make_new_directory(folder);
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG";
auto result = pmem_flag_file_handle.open(pmem_flag_file);
if (!result.is_valid()) {
load_mem_at_startup = true;
}
check_load_mem_at_startup.set_value(load_mem_at_startup);
check_load_mem_at_startup.on_select = [this](Checkbox&, bool v) {
check_use_sdcard_for_pmem.set_value(portapack::persistent_memory::should_use_sdcard_for_pmem());
check_use_sdcard_for_pmem.on_select = [this](Checkbox&, bool v) {
File pmem_flag_file_handle;
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG";
std::string pmem_flag_file = PMEM_FILEFLAG;
if (v) {
auto result = pmem_flag_file_handle.open(pmem_flag_file);
if (result.is_valid()) {
@@ -490,7 +479,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
};
button_save_mem_to_file.on_select = [&nav, this](Button&) {
if (!portapack::persistent_memory::save_persistent_settings_to_file("SETTINGS/pmem_settings")) {
if (!portapack::persistent_memory::save_persistent_settings_to_file()) {
text_pmem_status.set("!problem saving settings!");
} else {
text_pmem_status.set("settings saved");
@@ -498,7 +487,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
};
button_load_mem_from_file.on_select = [&nav, this](Button&) {
if (!portapack::persistent_memory::load_persistent_settings_from_file("SETTINGS/pmem_settings")) {
if (!portapack::persistent_memory::load_persistent_settings_from_file()) {
text_pmem_status.set("!problem loading settings!");
} else {
text_pmem_status.set("settings loaded");
+2 -7
View File
@@ -127,11 +127,6 @@ class SetRadioView : public View {
std::string title() const override { return "Radio"; };
private:
const Style style_text{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::light_grey(),
};
uint8_t freq_step_khz = 3;
Text label_source{
@@ -485,10 +480,10 @@ class SetPersistentMemoryView : public View {
{0, 3 * 16, 240, 16},
""};
Checkbox check_load_mem_at_startup{
Checkbox check_use_sdcard_for_pmem{
{18, 6 * 16},
19,
"load from sd at startup"};
"use sdcard for p.mem"};
Button button_save_mem_to_file{
{0, 8 * 16, 240, 32},
+3 -3
View File
@@ -131,9 +131,9 @@ SIGFRXView::SIGFRXView(
&text_data,
&button_exit});
text_type.set_style(&style_white);
text_channel.set_style(&style_white);
text_data.set_style(&style_white);
text_type.set_style(&Styles::bg_white);
text_channel.set_style(&Styles::bg_white);
text_data.set_style(&Styles::bg_white);
button_exit.on_select = [&nav](Button&) {
nav.pop();
+1 -6
View File
@@ -24,8 +24,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_menu.hpp"
#include "ui_styles.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "clock_manager.hpp"
#include "message.hpp"
#include "rf_path.hpp"
@@ -50,11 +50,6 @@ class SIGFRXView : public View {
uint8_t last_channel;
uint8_t detect_counter = 0;
const Style style_white{
.font = font::fixed_8x16,
.background = Color::white(),
.foreground = Color::black()};
const uint16_t sigfrx_marks[18] = {
10, 8, 0,
60, 52, 90,
-12
View File
@@ -128,13 +128,6 @@ SondeView::SondeView(NavigationView& nav) {
if (logger)
logger->append(LOG_ROOT_DIR "/SONDE.TXT");
// initialize audio:
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
audio::output::start();
audio::output::speaker_unmute();
@@ -251,11 +244,6 @@ void SondeView::on_packet(const sonde::Packet& packet) {
}
}
void SondeView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
void SondeView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(target_frequency_);
+2 -8
View File
@@ -109,13 +109,8 @@ class SondeView : public View {
{21 * 8, 0, 6 * 8, 4},
};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_beep{
{22 * 8, 6 * 16},
@@ -181,7 +176,6 @@ class SondeView : public View {
}};
void on_packet(const sonde::Packet& packet);
void on_headphone_volume_changed(int32_t v);
char* float_to_char(float x, char* p);
void set_target_frequency(const uint32_t new_value);
@@ -234,14 +234,14 @@ std::vector<uint8_t> SpectrumInputImageView::get_line(uint16_t y) {
grey_buffer[px] = color.to_greyscale();
}
delete buffer;
delete[] buffer;
std::vector<uint8_t> values(width);
for (int i = 0; i < width; i++) {
values[i] = grey_buffer[i];
}
delete grey_buffer;
delete[] grey_buffer;
return values;
}
@@ -20,6 +20,7 @@
*/
#include "ui_spectrum_painter_text.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "cpld_update.hpp"
#include "bmp.hpp"
#include "baseband_api.hpp"
@@ -28,7 +29,6 @@
#include "io_file.hpp"
#include "file.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_ss_viewer.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
ScreenshotViewer::ScreenshotViewer(
NavigationView& nav,
const std::filesystem::path& path)
: nav_{nav},
path_{path} {
set_focusable(true);
}
bool ScreenshotViewer::on_key(KeyEvent) {
nav_.pop();
return true;
}
void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t pixel_width = 240;
constexpr size_t pixel_height = 320;
File file{};
painter.fill_rectangle({0, 0, pixel_width, pixel_height}, Color::black());
auto show_invalid = [&]() {
painter.draw_string({10, 160}, Styles::white, "Not a valid screenshot.");
};
auto error = file.open(path_);
if (error) {
painter.draw_string({10, 160}, Styles::white, error->what());
return;
}
// Screenshots from PNGWriter are all this size.
if (file.size() != 232383) {
show_invalid();
return;
}
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
std::array<Color, pixel_width> pixel_data;
// Seek past all the headers.
file.seek(43);
for (auto line = 0u; line < pixel_height; ++line) {
// Seek past the per-line header.
file.seek(file.tell() + 6);
// Per comment in PNGWriter, read in chunks of 80.
// NB: Reading in one large chunk caused corruption so there's
// likely a bug lurking in the SD Card/FatFs layer.
for (auto offset = 0u; offset < pixel_width; offset += read_chunk) {
auto read = file.read(buffer, buffer_size);
if (!read || *read != buffer_size) {
show_invalid();
return;
}
auto c8 = (ColorRGB888*)buffer;
for (auto i = 0u; i < read_chunk; ++i) {
pixel_data[i + offset] = Color(c8->r, c8->g, c8->b);
++c8;
}
}
display.draw_pixels({0, (int)line, pixel_width, 1}, pixel_data);
}
}
} // namespace ui
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_SS_VIEWER_H__
#define __UI_SS_VIEWER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include "file.hpp"
namespace ui {
class ScreenshotViewer : public View {
public:
ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path);
bool on_key(KeyEvent key) override;
void paint(Painter& painter) override;
private:
NavigationView& nav_;
std::filesystem::path path_{};
};
} // namespace ui
#endif // __UI_SS_VIEWER_H__
-1
View File
@@ -27,7 +27,6 @@
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "baseband_api.hpp"
#include "ui_transmitter.hpp"
#include "message.hpp"
+168 -253
View File
@@ -39,216 +39,6 @@ namespace {
namespace ui {
/* FileWrapper ******************************************************/
FileWrapper::FileWrapper() {
}
Optional<FileWrapper::Error> FileWrapper::open(const fs::path& path) {
file_ = File();
auto error = file_.open(path);
if (!error)
initialize();
return error;
}
std::string FileWrapper::get_text(Offset line, Offset col, Offset length) {
// TODO: better way to return errors.
auto range = line_range(line);
int32_t to_read = length;
if (!range)
return "[UNCACHED LINE]";
// Don't read past end of line.
if (range->start + col + to_read >= range->end)
to_read = range->end - col - range->start;
if (to_read <= 0)
return {};
return read(range->start + col, to_read);
}
Optional<FileWrapper::Range> FileWrapper::line_range(Line line) {
ensure_cached(line);
auto offset = offset_for_line(line);
if (!offset)
return {};
auto start = *offset == 0 ? start_offset_ : (newlines_[*offset - 1] + 1);
auto end = newlines_[*offset] + 1;
return {Range{start, end}};
}
FileWrapper::Offset FileWrapper::line_length(Line line) {
auto range = line_range(line);
if (range)
return range->end - range->start;
return 0;
}
void FileWrapper::initialize() {
start_offset_ = 0;
start_line_ = 0;
line_count_ = 0;
newlines_.clear();
line_ending_ = LineEnding::LF;
Offset offset = 0;
auto result = next_newline(offset);
while (result) {
++line_count_;
if (newlines_.size() < max_newlines)
newlines_.push_back(*result);
offset = *result + 1;
result = next_newline(offset);
}
}
std::string FileWrapper::read(Offset offset, Offset length) {
// TODO: better way to return errors.
if (offset + length > file_.size())
return {"[BAD OFFSET]"};
std::string buffer(length, '\0');
file_.seek(offset);
auto result = file_.read(&buffer[0], length);
if (result.is_ok())
buffer.resize(*result);
else
return result.error().what();
return buffer;
}
Optional<FileWrapper::Offset> FileWrapper::offset_for_line(Line line) const {
if (line >= line_count_)
return {};
Offset actual = line - start_line_;
if (actual < newlines_.size()) // NB: underflow wrap.
return {actual};
return {};
}
void FileWrapper::ensure_cached(Line line) {
if (line >= line_count_)
return;
auto result = offset_for_line(line);
if (result)
return;
if (line < start_line_) {
while (line < start_line_ && start_offset_ >= 2) {
// start_offset_ - 1 should be a newline. Need to
// find the new value for start_offset_. start_line_
// has to be > 0 to get into this block so there should
// always be one newline before start_offset_.
auto offset = previous_newline(start_offset_ - 2);
newlines_.push_front(start_offset_ - 1);
if (!offset) {
// Must be at beginning.
start_line_ = 0;
start_offset_ = 0;
} else {
// Found an previous newline, the new start_line_
// starts at the newline offset + 1.
start_line_--;
start_offset_ = *offset + 1;
}
}
} else {
while (line >= start_line_ + newlines_.size()) {
auto offset = next_newline(newlines_.back() + 1);
if (offset) {
start_line_++;
start_offset_ = newlines_.front() + 1;
newlines_.push_back(*offset);
} /* else at the EOF. */
}
}
}
Optional<FileWrapper::Offset> FileWrapper::previous_newline(Offset start) {
char buffer[buffer_size];
Offset offset = start;
auto to_read = buffer_size;
do {
if (offset < to_read) {
// NB: Char at 'offset' was read in the previous iteration.
to_read = offset;
offset = 0;
} else
offset -= to_read;
file_.seek(offset);
auto result = file_.read(buffer, to_read);
if (result.is_error())
break;
// Find newlines in the buffer backwards.
for (int32_t i = *result - 1; i >= 0; --i) {
switch (buffer[i]) {
case '\n':
return {offset + i};
}
}
if (offset == 0)
break;
} while (true);
return {}; // Didn't find one.
}
Optional<FileWrapper::Offset> FileWrapper::next_newline(Offset start) {
char buffer[buffer_size];
Offset offset = start;
// EOF, nothing to do.
if (start >= size())
return {};
file_.seek(offset);
while (true) {
auto result = file_.read(buffer, buffer_size);
if (result.is_error())
return {};
// Find newlines in the buffer.
for (Offset i = 0; i < *result; ++i) {
switch (buffer[i]) {
case '\n':
return {offset + i};
}
}
offset += *result;
if (*result < buffer_size)
break;
}
// Fake a newline at the end for consistency.
return {offset};
}
/* TextViewer *******************************************************/
TextViewer::TextViewer(Rect parent_rect)
@@ -334,6 +124,22 @@ bool TextViewer::on_encoder(EncoderEvent delta) {
return updated;
}
void TextViewer::redraw(bool redraw_text) {
paint_state_.redraw_text = redraw_text;
set_dirty();
}
uint32_t TextViewer::offset() const {
auto range = file_->line_range(cursor_.line);
if (range)
return range->start + col();
return 0;
}
uint16_t TextViewer::line_length() {
return file_->line_length(cursor_.line);
}
bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
if (!has_file())
return false;
@@ -385,38 +191,30 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
return true;
}
void TextViewer::redraw(bool redraw_text) {
paint_state_.redraw_text = redraw_text;
set_dirty();
}
void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
// CONSIDER: A line cache would use more memory but save a lot of IO.
// Only the new lines/characters would need to be refetched.
auto r = screen_rect();
char buffer[max_col + 1];
// Draw the lines from the file
for (auto i = 0u; i < max_line; ++i) {
if (line + i >= file_->line_count())
break;
auto str = file_->get_text(line + i, col, max_col);
auto result = file_->get_text(line + i, col, buffer, max_col);
// Draw text.
if (str.length() > 0)
if (result && *result > 0)
painter.draw_string(
{0, r.top() + (int)i * char_height},
style_text, str);
Styles::white_small, {buffer, *result});
// Clear empty line sections.
int32_t clear_width = max_col - str.length();
// Clear empty line sections. This is less visually jarring than full clear.
int32_t clear_width = max_col - (result ? *result : 0);
if (clear_width > 0)
painter.fill_rectangle(
{(max_col - clear_width) * char_width,
r.top() + (int)i * char_height,
clear_width * char_width, char_height},
style_text.background);
Styles::white_small.background);
}
}
@@ -437,8 +235,8 @@ void TextViewer::paint_cursor(Painter& painter) {
};
// Clear old cursor. CONSIDER: XOR cursor?
draw_cursor(paint_state_.line, paint_state_.col, style_text.background);
draw_cursor(cursor_.line, cursor_.col, style_text.foreground);
draw_cursor(paint_state_.line, paint_state_.col, Styles::white_small.background);
draw_cursor(cursor_.line, cursor_.col, Styles::white_small.foreground);
paint_state_.line = cursor_.line;
paint_state_.col = cursor_.col;
}
@@ -452,10 +250,6 @@ void TextViewer::reset_file(FileWrapper* file) {
redraw(true);
}
uint16_t TextViewer::line_length() {
return file_->line_length(cursor_.line);
}
/* TextEditorMenu ***************************************************/
TextEditorMenu::TextEditorMenu()
@@ -523,25 +317,52 @@ TextEditorView::TextEditorView(NavigationView& nav)
menu.on_copy() = [this]() {
show_nyi();
};
menu.on_delete_line() = [this]() {
show_nyi();
prepare_for_write();
file_->delete_line(viewer.line());
refresh_ui();
hide_menu(true);
};
menu.on_edit_line() = [this]() {
show_nyi();
show_edit_line();
};
menu.on_add_line() = [this]() {
show_nyi();
prepare_for_write();
if (viewer.offset() < file_->size() - 1)
file_->insert_line(viewer.line());
else
file_->insert_line(-1); // Add after last line.
refresh_ui();
hide_menu(true);
};
menu.on_open() = [this]() {
// TODO: confirm.
show_file_picker();
/*show_save_prompt([this]() {
show_file_picker();
});*/
// HACK: above should work but it's faulting.
if (!file_dirty_) {
show_file_picker();
} else {
show_save_prompt(nullptr);
show_file_picker(false);
}
};
menu.on_save() = [this]() {
show_nyi();
save_temp_file();
hide_menu(true);
};
menu.on_exit() = [this]() {
// TODO: confirm.
nav_.pop();
show_save_prompt([this]() {
nav_.pop();
});
};
button_menu.on_select = [this]() {
@@ -559,6 +380,10 @@ TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
open_file(path);
}
TextEditorView::~TextEditorView() {
delete_temp_file();
}
void TextEditorView::on_show() {
if (file_)
viewer.focus();
@@ -567,15 +392,21 @@ void TextEditorView::on_show() {
}
void TextEditorView::open_file(const fs::path& path) {
auto file = std::make_unique<FileWrapper>();
auto error = file->open(path);
file_.reset();
viewer.clear_file();
delete_temp_file();
path_ = {};
file_dirty_ = false;
has_temp_file_ = false;
auto result = FileWrapper::open(path);
if (!result) {
nav_.display_modal("Read Error", "Cannot open file:\n" + result.error().what());
if (error) {
nav_.display_modal("Read Error", "Cannot open file:\n" + error->what());
file_.reset();
viewer.clear_file();
} else {
file_ = std::move(file);
file_ = *std::move(result);
path_ = path;
viewer.set_file(*file_);
}
@@ -616,16 +447,100 @@ void TextEditorView::hide_menu(bool hidden) {
set_dirty();
}
void TextEditorView::show_file_picker() {
auto open_view = nav_.push<FileLoadView>("");
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
hide_menu();
};
void TextEditorView::show_file_picker(bool immediate) {
// TODO: immediate is a hack until nav_.on_pop is fixed.
auto open_view = immediate ? nav_.push<FileLoadView>("") : nav_.push_under_current<FileLoadView>("");
if (open_view) {
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
hide_menu();
};
}
}
void TextEditorView::show_edit_line() {
auto str = file_->get_text(viewer.line(), 0, viewer.line_length());
if (!str) {
nav_.display_modal("Error", "Failed to get line text.");
return;
}
edit_line_buffer_ = *std::move(str);
text_prompt(
nav_,
edit_line_buffer_,
viewer.col(),
max_edit_length,
[this](std::string& buffer) {
auto range = file_->line_range(viewer.line());
if (!range)
return;
prepare_for_write();
file_->replace_range(*range, buffer);
});
nav_.set_on_pop([this]() {
edit_line_buffer_.clear();
refresh_ui();
hide_menu(true);
});
}
void TextEditorView::show_nyi() {
nav_.display_modal("Soon...", "Coming soon.");
}
} // namespace ui
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
if (!file_dirty_) {
if (continuation)
continuation();
return;
}
nav_.display_modal(
"Save?", "Save changes?", YESNO,
[this](bool choice) {
if (choice)
save_temp_file();
});
nav_.set_on_pop(continuation);
}
void TextEditorView::prepare_for_write() {
file_dirty_ = true;
if (has_temp_file_)
return;
// Copy to temp file on write.
has_temp_file_ = true;
delete_temp_file();
copy_file(path_, get_temp_path());
file_->assume_file(get_temp_path());
}
void TextEditorView::delete_temp_file() const {
auto temp_path = get_temp_path();
if (!temp_path.empty()) {
delete_file(temp_path);
}
}
void TextEditorView::save_temp_file() {
if (file_dirty_) {
delete_file(path_);
copy_file(get_temp_path(), path_);
file_dirty_ = false;
}
}
fs::path TextEditorView::get_temp_path() const {
if (!path_.empty())
return path_ + "~";
return {};
}
} // namespace ui
+28 -83
View File
@@ -19,99 +19,32 @@
* Boston, MA 02110-1301, USA.
*/
/* TODO:
* - Busy indicator while reading files.
*/
#ifndef __UI_TEXT_EDITOR_H__
#define __UI_TEXT_EDITOR_H__
#include "ui.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include "circular_buffer.hpp"
#include "file.hpp"
#include "file_wrapper.hpp"
#include "optional.hpp"
#include <memory>
#include <string>
#include <vector>
namespace ui {
/* TODO:
* - Copy on write into temp file so startup is fast.
*/
enum class LineEnding : uint8_t {
LF,
CRLF
};
enum class ScrollDirection : uint8_t {
Vertical,
Horizontal
};
/* Wraps a file and provides an API for accessing lines efficiently. */
class FileWrapper {
public:
using Error = std::filesystem::filesystem_error;
using Offset = uint32_t; // TODO: make enums?
using Line = uint32_t;
using Column = uint32_t;
using Range = struct {
// Offset of the line start.
Offset start;
// Offset of one past the line end.
Offset end;
};
FileWrapper();
/* Prevent copies. */
FileWrapper(const FileWrapper&) = delete;
FileWrapper& operator=(const FileWrapper&) = delete;
Optional<Error> open(const std::filesystem::path& path);
std::string get_text(Line line, Column col, Offset length);
File::Size size() const { return file_.size(); }
uint32_t line_count() const { return line_count_; }
Optional<Range> line_range(Line line);
Offset line_length(Line line);
private:
/* Number of newline offsets to cache. */
static constexpr Offset max_newlines = 64;
static constexpr size_t buffer_size = 512;
void initialize();
std::string read(Offset offset, Offset length = 30);
/* Returns the offset into the newline cache if valid. */
Optional<Offset> offset_for_line(Line line) const;
/* Ensure specified line is in the newline cache. */
void ensure_cached(Line line);
/* Helpers for finding the prev/next newline. */
Optional<Offset> previous_newline(Offset start);
Optional<Offset> next_newline(Offset start);
File file_{};
/* Total number of lines in the file. */
Offset line_count_{0};
/* The offset and line of the newlines cache. */
Offset start_offset_{0};
Offset start_line_{0};
LineEnding line_ending_{LineEnding::LF};
CircularBuffer<Offset, max_newlines + 1> newlines_{};
};
/* Control that renders a text file. */
class TextViewer : public Widget {
public:
@@ -126,7 +59,7 @@ class TextViewer : public Widget {
std::function<void()> on_cursor_moved{};
void paint(Painter& painter) override;
bool on_key(KeyEvent delta) override;
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
void redraw(bool redraw_text = false);
@@ -137,15 +70,14 @@ class TextViewer : public Widget {
uint32_t line() const { return cursor_.line; }
uint32_t col() const { return cursor_.col; }
uint32_t offset() const;
// Gets the length of the current line.
uint16_t line_length();
private:
static constexpr int8_t char_width = 5;
static constexpr int8_t char_height = 8;
static constexpr Style style_text{
.font = font::fixed_5x8,
.background = Color::black(),
.foreground = Color::white(),
};
const uint8_t max_line = 32;
const uint8_t max_col = 48;
@@ -160,9 +92,6 @@ class TextViewer : public Widget {
void reset_file(FileWrapper* file = nullptr);
// Gets the length of the current line.
uint16_t line_length();
FileWrapper* file_{};
struct {
@@ -272,6 +201,7 @@ class TextEditorView : public View {
TextEditorView(
NavigationView& nav,
const std::filesystem::path& path);
~TextEditorView();
std::string title() const override {
return "Notepad";
@@ -280,15 +210,30 @@ class TextEditorView : public View {
void on_show() override;
private:
static constexpr size_t max_edit_length = 1024;
std::string edit_line_buffer_{};
void open_file(const std::filesystem::path& path);
void save_file();
void refresh_ui();
void update_position();
void hide_menu(bool hidden = true);
void show_file_picker();
void show_file_picker(bool immediate = true);
void show_edit_line();
void show_nyi();
void show_save_prompt(std::function<void()> continuation);
void prepare_for_write();
void create_temp_file() const;
void delete_temp_file() const;
void save_temp_file();
std::filesystem::path get_temp_path() const;
NavigationView& nav_;
std::unique_ptr<FileWrapper> file_{};
std::filesystem::path path_{};
bool file_dirty_{false};
bool has_temp_file_{false};
TextViewer viewer{
/* 272 = 320 - 16 (top bar) - 32 (bottom controls) */
@@ -23,7 +23,6 @@
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
+15 -22
View File
@@ -26,10 +26,11 @@
#include <hal.h>
#include <string>
#include "ui_painter.hpp"
#include "portapack.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "string_format.hpp"
#include "ui_styles.hpp"
using namespace ui;
void runtime_error(LED);
std::string number_to_hex_string(uint32_t);
@@ -37,33 +38,29 @@ void draw_line(int32_t, const char*, regarm_t);
static bool error_shown = false;
void draw_guru_meditation_header(uint8_t source, const char* hint) {
ui::Painter painter;
ui::Style style_default{
.font = ui::font::fixed_8x16,
.background = ui::Color::black(),
.foreground = ui::Color::white()};
Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::red());
Color::red());
constexpr int border = 8;
painter.fill_rectangle(
{border, border, portapack::display.width() - (border * 2), portapack::display.height() - (border * 2)},
ui::Color::black());
Color::black());
// NOTE: in situations like a hard fault it seems not possible to write strings longer than 16 characters.
painter.draw_string({48, 24}, style_default, "M? Guru");
painter.draw_string({48 + 8 * 8, 24}, style_default, "Meditation");
painter.draw_string({48, 24}, Styles::white, "M? Guru");
painter.draw_string({48 + 8 * 8, 24}, Styles::white, "Meditation");
if (source == CORTEX_M0)
painter.draw_string({48 + 8, 24}, style_default, "0");
painter.draw_string({48 + 8, 24}, Styles::white, "0");
if (source == CORTEX_M4)
painter.draw_string({48 + 8, 24}, style_default, "4");
painter.draw_string({48 + 8, 24}, Styles::white, "4");
painter.draw_string({15, 55}, style_default, "Hint: ");
painter.draw_string({15 + 8 * 8, 55}, style_default, hint);
painter.draw_string({15, 55}, Styles::white, "Hint: ");
painter.draw_string({15 + 8 * 8, 55}, Styles::white, hint);
}
void draw_guru_meditation(uint8_t source, const char* hint) {
@@ -114,14 +111,10 @@ void draw_guru_meditation(uint8_t source, const char* hint, struct extctx* ctxp,
}
void draw_line(int32_t y_offset, const char* label, regarm_t value) {
ui::Painter painter;
ui::Style style_default{
.font = ui::font::fixed_8x16,
.background = ui::Color::black(),
.foreground = ui::Color::white()};
Painter painter;
painter.draw_string({15, y_offset}, style_default, label);
painter.draw_string({15 + 8 * 8, y_offset}, style_default, to_string_hex((uint32_t)value, 8));
painter.draw_string({15, y_offset}, Styles::white, label);
painter.draw_string({15 + 8 * 8, y_offset}, Styles::white, to_string_hex((uint32_t)value, 8));
}
void runtime_error(LED led) {
-1
View File
@@ -42,7 +42,6 @@ using namespace lpc43xx;
#include <array>
#include "ui_font_fixed_8x16.hpp"
#include "ui_navigation.hpp"
extern "C" {
+24 -10
View File
@@ -44,8 +44,9 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
}
}
Optional<File::Error> File::open(const std::filesystem::path& filename) {
return open_fatfs(filename, FA_READ);
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only) {
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
return open_fatfs(filename, mode);
}
Optional<File::Error> File::append(const std::filesystem::path& filename) {
@@ -60,7 +61,7 @@ File::~File() {
f_close(&f);
}
File::Result<File::Size> File::read(void* const data, const Size bytes_to_read) {
File::Result<File::Size> File::read(void* data, Size bytes_to_read) {
UINT bytes_read = 0;
const auto result = f_read(&f, data, bytes_to_read, &bytes_read);
if (result == FR_OK) {
@@ -70,7 +71,7 @@ File::Result<File::Size> File::read(void* const data, const Size bytes_to_read)
}
}
File::Result<File::Size> File::write(const void* const data, const Size bytes_to_write) {
File::Result<File::Size> File::write(const void* data, Size bytes_to_write) {
UINT bytes_written = 0;
const auto result = f_write(&f, data, bytes_to_write, &bytes_written);
if (result == FR_OK) {
@@ -84,9 +85,13 @@ File::Result<File::Size> File::write(const void* const data, const Size bytes_to
}
}
File::Result<File::Offset> File::seek(const Offset new_position) {
File::Offset File::tell() const {
return f_tell(&f);
}
File::Result<File::Offset> File::seek(Offset new_position) {
/* NOTE: Returns *old* position, not new position */
const auto old_position = f_tell(&f);
const auto old_position = tell();
const auto result = f_lseek(&f, new_position);
if (result != FR_OK) {
return {static_cast<Error>(result)};
@@ -97,6 +102,15 @@ File::Result<File::Offset> File::seek(const Offset new_position) {
return {static_cast<File::Offset>(old_position)};
}
File::Result<File::Offset> File::truncate() {
const auto position = f_tell(&f);
const auto result = f_truncate(&f);
if (result != FR_OK) {
return {static_cast<Error>(result)};
}
return {static_cast<File::Offset>(position)};
}
File::Size File::size() const {
return f_size(&f);
}
@@ -248,19 +262,19 @@ std::filesystem::filesystem_error copy_file(
File dst;
auto error = src.open(file_path);
if (error.is_valid()) return error.value();
if (error) return error.value();
error = dst.create(dest_path);
if (error.is_valid()) return error.value();
if (error) return error.value();
while (true) {
auto result = src.read(buffer, buffer_size);
if (result.is_error()) return result.error();
result = dst.write(buffer, result.value());
result = dst.write(buffer, *result);
if (result.is_error()) return result.error();
if (result.value() < buffer_size)
if (*result < buffer_size)
break;
}
+26 -11
View File
@@ -288,6 +288,7 @@ class File {
using Timestamp = uint32_t;
using Error = std::filesystem::filesystem_error;
// TODO: move to common.
template <typename T>
struct Result {
enum class Type {
@@ -303,11 +304,15 @@ class File {
return type == Type::Success;
}
operator bool() const {
return is_ok();
}
bool is_error() const {
return type == Type::Error;
}
const T& value() const {
const T& value() const& {
return value_;
}
@@ -315,6 +320,10 @@ class File {
return value_;
}
T&& operator*() && {
return std::move(value_);
}
Error error() const {
return error_;
}
@@ -322,9 +331,9 @@ class File {
Result() = delete;
constexpr Result(
T value)
T&& value)
: type{Type::Success},
value_{value} {
value_{std::forward<T>(value)} {
}
constexpr Result(
@@ -334,31 +343,37 @@ class File {
}
~Result() {
if (type == Type::Success) {
if (is_ok())
value_.~T();
}
}
};
File(){};
~File();
File(File&&) = default;
File& operator=(File&&) = default;
File(File&& other) {
std::swap(f, other.f);
}
File& operator=(File&& other) {
std::swap(f, other.f);
return *this;
}
/* Prevent copies */
File(const File&) = delete;
File& operator=(const File&) = delete;
// TODO: Return Result<>.
Optional<Error> open(const std::filesystem::path& filename);
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true);
Optional<Error> append(const std::filesystem::path& filename);
Optional<Error> create(const std::filesystem::path& filename);
Result<Size> read(void* const data, const Size bytes_to_read);
Result<Size> write(const void* const data, const Size bytes_to_write);
Result<Size> read(void* data, const Size bytes_to_read);
Result<Size> write(const void* data, Size bytes_to_write);
Result<Offset> seek(const uint64_t Offset);
Offset tell() const;
Result<Offset> seek(uint64_t Offset);
Result<Offset> truncate();
// Timestamp created_date() const;
Size size() const;
+507
View File
@@ -0,0 +1,507 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __FILE_WRAPPER_HPP__
#define __FILE_WRAPPER_HPP__
#include "circular_buffer.hpp"
#include "file.hpp"
#include "optional.hpp"
#include <memory>
#include <string_view>
enum class LineEnding : uint8_t {
LF,
CRLF
};
/* TODO:
* - CRLF handling.
* - Avoid full re-read on edits.
* - Would need to read old/new text when editing to track newlines.
* - How to surface errors? Exceptions?
*/
/* FatFs docs http://elm-chan.org/fsw/ff/00index_e.html */
/* BufferType requires the following members
* Size size()
* Result<Size> read(void* data, Size bytes_to_read)
* Result<Size> write(const void* data, Size bytes_to_write)
* Result<Offset> seek(uint32_t offset)
* Result<Offset> truncate()
* Optional<Error> sync()
*/
/* Wraps a buffer and provides an API for accessing lines efficiently. */
template <typename BufferType, uint32_t CacheSize>
class BufferWrapper {
public:
using Offset = uint32_t;
using Line = uint32_t;
using Column = uint32_t;
using Range = struct {
// Offset of the start, inclusive.
Offset start;
// Offset of the end, exclusive.
Offset end;
Offset length() const { return end - start; }
};
BufferWrapper(BufferType* buffer)
: wrapped_{buffer} {
initialize();
}
virtual ~BufferWrapper() {}
/* Prevent copies */
BufferWrapper(const BufferWrapper&) = delete;
BufferWrapper& operator=(const BufferWrapper&) = delete;
Optional<std::string> get_text(Line line, Column col, Offset length) {
std::string buffer;
buffer.resize(length);
auto result = get_text(line, col, &buffer[0], length);
if (!result)
return {};
buffer.resize(*result);
return buffer;
}
Optional<Offset> get_text(Line line, Column col, char* output, Offset length) {
auto range = line_range(line);
int32_t to_read = length;
if (!range)
return {};
// Don't read past end of line.
if (range->start + col + to_read >= range->end)
to_read = range->end - col - range->start;
if (to_read <= 0)
return {};
return read(range->start + col, output, to_read);
}
/* Gets the size of the buffer in bytes. */
File::Size size() const { return wrapped_->size(); }
/* Get the count of the lines in the buffer. */
uint32_t line_count() const { return line_count_; }
/* Gets the range of the line if valid. */
Optional<Range> line_range(Line line) {
ensure_cached(line);
auto index = index_for_line(line);
if (!index)
return {};
auto start = *index == 0 ? start_offset_ : (newlines_[*index - 1] + 1);
auto end = newlines_[*index] + 1;
return Range{start, end};
}
/* Gets the length of the line, or 0 if invalid. */
Offset line_length(Line line) {
auto range = line_range(line);
return range ? range->length() : 0;
}
/* Gets the buffer offset of the line & col if valid. */
Optional<Offset> get_offset(Line line, Column col) {
auto range = line_range(line);
if (range)
return range->start + col;
return {};
}
/* Gets the index of the first line in the cache.
* Only really useful for unit testing or diagnostics. */
Offset start_line() { return start_line_; };
/* Inserts a line before the specified line or at the
* end of the buffer if line >= line_count. */
void insert_line(Line line) {
auto range = line_range(line);
if (range)
replace_range({range->start, range->start}, "\n");
else if (line >= line_count_)
replace_range({(Offset)size(), (Offset)size()}, "\n");
}
/* Deletes the specified line. */
void delete_line(Line line) {
auto range = line_range(line);
if (range)
replace_range(*range, {});
}
/* Replace the specified range with the string contents.
* A range with start/end set to the same value will insert.
* A range with an empty string will delete. */
void replace_range(Range range, std::string_view value) {
if (range.start > size() || range.end > size() || range.start > range.end)
return;
/* If delta_length == 0, it's an overwrite. Could still have
* added or removed newlines so caches will need to be rebuilt.
* If delta_length > 0, the file needs to grow and content needs
* to be shifted forward until the end of the range.
* If delta_length < 0, the file needs to be truncated and the
* content after the value needs to be shifted backward. */
int32_t delta_length = value.length() - range.length();
if (delta_length > 0)
expand(range.end, delta_length);
else if (delta_length < 0)
shrink(range.end, delta_length);
write(range.start, value);
wrapped_->sync();
rebuild_cache();
}
protected:
BufferWrapper() {}
void set_buffer(BufferType* buffer) {
wrapped_ = buffer;
initialize();
}
private:
/* Number of newline offsets to cache. */
static constexpr Offset max_newlines = CacheSize;
/* Size of stack buffer used for reading/writing. */
static constexpr Offset buffer_size = 512;
void initialize() {
start_offset_ = 0;
start_line_ = 0;
line_count_ = 0;
rebuild_cache();
}
void rebuild_cache() {
newlines_.clear();
// Special case for empty files to keep them consistent.
if (size() == 0) {
line_count_ = 1;
newlines_.push_back(0);
return;
}
// TODO: think through this for edit cases.
// E.g. don't read to end, maybe could specify
// a range to re-read because it should be possible
// to tell where the dirty regions are. After the
// dirty region, it should be possible to fixup
// the line_count data.
// TODO: seems like shrink/expand could do this while
// they are running.
line_count_ = start_line_;
Offset offset = start_offset_;
auto result = next_newline(offset);
while (result) {
++line_count_;
if (newlines_.size() < max_newlines)
newlines_.push_back(*result);
offset = *result + 1;
result = next_newline(offset);
}
}
Optional<Offset> read(Offset offset, char* buffer, Offset length) {
if (offset + length > size())
return {};
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, length);
if (result.is_error())
return {};
return *result;
}
bool write(Offset offset, std::string_view value) {
wrapped_->seek(offset);
auto result = wrapped_->write(value.data(), value.length());
return result.is_ok();
}
/* Returns the index of the line in the newline cache if valid. */
Optional<Offset> index_for_line(Line line) const {
if (line >= line_count_)
return {};
Offset actual = line - start_line_;
if (actual >= newlines_.size()) // NB: underflow wrap.
return {};
return actual;
}
/* Ensure specified line is in the newline cache. */
void ensure_cached(Line line) {
if (line >= line_count_)
return;
auto index = index_for_line(line);
if (index)
return;
if (line < start_line_) {
while (line < start_line_ && start_offset_ >= 2) {
// start_offset_ - 1 should be a newline. Need to
// find the new value for start_offset_. start_line_
// has to be > 0 to get into this block so there should
// always be one newline before start_offset_.
auto offset = previous_newline(start_offset_ - 2);
newlines_.push_front(start_offset_ - 1);
if (!offset) {
// Must be at beginning.
start_line_ = 0;
start_offset_ = 0;
} else {
// Found an previous newline, the new start_line_
// starts at the newline offset + 1.
start_line_--;
start_offset_ = *offset + 1;
}
}
} else {
while (line >= start_line_ + newlines_.size()) {
auto offset = next_newline(newlines_.back() + 1);
if (offset) {
start_line_++;
start_offset_ = newlines_.front() + 1;
newlines_.push_back(*offset);
} /* else at the EOF. */
}
}
}
/* Finding the first newline backward from offset. */
Optional<Offset> previous_newline(Offset offset) {
char buffer[buffer_size];
auto to_read = buffer_size;
do {
if (offset < to_read) {
// NB: Char at 'offset' was read in the previous iteration.
to_read = offset;
offset = 0;
} else
offset -= to_read;
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, to_read);
if (result.is_error())
break;
// Find newlines in the buffer backwards.
for (int32_t i = *result - 1; i >= 0; --i) {
switch (buffer[i]) {
case '\n':
return offset + i;
}
}
if (offset == 0)
break;
} while (true);
return {}; // Didn't find one.
}
/* Finding the first newline forward from offset. */
Optional<Offset> next_newline(Offset offset) {
// EOF, no more newlines to find.
if (offset >= size())
return {};
char buffer[buffer_size];
wrapped_->seek(offset);
while (true) {
auto result = wrapped_->read(buffer, buffer_size);
if (result.is_error())
return {};
// Find newlines in the buffer.
for (Offset i = 0; i < *result; ++i) {
switch (buffer[i]) {
case '\n':
return offset + i;
}
}
offset += *result;
if (*result < buffer_size)
break;
}
// For consistency, treat the end of the file as a "newline".
return size() - 1;
}
/* Grow the file and move file content so that the
* content at src is shifted forward by 'delta'. */
void expand(Offset src, int32_t delta) {
if (delta <= 0) // Not an expand.
return;
char buffer[buffer_size];
auto to_read = buffer_size;
// Number of bytes left to shift.
Offset remaining = size() - src;
Offset offset = size();
while (remaining > 0) {
offset -= std::min(remaining, buffer_size);
to_read = std::min(remaining, buffer_size);
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, to_read);
if (result.is_error())
break;
wrapped_->seek(offset + delta);
result = wrapped_->write(buffer, *result);
if (result.is_error())
break;
remaining -= *result;
}
}
/* Shrink the file and move file content so that the
* content at src is shifted backward by 'delta'. */
void shrink(Offset src, int32_t delta) {
if (delta >= 0) // Not a shrink.
return;
char buffer[buffer_size];
auto offset = src;
while (true) {
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, buffer_size);
if (result.is_error())
break;
wrapped_->seek(offset + delta);
result = wrapped_->write(buffer, *result);
if (result.is_error() || *result < buffer_size)
break;
offset += *result;
}
// Delete the extra bytes at the end of the file.
wrapped_->truncate();
}
BufferType* wrapped_{};
/* Total number of lines in the buffer. */
Offset line_count_{0};
/* The offset and line of the newlines cache. */
Offset start_offset_{0};
Offset start_line_{0};
LineEnding line_ending_{LineEnding::LF};
CircularBuffer<Offset, max_newlines + 1> newlines_{};
};
/* A BufferWrapper over a file. */
class FileWrapper : public BufferWrapper<File, 64> {
public:
template <typename T>
using Result = File::Result<T>;
using Error = File::Error;
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path) {
auto fw = std::unique_ptr<FileWrapper>(new FileWrapper());
auto error = fw->file_.open(path, /*read_only*/ false);
if (error)
return *error;
fw->initialize();
return fw;
}
/* Underlying file. */
File& file() { return file_; }
/* Swaps out the underlying file for the specified file.
* The swapped file is expected have the same contents.
* For copy-on-write scenario with a temp file. */
bool assume_file(const std::filesystem::path& path) {
File file;
auto error = file.open(path, /*read_only*/ false);
if (error)
return false;
file_ = std::move(file);
return true;
}
private:
FileWrapper() {}
void initialize() {
set_buffer(&file_);
}
File file_{};
};
template <uint32_t CacheSize = 64, typename T>
BufferWrapper<T, CacheSize> wrap_buffer(T& buffer) {
return {&buffer};
}
#endif // __FILE_WRAPPER_HPP__
+30 -54
View File
@@ -45,9 +45,9 @@ options_t freqman_entry_bandwidths[4] = {
{"16k", 2}},
{
// WFM
{"200k", 0},
{"180k", 1},
{"40k", 2},
{"180k", 1},
{"200k", 0},
}};
options_t freqman_entry_steps = {
@@ -86,27 +86,14 @@ options_t freqman_entry_steps_short = {
{"500kHz", 500000},
{"1MHz", 1000000}};
std::vector<std::string> get_freqman_files() {
std::vector<std::string> file_list;
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
for (auto file : files) {
std::string file_name = file.stem().string();
// don't propose tmp / hidden files in freqman's list
if (file_name.length() && file_name[0] != '.') {
file_list.emplace_back(file_name);
}
}
return file_list;
};
bool load_freqman_file(std::string& file_stem, freqman_db& db) {
return load_freqman_file_ex(file_stem, db, true, true, true);
return load_freqman_file_ex(file_stem, db, true, true, true, FREQMAN_MAX_PER_FILE);
}
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios) {
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t max_num_freqs = FREQMAN_MAX_PER_FILE) {
// swap with empty vector to ensure memory is immediately released
std::vector<freqman_entry>().swap(db);
File freqman_file;
size_t length, n = 0, file_position = 0;
char* pos;
@@ -114,40 +101,33 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
char* line_end;
std::string description;
rf::Frequency frequency_a, frequency_b;
const int read_buf_size = 128;
char file_data[read_buf_size + 1];
char file_data[FREQMAN_READ_BUF_SIZE + 1];
freqman_entry_type type;
freqman_index_t modulation = 0;
freqman_index_t bandwidth = 0;
freqman_index_t step = 0;
freqman_index_t tone = 0;
// these are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
// db.clear();
// db.shrink_to_fit():
std::vector<freqman_entry>().swap(db);
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
if (result.is_valid())
return false;
while (1) {
// Read a read_buf_size bytes block from file
// Read a FREQMAN_READ_BUF_SIZE block from file
freqman_file.seek(file_position);
memset(file_data, 0, read_buf_size + 1);
auto read_size = freqman_file.read(file_data, read_buf_size);
memset(file_data, 0, FREQMAN_READ_BUF_SIZE + 1);
auto read_size = freqman_file.read(file_data, FREQMAN_READ_BUF_SIZE);
if (read_size.is_error())
return false; // Read error
file_position += read_buf_size;
file_position += FREQMAN_READ_BUF_SIZE;
// Reset line_start to beginning of buffer
line_start = file_data;
// If EOF reached, insert 0x0A after, in case the last line doesn't have a C/R
if (read_size.value() < read_buf_size)
if (read_size.value() < FREQMAN_READ_BUF_SIZE)
*(line_start + read_size.value()) = 0x0A;
// Look for complete lines in buffer
@@ -231,18 +211,18 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
if ((type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios)) {
db.push_back({frequency_a, frequency_b, description, type, modulation, bandwidth, step, tone});
n++;
if (n > FREQMAN_MAX_PER_FILE) return true;
if (n > max_num_freqs) return true;
}
line_start = line_end + 1;
if (line_start - file_data >= read_buf_size) break;
if (line_start - file_data >= FREQMAN_READ_BUF_SIZE) break;
}
if (read_size.value() != read_buf_size)
if (read_size.value() != FREQMAN_READ_BUF_SIZE)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + read_buf_size - line_start);
file_position -= (file_data + FREQMAN_READ_BUF_SIZE - line_start);
}
/* populate implicitly specified modulation / bandwidth */
@@ -301,28 +281,24 @@ bool get_freq_string(freqman_entry& entry, std::string& item_string) {
return true;
}
bool delete_freqman_file(std::string& file_stem) {
File freqman_file;
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
delete_file(freq_file_path);
return false;
}
bool save_freqman_file(std::string& file_stem, freqman_db& db) {
File freqman_file;
std::string freq_file_path = "FREQMAN/" + file_stem + ".TXT";
std::string tmp_freq_file_path = "FREQMAN/" + file_stem + ".TXT.TMP";
if (!db.size()) {
delete_file("FREQMAN/" + file_stem + ".TXT");
return true;
}
delete_file(tmp_freq_file_path);
auto result = freqman_file.open(tmp_freq_file_path);
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
delete_file(freq_file_path);
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < db.size(); n++) {
std::string item_string;
auto& entry = db[n];
get_freq_string(entry, item_string);
freqman_file.write_line(item_string);
std::string line;
get_freq_string(db[n], line);
freqman_file.write_line(line);
}
delete_file(freq_file_path);
rename_file(tmp_freq_file_path, freq_file_path);
return true;
}
return false;
+3 -2
View File
@@ -36,6 +36,7 @@
#define FREQMAN_MAX_PER_FILE 60 // Maximum of entries we can read. This is a hardware limit
// It was tested and lowered to leave a bit of space to the caller
#define FREQMAN_MAX_PER_FILE_STR "60" // STRING OF FREQMAN_MAX_PER_FILE
#define FREQMAN_READ_BUF_SIZE 96 // max freqman line size including desc is 90, +6 for a bit of space
using namespace ui;
using namespace std;
@@ -94,10 +95,10 @@ struct freqman_entry {
using freqman_db = std::vector<freqman_entry>;
std::vector<std::string> get_freqman_files();
bool load_freqman_file(std::string& file_stem, freqman_db& db);
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios);
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t limit);
bool get_freq_string(freqman_entry& entry, std::string& item_string);
bool delete_freqman_file(std::string& file_stem);
bool save_freqman_file(std::string& file_stem, freqman_db& db);
bool create_freqman_file(std::string& file_stem, File& freqman_file);
+4 -4
View File
@@ -71,10 +71,10 @@ Samples get() {
const auto yp_reg = LPC_ADC0->DR[portapack::adc0_touch_yp_input];
const auto yn_reg = LPC_ADC0->DR[portapack::adc0_touch_yn_input];
return {
((xp_reg >> 6) & 0x3ff) * 16,
((xn_reg >> 6) & 0x3ff) * 16,
((yp_reg >> 6) & 0x3ff) * 16,
((yn_reg >> 6) & 0x3ff) * 16,
(xp_reg >> 6) & 0x3ff,
(xn_reg >> 6) & 0x3ff,
(yp_reg >> 6) & 0x3ff,
(yn_reg >> 6) & 0x3ff,
};
}
+20 -17
View File
@@ -233,20 +233,23 @@ static PortaPackModel portapack_model() {
if (!model.is_valid()) {
const auto switches_state = get_switches_state();
if (switches_state[(size_t)ui::KeyEvent::Up]) {
save_config(1);
// model = PortaPackModel::R2_20170522; // Commented these out as they should be set down below anyway
} else if (switches_state[(size_t)ui::KeyEvent::Down]) {
save_config(2);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Left]) {
save_config(3);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Right]) {
save_config(4);
// model = PortaPackModel::R2_20170522;
} else if (switches_state[(size_t)ui::KeyEvent::Select]) {
save_config(0);
// Only save config if no other multi key boot action is triggered (like pmem reset)
if (switches_state.count() == 1) {
if (switches_state[(size_t)ui::KeyEvent::Up]) {
save_config(1);
// model = PortaPackModel::R2_20170522; // Commented these out as they should be set down below anyway
} else if (switches_state[(size_t)ui::KeyEvent::Down]) {
save_config(2);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Left]) {
save_config(3);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Right]) {
save_config(4);
// model = PortaPackModel::R2_20170522;
} else if (switches_state[(size_t)ui::KeyEvent::Select]) {
save_config(0);
}
}
if (load_config() == 1) {
@@ -469,13 +472,13 @@ bool init() {
i2c0.start(i2c_config_fast_clock);
chThdSleepMilliseconds(10);
/* Cache some configuration data from persistent memory. */
persistent_memory::cache::init();
touch::adc::init();
controls_init();
chThdSleepMilliseconds(10);
/* Cache some configuration data from persistent memory. */
persistent_memory::cache::init();
clock_manager.set_reference_ppb(persistent_memory::correction_ppb());
clock_manager.enable_if_clocks();
clock_manager.enable_codec_clocks();
+16 -22
View File
@@ -35,6 +35,7 @@ using namespace portapack;
#include "dsp_fir_taps.hpp"
#include "dsp_iir.hpp"
#include "dsp_iir_config.hpp"
#include "utility.hpp"
namespace {
@@ -118,15 +119,6 @@ void ReceiverModel::set_vga(int32_t v_db) {
update_vga();
}
/*int32_t ReceiverModel::tx_gain() const {
return tx_gain_db_;
}
void ReceiverModel::set_tx_gain(int32_t v_db) {
tx_gain_db_ = v_db;
update_tx_gain();
}*/
uint32_t ReceiverModel::sampling_rate() const {
return sampling_rate_;
}
@@ -146,14 +138,25 @@ void ReceiverModel::set_modulation(const Mode v) {
}
volume_t ReceiverModel::headphone_volume() const {
return headphone_volume_;
return persistent_memory::headphone_volume();
}
void ReceiverModel::set_headphone_volume(volume_t v) {
headphone_volume_ = v;
persistent_memory::set_headphone_volume(v);
update_headphone_volume();
}
int32_t ReceiverModel::normalized_headphone_volume() const {
return (headphone_volume() - audio::headphone::volume_range().max).decibel() + 99;
}
void ReceiverModel::set_normalized_headphone_volume(int32_t v) {
// TODO: Linear map instead to ensure 0 is minimal value or fix volume_range_t::normalize.
v = clip<int32_t>(v, 0, 99);
auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
set_headphone_volume(new_volume);
}
uint8_t ReceiverModel::squelch_level() const {
return squelch_level_;
}
@@ -171,13 +174,11 @@ void ReceiverModel::enable() {
update_rf_amp();
update_lna();
update_vga();
// update_tx_gain();
update_baseband_bandwidth();
update_sampling_rate();
update_modulation();
// TODO: would challenge if this should belong to the
// receiver_model namespace:
// TODO: maybe not the perfect place for this, but it's reasonable.
update_headphone_volume();
led_rx.on();
@@ -229,10 +230,6 @@ void ReceiverModel::update_vga() {
radio::set_vga_gain(vga_gain_db_);
}
/*void ReceiverModel::update_tx_gain() {
radio::set_tx_gain(tx_gain_db_);
}*/
void ReceiverModel::set_am_configuration(const size_t n) {
if (n < am_configs.size()) {
am_config_index = n;
@@ -265,10 +262,7 @@ void ReceiverModel::update_sampling_rate() {
}
void ReceiverModel::update_headphone_volume() {
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
// both?
audio::headphone::set_volume(headphone_volume_);
audio::headphone::set_volume(headphone_volume());
}
void ReceiverModel::update_modulation() {
+4 -6
View File
@@ -61,10 +61,6 @@ class ReceiverModel {
int32_t vga() const;
void set_vga(int32_t v_db);
// TODO: Why does receiver need tx_gain?
// int32_t tx_gain() const;
// void set_tx_gain(int32_t v_db);
uint32_t sampling_rate() const;
void set_sampling_rate(uint32_t v);
@@ -74,6 +70,10 @@ class ReceiverModel {
volume_t headphone_volume() const;
void set_headphone_volume(volume_t v);
/* Volume range 0-99, normalized for audio HW. */
int32_t normalized_headphone_volume() const;
void set_normalized_headphone_volume(int32_t v);
uint8_t squelch_level() const;
void set_squelch_level(uint8_t v);
@@ -104,13 +104,11 @@ class ReceiverModel {
int32_t lna_gain_db_{32};
uint32_t baseband_bandwidth_{max283x::filter::bandwidth_minimum};
int32_t vga_gain_db_{32};
// int32_t tx_gain_db_{47};
Mode mode_{Mode::NarrowbandFMAudio};
uint32_t sampling_rate_{3072000};
size_t am_config_index = 0;
size_t nbfm_config_index = 0;
size_t wfm_config_index = 0;
volume_t headphone_volume_{-43.0_dB};
uint8_t squelch_level_{80};
int32_t tuning_offset();
+2 -2
View File
@@ -23,7 +23,6 @@
#define __RECENT_ENTRIES_H__
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstddef>
#include <cstdint>
@@ -128,7 +127,6 @@ class RecentEntriesTable : public Widget {
RecentEntriesTable(
Entries& recent)
: recent{recent} {
set_focusable(true);
}
void paint(Painter& painter) override {
@@ -138,6 +136,8 @@ class RecentEntriesTable : public Widget {
Rect target_rect{r.location(), {r.width(), s.font.line_height()}};
const size_t visible_item_count = r.height() / s.font.line_height();
set_focusable(!recent.empty());
auto selected = find(recent, selected_key);
if (selected == std::end(recent)) {
selected = std::begin(recent);
+6 -6
View File
@@ -34,16 +34,16 @@ Metrics calculate_metrics(const Frame& frame) {
* fast *enough*?), so maybe leave it alone at least for now.
*/
const auto x_max = frame.x.xp / 16;
const auto x_min = frame.x.xn / 16;
const auto x_max = frame.x.xp;
const auto x_min = frame.x.xn;
const auto x_range = x_max - x_min;
const float x_position = (frame.x.yp / 16 + frame.x.yn / 16) * 0.5f;
const float x_position = (frame.x.yp + frame.x.yn) * 0.5f;
const float x_norm = (x_position - x_min) / x_range;
const auto y_max = frame.y.yn / 16;
const auto y_min = frame.y.yp / 16;
const auto y_max = frame.y.yn;
const auto y_min = frame.y.yp;
const auto y_range = y_max - y_min;
const float y_position = (frame.y.xp / 16 + frame.y.xn / 16) * 0.5f;
const float y_position = (frame.y.xp + frame.y.xn) * 0.5f;
const float y_norm = (y_position - y_min) / y_range;
const auto z_max = frame.pressure.yp;
+1 -1
View File
@@ -38,7 +38,7 @@ using sample_t = uint16_t;
constexpr sample_t sample_max = 1023;
constexpr sample_t touch_threshold = sample_max / 5;
constexpr sample_t touch_threshold = sample_max / 16;
struct Samples {
sample_t xp;
+2 -2
View File
@@ -43,8 +43,8 @@ class AlphanumView : public TextEntryView {
bool on_encoder(const EncoderEvent delta) override;
private:
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ' .<";
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz' .<";
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, .<";
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz, .<";
const char* const keys_digit = "0123456789!\"#'()*+-/:;=>?@[\\]<";
const std::pair<std::string, const char*> key_sets[3] = {
+28 -8
View File
@@ -65,23 +65,32 @@ void FreqManUIList::paint(Painter& painter) {
painter.fill_rectangle(
r_widget_screen,
Color::black());
// only return after clearing the screen so previous entries are not shown anymore
if (freqlist_db.size() == 0)
return;
// coloration if file is too big
auto text_color = &Styles::white;
if (freqlist_db.size() > FREQMAN_MAX_PER_FILE)
text_color = &Styles::yellow;
uint8_t nb_lines = 0;
for (uint8_t it = current_index; it < freqlist_db.size(); it++) {
uint8_t line_height = (int)nb_lines * char_height;
if (line_height < (r.height() - char_height)) {
if (line_height < (r.height() - char_height)) { // line is within the widget
std::string description = freqman_item_string(freqlist_db[it], 30);
// line is within the widget
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
style_default, description);
if (nb_lines == highlighted_index) {
const Rect r_highlighted_freq{0, r.location().y() + (int)nb_lines * char_height, 240, char_height};
painter.draw_rectangle(
painter.fill_rectangle(
r_highlighted_freq,
Color::white());
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
Styles::bg_white, description);
} else {
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
*text_color, description);
}
nb_lines++;
} else {
// rect is filled, we can break
@@ -99,8 +108,19 @@ void FreqManUIList::paint(Painter& painter) {
void FreqManUIList::set_db(freqman_db& db) {
freqlist_db = db;
current_index = 0;
highlighted_index = 0;
if (db.size() == 0) {
current_index = 0;
highlighted_index = 0;
} else {
if ((unsigned)(current_index + highlighted_index) >= db.size()) {
current_index = db.size() - 1 - highlighted_index;
}
if (current_index < 0) {
current_index = 0;
if (highlighted_index > 0)
highlighted_index--;
}
}
}
void FreqManUIList::on_focus() {
+3 -8
View File
@@ -23,9 +23,9 @@
#define __UI_FREQLIST_H__
#include "ui.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "freqman.hpp"
@@ -56,17 +56,12 @@ class FreqManUIList : public Widget {
bool on_touch(const TouchEvent event) override;
bool on_encoder(EncoderEvent delta) override;
void set_highlighted_index(int index); // set highlighted_index and return capped highlighted_index value to set in caller
void set_highlighted_index(int index); // internal set highlighted_index in list handler
uint8_t get_index(); // return highlighed + index
uint8_t set_index(uint8_t index); // try to set current_index + highlighed from index, return capped index
void set_db(freqman_db& db);
private:
static constexpr Style style_default{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
static constexpr int8_t char_height = 16;
bool instant_exec_{false};
freqman_db freqlist_db{};
-1
View File
@@ -26,7 +26,6 @@
#include "ui.hpp"
#include "file.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "portapack.hpp"
-1
View File
@@ -26,7 +26,6 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "qrcodegen.hpp"
#include "portapack.hpp"
+17
View File
@@ -381,4 +381,21 @@ void VGAGainField::on_focus() {
}
}
/* AudioVolumeField *******************************************************/
AudioVolumeField::AudioVolumeField(
Point parent_pos)
: NumberField{
parent_pos,
/* length */ 2,
/* range */ {0, 99},
/* step */ 1,
/* fill char */ ' '} {
set_value(receiver_model.normalized_headphone_volume());
on_change = [](int32_t v) {
receiver_model.set_normalized_headphone_volume(v);
};
}
} /* namespace ui */
+5
View File
@@ -337,6 +337,11 @@ class VGAGainField : public NumberField {
void on_focus() override;
};
class AudioVolumeField : public NumberField {
public:
AudioVolumeField(Point parent_pos);
};
} /* namespace ui */
#endif /*__UI_RECEIVER_H__*/
+105
View File
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_font_fixed_8x16.hpp"
using namespace ui;
const Style Styles::white{
.font = ui::font::fixed_8x16,
.background = ui::Color::black(),
.foreground = ui::Color::white(),
};
const Style Styles::bg_white{
.font = ui::font::fixed_8x16,
.background = ui::Color::white(),
.foreground = ui::Color::black(),
};
const Style Styles::white_small{
.font = font::fixed_5x8,
.background = Color::black(),
.foreground = Color::white(),
};
const Style Styles::yellow{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style Styles::green{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style Styles::red{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style Styles::blue{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
const Style Styles::bg_blue{
.font = font::fixed_8x16,
.background = Color::blue(),
.foreground = Color::white(),
};
const Style Styles::light_grey{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::light_grey(),
};
const Style Styles::grey{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style Styles::dark_grey{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::dark_grey(),
};
const Style Styles::bg_dark_grey{
.font = font::fixed_8x16,
.background = Color::dark_grey(),
.foreground = Color::white(),
};
const Style Styles::orange{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::orange(),
};
+73
View File
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_STYLES_H__
#define __UI_STYLES_H__
#include "ui_painter.hpp"
namespace ui {
class Styles {
public:
/* White foreground. */
static const Style white;
/* White background. */
static const Style bg_white;
/* White foreground, small font. */
static const Style white_small;
/* Yellow foreground. */
static const Style yellow;
/* Green foreground. */
static const Style green;
/* Red foreground. */
static const Style red;
/* Blue foreground. */
static const Style blue;
/* Blue background. */
static const Style bg_blue;
/* Light grey foreground. */
static const Style light_grey;
/* Grey foreground. */
static const Style grey;
/* Dark grey foreground. */
static const Style dark_grey;
/* Dark grey background. */
static const Style bg_dark_grey;
/* Orange foreground. */
static const Style orange;
};
} // namespace ui
#endif /*__UI_STYLES_H__*/
+1
View File
@@ -21,6 +21,7 @@
*/
#include "ui_tabview.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "portapack.hpp"
using namespace portapack;
+10 -46
View File
@@ -26,9 +26,9 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rf_path.hpp"
@@ -73,36 +73,12 @@ class TransmitterView : public View {
void set_transmitting(const bool transmitting);
private:
const Style style_start{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_stop{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_locked{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::dark_grey(),
};
const Style style_power_low{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_power_med{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::orange(),
};
const Style style_power_high{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style& style_start = Styles::green;
const Style style_stop = Styles::red;
const Style style_locked = Styles::dark_grey;
const Style style_power_low = Styles::yellow;
const Style style_power_med = Styles::orange;
const Style style_power_high = Styles::red;
bool lock_{false};
bool transmitting_{false};
@@ -168,21 +144,9 @@ class TransmitterView2 : public View {
void paint(Painter& painter) override;
private:
const Style style_power_low{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_power_med{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::orange(),
};
const Style style_power_high{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style& style_power_low = Styles::yellow;
const Style& style_power_med = Styles::orange;
const Style& style_power_high = Styles::red;
Text text_gain_amp{
{0, 3 * 8, 5 * 8, 1 * 16},
-1
View File
@@ -26,7 +26,6 @@
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "core_control.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
+49 -49
View File
@@ -44,6 +44,7 @@
#include "ui_debug.hpp"
#include "ui_encoders.hpp"
#include "ui_fileman.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_freqman.hpp"
#include "ui_jammer.hpp"
// #include "ui_keyfob.hpp"
@@ -65,6 +66,7 @@
#include "ui_siggen.hpp"
#include "ui_sonde.hpp"
#include "ui_sstvtx.hpp"
#include "ui_styles.hpp"
// #include "ui_test.hpp"
#include "ui_text_editor.hpp"
#include "ui_tone_search.hpp"
@@ -104,12 +106,6 @@ namespace ui {
SystemStatusView::SystemStatusView(
NavigationView& nav)
: nav_(nav) {
static constexpr Style style_systemstatus{
.font = font::fixed_8x16,
.background = Color::dark_grey(),
.foreground = Color::white(),
};
add_children({
&backdrop,
&button_back,
@@ -126,6 +122,10 @@ SystemStatusView::SystemStatusView(
&sd_card_status_view,
});
if (portapack::persistent_memory::should_use_sdcard_for_pmem()) {
portapack::persistent_memory::load_persistent_settings_from_file();
}
if (portapack::persistent_memory::config_speaker())
button_speaker.hidden(false);
else
@@ -143,7 +143,7 @@ SystemStatusView::SystemStatusView(
}
button_back.id = -1; // Special ID used by FocusManager
title.set_style(&style_systemstatus);
title.set_style(&Styles::bg_dark_grey);
if (portapack::persistent_memory::stealth_mode())
button_stealth.set_foreground(ui::Color::green());
@@ -156,6 +156,9 @@ SystemStatusView::SystemStatusView(
refresh();
button_back.on_select = [this](ImageButton&) {
if (portapack::persistent_memory::should_use_sdcard_for_pmem()) {
portapack::persistent_memory::save_persistent_settings_to_file();
}
if (this->on_back)
this->on_back();
};
@@ -236,10 +239,8 @@ void SystemStatusView::refresh() {
if (portapack::clock_manager.get_reference().source == ClockManager::ReferenceSource::External) {
button_clock_status.set_bitmap(&bitmap_icon_clk_ext);
// button_bias_tee.set_foreground(ui::Color::green()); Typo?
} else {
button_clock_status.set_bitmap(&bitmap_icon_clk_int);
// button_bias_tee.set_foreground(ui::Color::green());
}
if (portapack::persistent_memory::clkout_enabled()) {
@@ -420,7 +421,7 @@ View* NavigationView::push_view(std::unique_ptr<View> new_view) {
free_view();
const auto p = new_view.get();
view_stack.emplace_back(std::move(new_view));
view_stack.emplace_back(ViewState{std::move(new_view), {}});
update_view();
@@ -428,34 +429,14 @@ View* NavigationView::push_view(std::unique_ptr<View> new_view) {
}
void NavigationView::pop() {
if (view() == modal_view) {
modal_view = nullptr;
}
// Can't pop last item from stack.
if (view_stack.size() > 1) {
free_view();
view_stack.pop_back();
update_view();
}
pop(true);
}
void NavigationView::pop_modal() {
if (view() == modal_view) {
modal_view = nullptr;
}
// Pop modal view + underlying app view
if (view_stack.size() > 2) {
free_view();
view_stack.pop_back();
free_view();
view_stack.pop_back();
update_view();
}
// Pop modal view + underlying app view.
// TODO: this shouldn't be necessary.
pop(false);
pop(true);
}
void NavigationView::display_modal(
@@ -475,12 +456,31 @@ void NavigationView::display_modal(
}
}
void NavigationView::pop(bool update) {
if (view() == modal_view) {
modal_view = nullptr;
}
// Can't pop last item from stack.
if (view_stack.size() > 1) {
auto on_pop = view_stack.back().on_pop;
free_view();
view_stack.pop_back();
if (update)
update_view();
if (on_pop) on_pop();
}
}
void NavigationView::free_view() {
remove_child(view());
}
void NavigationView::update_view() {
const auto new_view = view_stack.back().get();
const auto new_view = view_stack.back().view.get();
add_child(new_view);
new_view->set_parent_rect({{0, 0}, size()});
@@ -503,6 +503,18 @@ void NavigationView::focus() {
}
}
bool NavigationView::set_on_pop(std::function<void()> on_pop) {
if (view_stack.size() <= 1)
return false;
auto& top = view_stack.back();
if (top.on_pop)
return false;
top.on_pop = on_pop;
return true;
}
/* ReceiversMenuView *****************************************************/
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
@@ -629,17 +641,12 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
/* SystemView ************************************************************/
static constexpr ui::Style style_default{
.font = ui::font::fixed_8x16,
.background = ui::Color::black(),
.foreground = ui::Color::white()};
SystemView::SystemView(
Context& context,
const Rect parent_rect)
: View{parent_rect},
context_(context) {
set_style(&style_default);
set_style(&ui::Styles::white);
constexpr ui::Dim status_view_height = 16;
constexpr ui::Dim info_view_height = 16;
@@ -683,13 +690,6 @@ SystemView::SystemView(
navigation_view.push<SystemMenuView>();
File pmem_flag_file_handle;
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG";
auto result = pmem_flag_file_handle.open(pmem_flag_file);
if (!result.is_valid()) {
portapack::persistent_memory::load_persistent_settings_from_file("SETTINGS/pmem_settings");
}
if (portapack::persistent_memory::config_splash()) {
navigation_view.push<BMPView>();
}
+15 -3
View File
@@ -75,9 +75,11 @@ class NavigationView : public View {
// Pushes a new view under the current on the stack so the current view returns into this new one.
template <class T, class... Args>
void push_under_current(Args&&... args) {
T* push_under_current(Args&&... args) {
auto new_view = std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...));
view_stack.insert(view_stack.end() - 1, std::move(new_view));
auto new_view_ptr = new_view.get();
view_stack.insert(view_stack.end() - 1, ViewState{std::move(new_view), {}});
return reinterpret_cast<T*>(new_view_ptr);
}
template <class T, class... Args>
@@ -97,12 +99,22 @@ class NavigationView : public View {
void focus() override;
/* Sets the 'on_pop' handler for the current view.
* Returns true if the handler was bound successfully. */
bool set_on_pop(std::function<void()> on_pop);
private:
std::vector<std::unique_ptr<View>> view_stack{};
struct ViewState {
std::unique_ptr<View> view;
std::function<void()> on_pop;
};
std::vector<ViewState> view_stack{};
Widget* modal_view{nullptr};
Widget* view() const;
void pop(bool update);
void free_view();
void update_view();
View* push_view(std::unique_ptr<View> new_view);
+1 -1
View File
@@ -35,7 +35,7 @@ class GoertzelDetector {
private:
float coefficient{};
int16_t s[2]{0};
int16_t s[3]{0};
};
} /* namespace dsp */
+20
View File
@@ -175,6 +175,22 @@ uint8_t mode_sense6(msd_cbw_t* msd_cbw_data) {
return 0;
}
uint8_t mode_sense10(msd_cbw_t* msd_cbw_data) {
(void)msd_cbw_data;
scsi_mode_sense10_response_t ret = {
.byte = {
cpu_to_be16(sizeof(scsi_mode_sense6_response_t) - 2),
0,
0,
0}};
memcpy(&usb_bulk_buffer[0], &ret, sizeof(scsi_mode_sense10_response_t));
usb_send_bulk(&usb_bulk_buffer[0], sizeof(scsi_mode_sense10_response_t));
return 0;
}
static data_request_t decode_data_request(const uint8_t* cmd) {
data_request_t req;
uint32_t lba;
@@ -254,6 +270,10 @@ void scsi_command(msd_cbw_t* msd_cbw_data) {
status = mode_sense6(msd_cbw_data);
break;
case SCSI_CMD_MODE_SENSE_10:
status = mode_sense10(msd_cbw_data);
break;
case SCSI_CMD_READ_FORMAT_CAPACITIES:
status = read_format_capacities(msd_cbw_data);
break;
+7 -5
View File
@@ -35,13 +35,11 @@
#include "hackrf_core.h"
#include "usb_bulk_buffer.h"
#define MSD_CBW_SIGNATURE 0x43425355
#define MSD_CSW_SIGNATURE 0x53425355
#define SCSI_CMD_TEST_UNIT_READY 0x00
#define SCSI_CMD_REQUEST_SENSE 0x03
#define SCSI_CMD_INQUIRY 0x12
#define SCSI_CMD_MODE_SENSE_6 0x1A
#define SCSI_CMD_MODE_SENSE_10 0x5A
#define SCSI_CMD_START_STOP_UNIT 0x1B
#define SCSI_CMD_SEND_DIAGNOSTIC 0x1D
#define SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E
@@ -81,8 +79,8 @@
#define SCSI_ASENSEQ_INIT_COMMAND_REQUIRED 0x02
#define SCSI_ASENSEQ_OPERATION_IN_PROGRESS 0x07
#define MSD_CBW_SIGNATURE 0x43425355
#define MSD_CSW_SIGNATURE 0x53425355
#define MSD_CBW_SIGNATURE 0x43425355 // USBC
#define MSD_CSW_SIGNATURE 0x53425355 // USBS
#define USB_TRANSFER_SIZE 0x2000
@@ -136,6 +134,10 @@ typedef struct {
uint8_t byte[4];
} scsi_mode_sense6_response_t;
typedef struct {
uint16_t byte[4];
} scsi_mode_sense10_response_t;
typedef struct {
uint32_t first_lba;
uint16_t blk_cnt;
@@ -1835,7 +1835,7 @@ void gen_numname (
if (c > '9') c += 7;
ns[i--] = c;
seq /= 16;
} while (seq);
} while (seq && i != 0);
ns[i] = '~';
/* Append the number */
+2 -4
View File
@@ -43,10 +43,8 @@ class Optional {
const T& value() const& { return value_; }
const T& operator*() const& { return value_; }
T&& value() && { return value_; }
T&& operator*() && { return value_; }
const T&& value() const&& { return value_; }
const T&& operator*() const&& { return value_; }
T&& value() && { return std::move(value_); }
T&& operator*() && { return std::move(value_); }
T* operator->() { return &value_; }
const T* operator->() const { return &value_; }
@@ -22,6 +22,7 @@
#include "portapack_persistent_memory.hpp"
#include "audio.hpp"
#include "portapack.hpp"
#include "hal.h"
@@ -38,6 +39,8 @@
#include <fstream>
#include "file.hpp"
#include "irq_controls.hpp"
using namespace std;
namespace portapack {
@@ -75,7 +78,7 @@ constexpr clkout_freq_range_t clkout_freq_range{10, 60000};
constexpr uint32_t clkout_freq_reset_value{10000};
enum data_structure_version_enum : uint32_t {
VERSION_CURRENT = 0x10000002,
VERSION_CURRENT = 0x10000003,
};
static const uint32_t TOUCH_CALIBRATION_MAGIC = 0x074af82f;
@@ -305,6 +308,9 @@ struct data_t {
// Rotary encoder dial sensitivity (encoder.cpp/hpp)
uint8_t encoder_dial_sensitivity;
// Headphone volume in centibels.
int32_t headphone_volume_cb;
constexpr data_t()
: structure_version(data_structure_version_enum::VERSION_CURRENT),
tuned_frequency(tuned_frequency_reset_value),
@@ -341,7 +347,8 @@ struct data_t {
updown_frequency_rx_correction(0),
frequency_tx_correction(0),
updown_frequency_tx_correction(0),
encoder_dial_sensitivity(0) {
encoder_dial_sensitivity(0),
headphone_volume_cb(-600) {
}
};
@@ -423,6 +430,9 @@ namespace cache {
void defaults() {
cached_backup_ram = backup_ram_t();
*data = data_t(); // This is a workaround for apparently alignment issue
// that is causing backup_ram_t's block copy to be
// misaligned. This force sets values through the struct.
// defaults values for recon app
set_recon_autosave_freqs(false);
@@ -437,7 +447,8 @@ void defaults() {
}
void init() {
if (backup_ram->is_valid()) {
const auto switches_state = get_switches_state();
if (!(switches_state[(size_t)ui::KeyEvent::Left] && switches_state[(size_t)ui::KeyEvent::Right]) && backup_ram->is_valid()) {
// Copy valid persistent data into cache.
cached_backup_ram = *backup_ram;
@@ -469,6 +480,17 @@ void set_tuned_frequency(const rf::Frequency new_value) {
data->tuned_frequency = rf::tuning_range.clip(new_value);
}
volume_t headphone_volume() {
auto volume = volume_t::centibel(data->headphone_volume_cb);
volume = audio::headphone::volume_range().limit(volume);
return volume;
}
void set_headphone_volume(volume_t new_value) {
new_value = audio::headphone::volume_range().limit(new_value);
data->headphone_volume_cb = new_value.centibel();
}
ppb_t correction_ppb() {
ppb_range.reset_if_outside(data->correction_ppb, ppb_reset_value);
return data->correction_ppb;
@@ -820,8 +842,13 @@ void set_encoder_dial_sensitivity(uint8_t v) {
data->encoder_dial_sensitivity = v;
}
bool should_use_sdcard_for_pmem() {
return std::filesystem::file_exists(PMEM_FILEFLAG);
}
// sd persisting settings
int save_persistent_settings_to_file(std::string filename) {
int save_persistent_settings_to_file() {
std::string filename = PMEM_SETTING_FILE;
delete_file(filename);
File outfile;
auto result = outfile.create(filename);
@@ -832,7 +859,8 @@ int save_persistent_settings_to_file(std::string filename) {
return true;
}
int load_persistent_settings_from_file(std::string filename) {
int load_persistent_settings_from_file() {
std::string filename = PMEM_SETTING_FILE;
File infile;
auto result = infile.open(filename);
if (!result.is_valid()) {
@@ -842,5 +870,9 @@ int load_persistent_settings_from_file(std::string filename) {
return false;
}
size_t data_size() {
return sizeof(data_t);
}
} /* namespace persistent_memory */
} /* namespace portapack */
@@ -31,6 +31,12 @@
#include "touch.hpp"
#include "modems.hpp"
#include "serializer.hpp"
#include "volume.hpp"
// persistant memory from/to sdcard flag file
#define PMEM_FILEFLAG "/SETTINGS/PMEM_FILEFLAG"
// persistant memory from/to sdcard flag file
#define PMEM_SETTING_FILE "/SETTINGS/pmem_settings"
using namespace modems;
using namespace serializer;
@@ -127,6 +133,9 @@ using ppb_t = int32_t;
rf::Frequency tuned_frequency();
void set_tuned_frequency(const rf::Frequency new_value);
volume_t headphone_volume();
void set_headphone_volume(volume_t new_value);
ppb_t correction_ppb();
void set_correction_ppb(const ppb_t new_value);
@@ -244,8 +253,11 @@ void set_recon_load_hamradios(const bool v);
void set_recon_match_mode(const bool v);
// sd persisting settings
int save_persistent_settings_to_file(std::string filename);
int load_persistent_settings_from_file(std::string filename);
bool should_use_sdcard_for_pmem();
int save_persistent_settings_to_file();
int load_persistent_settings_from_file();
size_t data_size();
} /* namespace persistent_memory */
+21 -15
View File
@@ -35,18 +35,27 @@ Style Style::invert() const {
.foreground = background};
}
int Painter::draw_char(const Point p, const Style& style, const char c) {
int Painter::draw_char(Point p, const Style& style, char c) {
const auto glyph = style.font.glyph(c);
display.draw_glyph(p, glyph, style.foreground, style.background);
return glyph.advance().x();
}
int Painter::draw_string(Point p, const Font& font, const Color foreground, const Color background, const std::string& text) {
int Painter::draw_string(Point p, const Style& style, std::string_view text) {
return draw_string(p, style.font, style.foreground, style.background, text);
}
int Painter::draw_string(
Point p,
const Font& font,
Color foreground,
Color background,
std::string_view text) {
bool escape = false;
size_t width = 0;
Color pen = foreground;
for (const auto c : text) {
for (auto c : text) {
if (escape) {
if (c <= 15)
pen = term_colors[c & 15];
@@ -65,48 +74,45 @@ int Painter::draw_string(Point p, const Font& font, const Color foreground, cons
}
}
}
return width;
}
int Painter::draw_string(Point p, const Style& style, const std::string& text) {
return draw_string(p, style.font, style.foreground, style.background, text);
}
void Painter::draw_bitmap(const Point p, const Bitmap& bitmap, const Color foreground, const Color background) {
void Painter::draw_bitmap(Point p, const Bitmap& bitmap, Color foreground, Color background) {
display.draw_bitmap(p, bitmap.size, bitmap.data, foreground, background);
}
void Painter::draw_hline(Point p, int width, const Color c) {
void Painter::draw_hline(Point p, int width, Color c) {
display.fill_rectangle({p, {width, 1}}, c);
}
void Painter::draw_vline(Point p, int height, const Color c) {
void Painter::draw_vline(Point p, int height, Color c) {
display.fill_rectangle({p, {1, height}}, c);
}
void Painter::draw_rectangle(const Rect r, const Color c) {
void Painter::draw_rectangle(Rect r, Color c) {
draw_hline(r.location(), r.width(), c);
draw_vline({r.left(), r.top() + 1}, r.height() - 2, c);
draw_vline({r.left() + r.width() - 1, r.top() + 1}, r.height() - 2, c);
draw_hline({r.left(), r.top() + r.height() - 1}, r.width(), c);
}
void Painter::fill_rectangle(const Rect r, const Color c) {
void Painter::fill_rectangle(Rect r, Color c) {
display.fill_rectangle(r, c);
}
void Painter::fill_rectangle_unrolled8(const Rect r, const Color c) {
void Painter::fill_rectangle_unrolled8(Rect r, Color c) {
display.fill_rectangle_unrolled8(r, c);
}
void Painter::paint_widget_tree(Widget* const w) {
void Painter::paint_widget_tree(Widget* w) {
if (ui::is_dirty()) {
paint_widget(w);
ui::dirty_clear();
}
}
void Painter::paint_widget(Widget* const w) {
void Painter::paint_widget(Widget* w) {
if (w->hidden()) {
// Mark widget (and all children) as invisible.
w->visible(false);
+12 -12
View File
@@ -25,7 +25,7 @@
#include "ui.hpp"
#include "ui_text.hpp"
#include <string>
#include <string_view>
namespace ui {
@@ -46,24 +46,24 @@ class Painter {
Painter(const Painter&) = delete;
Painter(Painter&&) = delete;
int draw_char(const Point p, const Style& style, const char c);
int draw_char(Point p, const Style& style, char c);
int draw_string(Point p, const Font& font, const Color foreground, const Color background, const std::string& text);
int draw_string(Point p, const Style& style, const std::string& text);
int draw_string(Point p, const Style& style, std::string_view text);
int draw_string(Point p, const Font& font, Color foreground, Color background, std::string_view text);
void draw_bitmap(const Point p, const Bitmap& bitmap, const Color background, const Color foreground);
void draw_bitmap(Point p, const Bitmap& bitmap, Color background, Color foreground);
void draw_rectangle(const Rect r, const Color c);
void fill_rectangle(const Rect r, const Color c);
void fill_rectangle_unrolled8(const Rect r, const Color c);
void draw_rectangle(Rect r, Color c);
void fill_rectangle(Rect r, Color c);
void fill_rectangle_unrolled8(Rect r, Color c);
void paint_widget_tree(Widget* const w);
void paint_widget_tree(Widget* w);
void draw_hline(Point p, int width, const Color c);
void draw_vline(Point p, int height, const Color c);
void draw_hline(Point p, int width, Color c);
void draw_vline(Point p, int height, Color c);
private:
void paint_widget(Widget* const w);
void paint_widget(Widget* w);
};
} /* namespace ui */
+7 -1
View File
@@ -1477,7 +1477,13 @@ void OptionsField::on_focus() {
}
bool OptionsField::on_encoder(const EncoderEvent delta) {
set_selected_index(selected_index() + delta);
int32_t new_value = selected_index() + delta;
if (new_value < 0)
new_value = options.size() - 1;
else if ((size_t)new_value >= options.size())
new_value = 0;
set_selected_index(new_value);
return true;
}
-1
View File
@@ -27,7 +27,6 @@
#include "ui_text.hpp"
#include "ui_painter.hpp"
#include "ui_focus.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rtc_time.hpp"
#include "radio.hpp"

Some files were not shown because too many files have changed in this diff Show More