mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-21 23:19:02 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2279e297c | |||
| cdd524b9f3 | |||
| 99809c7919 | |||
| 2390d79111 | |||
| 3b41d73ddd | |||
| 3d46c28911 | |||
| ca7840557b | |||
| ceaa115025 | |||
| 8dbcefbc75 | |||
| b28b96fb4a | |||
| e15a8ed2d8 | |||
| 19b77bf03b | |||
| 7b1541d2e8 | |||
| 830fea63ed | |||
| 3d59de55f8 | |||
| 5c1f5a7d2e | |||
| 4e985420d4 | |||
| 3e584a9652 | |||
| 39acaf846a | |||
| 76948cffa8 | |||
| 4ea8abb53b | |||
| 32085f317b | |||
| faa4367295 | |||
| 3f8a4957af | |||
| b5ea81cf96 | |||
| e14fa5eab0 | |||
| 960ecf616c | |||
| 996ccb0e40 | |||
| 407fee23b9 | |||
| 199570d4a5 | |||
| 5cd9c377d2 | |||
| e08273b8b8 | |||
| 6b0f90c321 | |||
| 0dea488e98 | |||
| e60c4dc34b |
@@ -1 +1 @@
|
||||
v1.7.1
|
||||
v1.7.2
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.7.2
|
||||
v1.7.3
|
||||
|
||||
@@ -287,7 +287,7 @@ set(CPPSRC
|
||||
apps/ert_app.cpp
|
||||
apps/lge_app.cpp
|
||||
apps/pocsag_app.cpp
|
||||
apps/replay_app.cpp
|
||||
# apps/replay_app.cpp
|
||||
apps/ui_playlist.cpp
|
||||
apps/gps_sim_app.cpp
|
||||
apps/soundboard_app.cpp
|
||||
|
||||
@@ -404,22 +404,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
}
|
||||
|
||||
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
|
||||
tone_index idx = tone_key_index_by_value(value);
|
||||
if (idx >= 0)
|
||||
text_ctcss.set("CTCSS " + tone_key_string(idx));
|
||||
else
|
||||
text_ctcss.set("???");
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ class AnalogAudioView : public View {
|
||||
4096,
|
||||
4};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{true};
|
||||
spectrum::WaterfallView waterfall{true};
|
||||
|
||||
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
|
||||
void on_modulation_changed(ReceiverModel::Mode modulation);
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace ui {
|
||||
|
||||
CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
|
||||
add_children({
|
||||
@@ -59,41 +60,8 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
waterfall.on_hide();
|
||||
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options . */
|
||||
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
|
||||
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
|
||||
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
break;
|
||||
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
|
||||
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
|
||||
break;
|
||||
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
|
||||
anti_alias_baseband_bandwidth_filter = 3500000;
|
||||
break;
|
||||
|
||||
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 5000000;
|
||||
break;
|
||||
|
||||
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 6000000;
|
||||
break;
|
||||
|
||||
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 7000000;
|
||||
break;
|
||||
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
|
||||
// We tested also 9Mhz FPB stightly too much noise floor, better 8Mhz
|
||||
anti_alias_baseband_bandwidth_filter = 8000000;
|
||||
}
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options. */
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
|
||||
record_view.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
|
||||
@@ -31,23 +31,7 @@
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
#define BW_OPTIONS \
|
||||
{" 8k5", 8500}, \
|
||||
{" 11k", 11000}, \
|
||||
{" 16k", 16000}, \
|
||||
{" 25k", 25000}, \
|
||||
{" 50k", 50000}, \
|
||||
{" 100k", 100000}, \
|
||||
{" 250k", 250000}, \
|
||||
{" 500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/ \
|
||||
{" 600k", 600000}, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */ \
|
||||
{" 750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is decimated, (not real file size) */ \
|
||||
{"1100k", 1100000}, \
|
||||
{"1750k", 1750000}, \
|
||||
{"2000k", 2000000}, \
|
||||
{"2500k", 2500000}, \
|
||||
{"2750k", 2750000}, // That is our max Capture option, to keep using later / 8 decimation (22Mhz sampling ADC)
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -72,7 +56,6 @@ class CaptureAppView : public View {
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
uint32_t sampling_rate = 0;
|
||||
uint32_t anti_alias_baseband_bandwidth_filter = 2500000;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
|
||||
@@ -103,7 +86,7 @@ class CaptureAppView : public View {
|
||||
OptionsField option_bandwidth{
|
||||
{5 * 8, 1 * 16},
|
||||
5,
|
||||
{BW_OPTIONS}};
|
||||
{}};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
@@ -113,7 +96,7 @@ class CaptureAppView : public View {
|
||||
16384,
|
||||
3};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -124,7 +124,7 @@ class GpsSimAppView : public View {
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -121,7 +121,7 @@ class ReplayAppView : public View {
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -32,7 +32,7 @@ class SpectrumAnalysisModel {
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallView {
|
||||
public:
|
||||
private:
|
||||
SpectrumAnalysisModel model;
|
||||
|
||||
@@ -248,6 +248,7 @@ void ControlsSwitchesWidget::on_show() {
|
||||
|
||||
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||
key_event_mask = 1 << toUType(key);
|
||||
long_press_key_event_mask = switch_long_press_occurred((size_t)key) ? key_event_mask : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -325,6 +326,14 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
|
||||
switches_event = long_press_key_event_mask;
|
||||
for (const auto r : events_rects) {
|
||||
if (switches_event & 1)
|
||||
painter.fill_rectangle(r + pos, Color::cyan());
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::on_frame_sync() {
|
||||
@@ -335,12 +344,21 @@ void ControlsSwitchesWidget::on_frame_sync() {
|
||||
|
||||
DebugControlsView::DebugControlsView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&labels,
|
||||
&switches_widget,
|
||||
&options_switches_mode,
|
||||
&button_done,
|
||||
});
|
||||
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
button_done.on_select = [&nav](Button&) {
|
||||
switches_long_press_enable(0);
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
(void)v;
|
||||
switches_long_press_enable(options_switches_mode.selected_index_value());
|
||||
};
|
||||
}
|
||||
|
||||
void DebugControlsView::focus() {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "rffc507x.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "memory_map.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
@@ -220,7 +221,8 @@ class ControlsSwitchesWidget : public Widget {
|
||||
ControlsSwitchesWidget(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect},
|
||||
key_event_mask(0) {
|
||||
key_event_mask(0),
|
||||
long_press_key_event_mask{0} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
@@ -231,6 +233,7 @@ class ControlsSwitchesWidget : public Widget {
|
||||
|
||||
private:
|
||||
uint8_t key_event_mask;
|
||||
uint8_t long_press_key_event_mask;
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
@@ -250,15 +253,22 @@ class DebugControlsView : public View {
|
||||
std::string title() const override { return "Buttons Test"; };
|
||||
|
||||
private:
|
||||
Text text_title{
|
||||
{64, 16, 184, 16},
|
||||
"Controls State",
|
||||
};
|
||||
Labels labels{
|
||||
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
|
||||
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
|
||||
|
||||
ControlsSwitchesWidget switches_widget{
|
||||
{80, 80, 80, 112},
|
||||
};
|
||||
|
||||
OptionsField options_switches_mode{
|
||||
{17 * 8, 14 * 16},
|
||||
8,
|
||||
{
|
||||
{"Disabled", 0},
|
||||
{"Enabled", 0xFF}, // all KeyEvent bits to long-press mode
|
||||
}};
|
||||
|
||||
Button button_done{
|
||||
{72, 264, 96, 24},
|
||||
"Done"};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ui_dfu_menu.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -80,4 +81,57 @@ void DfuMenu::paint(Painter& painter) {
|
||||
ui::Color::dark_cyan());
|
||||
}
|
||||
|
||||
DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&text_head,
|
||||
&labels,
|
||||
&text_info_line_1,
|
||||
&text_info_line_2,
|
||||
&text_info_line_3,
|
||||
&text_info_line_4,
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7,
|
||||
&text_info_line_8});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
|
||||
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
|
||||
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
|
||||
text_info_line_4.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
||||
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
||||
text_info_line_8.set("");
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::black());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||
ui::Color::dark_cyan());
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -67,6 +67,38 @@ class DfuMenu : public View {
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
public:
|
||||
DfuMenu2(NavigationView& nav);
|
||||
~DfuMenu2() = default;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 8 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Receiver"};
|
||||
|
||||
Labels labels{
|
||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "Tgt freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "Bandwidt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "Sampl Rt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{14 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{14 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{14 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_DFU_MENU_H__*/
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "string_format.hpp"
|
||||
@@ -35,6 +36,13 @@
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
} // namespace ui
|
||||
|
||||
namespace {
|
||||
using namespace ui;
|
||||
|
||||
@@ -47,25 +55,6 @@ std::string truncate(const fs::path& path, size_t max_length) {
|
||||
return ::truncate(path.string(), max_length);
|
||||
}
|
||||
|
||||
// Case insensitive path equality on underlying "native" string.
|
||||
bool iequal(
|
||||
const fs::path& lhs,
|
||||
const fs::path& rhs) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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(
|
||||
@@ -86,15 +75,12 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
fs::path get_partner_file(fs::path path) {
|
||||
if (fs::is_directory(path))
|
||||
return {};
|
||||
|
||||
const fs::path txt_path{u".TXT"};
|
||||
const fs::path c16_path{u".C16"};
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(ext, txt_path))
|
||||
ext = c16_path;
|
||||
else if (iequal(ext, c16_path))
|
||||
ext = txt_path;
|
||||
if (path_iequal(ext, txt_ext))
|
||||
ext = c16_ext;
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
ext = txt_ext;
|
||||
else
|
||||
return {};
|
||||
|
||||
@@ -163,7 +149,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
continue;
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || iequal(entry.path().extension(), extension_filter))
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
|
||||
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path(), 0, true});
|
||||
@@ -301,7 +287,7 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
size_t index = 0;
|
||||
|
||||
for (; index < file_types.size() - 1; ++index)
|
||||
if (iequal(ext, file_types[index].extension))
|
||||
if (path_iequal(ext, file_types[index].extension))
|
||||
return file_types[index];
|
||||
|
||||
// Default to last entry in the list.
|
||||
@@ -507,10 +493,14 @@ bool FileManagerView::handle_file_open() {
|
||||
auto path = get_selected_full_path();
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
return true;
|
||||
} else if (iequal(u".PNG", ext)) {
|
||||
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
nav_.push<PlaylistView>(path);
|
||||
return true;
|
||||
} else if (path_iequal(png_ext, ext)) {
|
||||
nav_.push<ScreenshotViewer>(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const char16_t* firmware_folder = u"/FIRMWARE";
|
||||
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
@@ -35,7 +37,9 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(u"FIRMWARE", u"*.bin")) {
|
||||
ensure_directory(firmware_folder);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class FlashUtilityView : public View {
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "FlashUtility"; }; // Removed the space because the title and speaker icon overlapped.
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -199,6 +199,16 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
|
||||
text_ctcss.set(" ");
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -206,34 +216,23 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
|
||||
void LevelView::handle_coded_squelch(const uint32_t value) {
|
||||
static int32_t last_idx = -1;
|
||||
static tone_index last_squelch_index = -1;
|
||||
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
if (field_mode.selected_index() == NFM_MODULATION) {
|
||||
tone_index idx = tone_key_index_by_value(value);
|
||||
|
||||
if (field_mode.selected_index() != NFM_MODULATION) {
|
||||
text_ctcss.set(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
if ((last_squelch_index < 0) || (last_squelch_index != idx)) {
|
||||
last_squelch_index = idx;
|
||||
if (idx >= 0) {
|
||||
text_ctcss.set("T: " + tone_key_string(idx));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (last_idx < 0 || (unsigned)last_idx != min_idx) {
|
||||
last_idx = min_idx;
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("T: " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -91,7 +91,7 @@ class LevelView : public View {
|
||||
|
||||
OptionsField field_mode{
|
||||
{15 * 8, 1 * 16},
|
||||
3,
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField step_mode{
|
||||
|
||||
@@ -63,7 +63,7 @@ class GlassView : public View {
|
||||
GlassView& operator=(const GlassView& nav);
|
||||
|
||||
~GlassView();
|
||||
std::string title() const override { return "LookingGlass"; };
|
||||
std::string title() const override { return "Looking Glass"; };
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
*/
|
||||
|
||||
#include "ui_playlist.hpp"
|
||||
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
@@ -39,13 +40,17 @@
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/* TODO: wouldn't it be easier if the playlist were just a list of C16 files
|
||||
* (and maybe delays) and then read the metadata file next to the C16 file?
|
||||
* TODO: use metadata_file.hpp to read the metadata.
|
||||
* TODO: change PPL format to only allow paths, and !<number> for delay. */
|
||||
/* TODO
|
||||
* - Should frequency overrides be saved in the playlist?
|
||||
* - Start playing from current track (vs start over)?
|
||||
*/
|
||||
|
||||
namespace ui {
|
||||
|
||||
// TODO: consolidate extesions into a shared header?
|
||||
static const fs::path c16_ext = u".C16";
|
||||
static const fs::path ppl_ext = u".PPL";
|
||||
|
||||
void PlaylistView::load_file(const fs::path& playlist_path) {
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(playlist_path.string());
|
||||
@@ -59,57 +64,154 @@ void PlaylistView::load_file(const fs::path& playlist_path) {
|
||||
continue; // Empty or comment line.
|
||||
|
||||
auto cols = split_string(line, ',');
|
||||
if (cols.size() < 3)
|
||||
continue; // Line doesn't have enough columns.
|
||||
auto entry = load_entry(trim(cols[0]));
|
||||
|
||||
playlist_entry entry{};
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
parse_int(cols[0], entry.replay_frequency);
|
||||
parse_int(cols[2], entry.sample_rate);
|
||||
if (entry.replay_frequency == 0 || entry.sample_rate == 0)
|
||||
continue; // Invalid freq or rate.
|
||||
// Read optional delay value.
|
||||
if (cols.size() > 1)
|
||||
parse_int(cols[1], entry->ms_delay);
|
||||
|
||||
entry.replay_file = fs::path{u"/"} + std::string{cols[1]};
|
||||
|
||||
if (cols.size() == 4) // Optional delay value.
|
||||
parse_int(cols[3], entry.initial_delay);
|
||||
|
||||
playlist_db_.emplace_back(std::move(entry));
|
||||
playlist_db_.emplace_back(*std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
Optional<PlaylistView::playlist_entry> PlaylistView::load_entry(fs::path&& path) {
|
||||
File capture_file;
|
||||
|
||||
auto error = capture_file.open(path);
|
||||
if (error)
|
||||
return {};
|
||||
|
||||
// Read metafile if it exists.
|
||||
auto metadata_path = get_metadata_path(path);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
// If no metadata found, fallback to the TX frequency.
|
||||
if (!metadata)
|
||||
metadata = {transmitter_model.target_frequency(), 500'000};
|
||||
|
||||
return playlist_entry{
|
||||
std::move(path),
|
||||
*metadata,
|
||||
capture_file.size(),
|
||||
0u};
|
||||
}
|
||||
|
||||
void PlaylistView::on_file_changed(const fs::path& new_file_path) {
|
||||
stop();
|
||||
reset();
|
||||
|
||||
current_index_ = 0;
|
||||
playlist_path_ = new_file_path;
|
||||
playlist_db_.clear();
|
||||
load_file(playlist_path_);
|
||||
|
||||
update_ui();
|
||||
button_play.focus();
|
||||
|
||||
// TODO: fix in UI framework with 'try_focus()'?
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::reset() {
|
||||
current_track_ = 0;
|
||||
current_entry_ = nullptr;
|
||||
current_entry_size_ = 0;
|
||||
void PlaylistView::open_file(bool prompt_save) {
|
||||
if (playlist_dirty_ && prompt_save) {
|
||||
nav_.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
save_file(false);
|
||||
});
|
||||
nav_.set_on_pop([this]() { open_file(false); });
|
||||
return;
|
||||
}
|
||||
|
||||
auto open_view = nav_.push<FileLoadView>(".PPL");
|
||||
open_view->push_dir(u"PLAYLIST");
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistView::save_file(bool show_dialogs) {
|
||||
if (!playlist_dirty_ || playlist_path_.empty())
|
||||
return;
|
||||
|
||||
ensure_directory(playlist_path_.parent_path());
|
||||
|
||||
File playlist_file;
|
||||
auto error = playlist_file.create(playlist_path_.string());
|
||||
|
||||
if (error) {
|
||||
if (show_dialogs)
|
||||
nav_.display_modal(
|
||||
"Save Error",
|
||||
"Could not save file\n" + playlist_path_.string());
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : playlist_db_) {
|
||||
playlist_file.write_line(
|
||||
entry.path.string() + "," +
|
||||
to_string_dec_uint(entry.ms_delay));
|
||||
}
|
||||
|
||||
playlist_dirty_ = false;
|
||||
|
||||
if (show_dialogs)
|
||||
nav_.display_modal(
|
||||
"Save",
|
||||
"Saved playlist\n" + playlist_path_.string());
|
||||
}
|
||||
|
||||
void PlaylistView::add_entry(fs::path&& path) {
|
||||
if (playlist_path_.empty()) {
|
||||
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
|
||||
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
auto entry = load_entry(std::move(path));
|
||||
if (entry) {
|
||||
playlist_db_.emplace_back(*std::move(entry));
|
||||
current_index_ = playlist_db_.size() - 1;
|
||||
playlist_dirty_ = true;
|
||||
}
|
||||
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::delete_entry() {
|
||||
if (playlist_db_.empty())
|
||||
return;
|
||||
|
||||
// Ugh, the STL is gross.
|
||||
playlist_db_.erase(playlist_db_.begin() + current_index_);
|
||||
|
||||
if (current_index_ > 0)
|
||||
--current_index_;
|
||||
|
||||
playlist_dirty_ = true;
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::show_file_error(const fs::path& path, const std::string& message) {
|
||||
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
|
||||
}
|
||||
|
||||
PlaylistView::playlist_entry* PlaylistView::current() {
|
||||
return playlist_db_.empty() ? nullptr : &playlist_db_[current_index_];
|
||||
}
|
||||
|
||||
bool PlaylistView::is_active() const {
|
||||
return replay_thread_ != nullptr;
|
||||
}
|
||||
|
||||
bool PlaylistView::loop() const {
|
||||
return check_loop.value();
|
||||
}
|
||||
|
||||
bool PlaylistView::is_done() const {
|
||||
return current_track_ >= playlist_db_.size();
|
||||
bool PlaylistView::at_end() const {
|
||||
return current_index_ >= playlist_db_.size();
|
||||
}
|
||||
|
||||
void PlaylistView::toggle() {
|
||||
@@ -120,25 +222,24 @@ void PlaylistView::toggle() {
|
||||
}
|
||||
|
||||
void PlaylistView::start() {
|
||||
reset();
|
||||
|
||||
if (playlist_db_.empty())
|
||||
return; // Nothing to do.
|
||||
|
||||
if (next_track())
|
||||
send_current_track();
|
||||
current_index_ = 0u;
|
||||
send_current_track();
|
||||
}
|
||||
|
||||
/* Advance to the next track in the playlist. */
|
||||
bool PlaylistView::next_track() {
|
||||
if (is_done()) {
|
||||
if (loop())
|
||||
current_track_ = 0;
|
||||
else
|
||||
return false; // All done.
|
||||
++current_index_;
|
||||
|
||||
// Reset to the 0 once at the end to prevent current_index_
|
||||
// from being outside the bounds of playlist_db_ when painting.
|
||||
if (at_end()) {
|
||||
current_index_ = 0;
|
||||
return check_loop.value(); // Keep going if looping.
|
||||
}
|
||||
current_entry_ = &playlist_db_[current_track_];
|
||||
current_track_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -149,30 +250,25 @@ void PlaylistView::send_current_track() {
|
||||
transmitter_model.disable();
|
||||
ready_signal_ = false;
|
||||
|
||||
if (!current_entry_)
|
||||
if (!current())
|
||||
return;
|
||||
|
||||
// TODO: Use a timer so the UI isn't frozen.
|
||||
if (current()->ms_delay > 0)
|
||||
chThdSleepMilliseconds(current()->ms_delay);
|
||||
|
||||
// Open the sample file to send.
|
||||
auto reader = std::make_unique<FileReader>();
|
||||
auto error = reader->open(current_entry_->replay_file);
|
||||
auto error = reader->open(current()->path);
|
||||
if (error) {
|
||||
show_file_error(current_entry_->replay_file, "Can't open file to send.");
|
||||
show_file_error(current()->path, "Can't open file to send.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the sample file size for the progress bar.
|
||||
current_entry_size_ = reader->file().size();
|
||||
progressbar_transmit.set_value(0);
|
||||
|
||||
// Wait a bit before sending if specified.
|
||||
// TODO: this pauses the main thread, move into ReplayThread instead.
|
||||
if (current_entry_->initial_delay > 0)
|
||||
chThdSleepMilliseconds(current_entry_->initial_delay);
|
||||
|
||||
// ReplayThread starts immediately on contruction so
|
||||
// ReplayThread starts immediately on construction so
|
||||
// these need to be set before creating the ReplayThread.
|
||||
transmitter_model.set_target_frequency(current_entry_->replay_frequency);
|
||||
transmitter_model.set_sampling_rate(current_entry_->sample_rate * 8);
|
||||
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
|
||||
transmitter_model.set_sampling_rate(current()->metadata.sample_rate * 8);
|
||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||
transmitter_model.enable();
|
||||
|
||||
@@ -180,6 +276,9 @@ void PlaylistView::send_current_track() {
|
||||
// TODO: Why doesn't the transmitter_model just handle this?
|
||||
baseband::set_sample_rate(transmitter_model.sampling_rate());
|
||||
|
||||
// Reset the transmit progress bar.
|
||||
progressbar_transmit.set_value(0);
|
||||
|
||||
// Use the ReplayThread class to send the data.
|
||||
replay_thread_ = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
@@ -199,37 +298,48 @@ void PlaylistView::stop() {
|
||||
// This terminates the underlying chThread.
|
||||
replay_thread_.reset();
|
||||
transmitter_model.disable();
|
||||
|
||||
// Reset the transmit progress bar.
|
||||
progressbar_transmit.set_value(0);
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::update_ui() {
|
||||
if (playlist_db_.empty()) {
|
||||
text_track.set("Open a playlist file.");
|
||||
} else {
|
||||
text_track.set(
|
||||
to_string_dec_uint(current_track_) + "/" +
|
||||
to_string_dec_uint(playlist_db_.size()) + " " +
|
||||
playlist_path_.filename().string());
|
||||
}
|
||||
|
||||
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
|
||||
|
||||
progressbar_track.set_value(current_track_);
|
||||
progressbar_track.set_max(playlist_db_.size());
|
||||
progressbar_transmit.set_max(current_entry_size_);
|
||||
|
||||
if (current_entry_) {
|
||||
auto duration = ms_duration(current_entry_size_, current_entry_->sample_rate, 4);
|
||||
text_filename.set(truncate(current_entry_->replay_file.filename().string(), 12));
|
||||
text_sample_rate.set(unit_auto_scale(current_entry_->sample_rate, 3, 0) + "Hz");
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
text_frequency.set(to_string_short_freq(current_entry_->replay_frequency));
|
||||
} else {
|
||||
text_filename.set("-");
|
||||
text_sample_rate.set("-");
|
||||
text_duration.set("-");
|
||||
text_frequency.set("-");
|
||||
|
||||
if (playlist_path_.empty())
|
||||
text_track.set("Open playlist or add capture.");
|
||||
else
|
||||
text_track.set(playlist_path_.filename().string());
|
||||
|
||||
progressbar_track.set_value(0);
|
||||
progressbar_track.set_max(0);
|
||||
progressbar_transmit.set_max(0);
|
||||
|
||||
} else {
|
||||
chDbgAssert(!at_end(), "update_ui #1", "current_index_ invalid");
|
||||
|
||||
text_filename.set(current()->path.filename().string());
|
||||
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, 0) + "Hz");
|
||||
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
field_frequency.set_value(current()->metadata.center_frequency);
|
||||
|
||||
text_track.set(
|
||||
to_string_dec_uint(current_index_ + 1) + "/" +
|
||||
to_string_dec_uint(playlist_db_.size()) + " " +
|
||||
playlist_path_.filename().string());
|
||||
|
||||
progressbar_track.set_max(playlist_db_.size() - 1);
|
||||
progressbar_track.set_value(current_index_);
|
||||
progressbar_transmit.set_max(current()->file_size);
|
||||
}
|
||||
|
||||
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
|
||||
}
|
||||
|
||||
void PlaylistView::on_tx_progress(uint32_t progress) {
|
||||
@@ -245,7 +355,7 @@ void PlaylistView::handle_replay_thread_done(uint32_t return_code) {
|
||||
}
|
||||
|
||||
if (return_code == ReplayThread::READ_ERROR)
|
||||
show_file_error(current_entry_->replay_file, "Replay read failed.");
|
||||
show_file_error(current()->path, "Replay read failed.");
|
||||
|
||||
stop();
|
||||
}
|
||||
@@ -256,36 +366,103 @@ PlaylistView::PlaylistView(
|
||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||
|
||||
add_children({
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&text_sample_rate,
|
||||
&text_duration,
|
||||
&progressbar_track,
|
||||
&progressbar_transmit,
|
||||
&text_frequency,
|
||||
&field_frequency,
|
||||
&tx_view,
|
||||
&check_loop,
|
||||
&button_play,
|
||||
&text_track,
|
||||
&button_prev,
|
||||
&button_add,
|
||||
&button_delete,
|
||||
&button_open,
|
||||
&button_save,
|
||||
&button_next,
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
field_frequency.set_value(100'000'000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
if (current())
|
||||
current()->metadata.center_frequency = f;
|
||||
};
|
||||
field_frequency.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
toggle();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".PPL");
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
button_add.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
auto open_view = nav_.push<FileLoadView>(".C16");
|
||||
open_view->push_dir(u"CAPTURES");
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
add_entry(std::move(path));
|
||||
};
|
||||
};
|
||||
|
||||
button_delete.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
delete_entry();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
stop();
|
||||
open_file();
|
||||
};
|
||||
|
||||
button_save.on_select = [this]() {
|
||||
if (is_active())
|
||||
stop();
|
||||
save_file();
|
||||
};
|
||||
|
||||
button_prev.on_select = [this]() {
|
||||
if (is_active())
|
||||
return;
|
||||
--current_index_;
|
||||
if (at_end())
|
||||
current_index_ = playlist_db_.size() - 1;
|
||||
update_ui();
|
||||
};
|
||||
|
||||
button_next.on_select = [this]() {
|
||||
if (is_active())
|
||||
return;
|
||||
++current_index_;
|
||||
if (at_end())
|
||||
current_index_ = 0;
|
||||
update_ui();
|
||||
};
|
||||
|
||||
update_ui();
|
||||
}
|
||||
|
||||
PlaylistView::PlaylistView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path)
|
||||
: PlaylistView(nav) {
|
||||
auto ext = path.extension();
|
||||
if (path_iequal(ext, ppl_ext))
|
||||
on_file_changed(path);
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
add_entry(fs::path{path});
|
||||
}
|
||||
|
||||
PlaylistView::~PlaylistView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
@@ -294,20 +471,23 @@ PlaylistView::~PlaylistView() {
|
||||
void PlaylistView::set_parent_rect(Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
const ui::Rect waterfall_rect{
|
||||
ui::Rect waterfall_rect{
|
||||
0, header_height, new_parent_rect.width(),
|
||||
new_parent_rect.height() - header_height};
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
void PlaylistView::focus() {
|
||||
if (playlist_path_.empty())
|
||||
button_add.focus();
|
||||
else
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::on_hide() {
|
||||
stop();
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void PlaylistView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -25,13 +25,16 @@
|
||||
#define NORMAL_UI false
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "file.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -42,17 +45,15 @@ namespace ui {
|
||||
class PlaylistView : public View {
|
||||
public:
|
||||
PlaylistView(NavigationView& nav);
|
||||
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
|
||||
~PlaylistView();
|
||||
|
||||
// Disable copy to make -Weffc++ happy.
|
||||
PlaylistView(const PlaylistView&) = delete;
|
||||
PlaylistView& operator=(const PlaylistView&) = delete;
|
||||
|
||||
// Following 2 called by 'NavigationView::update_view' after view is created.
|
||||
void set_parent_rect(Rect new_parent_rect) override;
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
void on_hide() override;
|
||||
|
||||
std::string title() const override { return "Playlist"; };
|
||||
std::string title() const override { return "Replay"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
@@ -61,35 +62,40 @@ class PlaylistView : public View {
|
||||
"tx_playlist", app_settings::Mode::TX};
|
||||
|
||||
// More header == less spectrum view.
|
||||
static constexpr ui::Dim header_height = 4 * 16;
|
||||
static constexpr ui::Dim header_height = 6 * 16;
|
||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
||||
|
||||
struct playlist_entry {
|
||||
rf::Frequency replay_frequency{0};
|
||||
std::filesystem::path replay_file{};
|
||||
uint32_t sample_rate{};
|
||||
uint32_t initial_delay{};
|
||||
std::filesystem::path path{};
|
||||
capture_metadata metadata{};
|
||||
File::Size file_size{};
|
||||
uint32_t ms_delay{};
|
||||
};
|
||||
|
||||
std::unique_ptr<ReplayThread> replay_thread_{};
|
||||
bool ready_signal_{}; // Used to signal the ReplayThread.
|
||||
|
||||
size_t current_track_{0};
|
||||
const playlist_entry* current_entry_{};
|
||||
size_t current_entry_size_{0};
|
||||
size_t current_index_{0};
|
||||
bool playlist_dirty_{};
|
||||
std::vector<playlist_entry> playlist_db_{};
|
||||
std::filesystem::path playlist_path_{};
|
||||
|
||||
void load_file(const std::filesystem::path& path);
|
||||
Optional<playlist_entry> load_entry(std::filesystem::path&& path);
|
||||
void on_file_changed(const std::filesystem::path& path);
|
||||
void open_file(bool prompt_save = true);
|
||||
void save_file(bool show_dialogs = true);
|
||||
void add_entry(std::filesystem::path&& path);
|
||||
void delete_entry();
|
||||
void reset();
|
||||
void show_file_error(
|
||||
const std::filesystem::path& path,
|
||||
const std::string& message);
|
||||
|
||||
playlist_entry* current();
|
||||
|
||||
bool is_active() const;
|
||||
bool loop() const;
|
||||
bool is_done() const;
|
||||
bool at_end() const;
|
||||
|
||||
void toggle();
|
||||
void start();
|
||||
@@ -103,31 +109,33 @@ class PlaylistView : public View {
|
||||
void on_tx_progress(uint32_t progress);
|
||||
void handle_replay_thread_done(uint32_t return_code);
|
||||
|
||||
Button button_open{
|
||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
||||
"Open PPL"};
|
||||
|
||||
Text text_filename{
|
||||
{11 * 8, 0 * 16, 12 * 8, 16}};
|
||||
{0 * 8, 0 * 16, 30 * 8, 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{0 * 8, 1 * 16}};
|
||||
|
||||
Text text_sample_rate{
|
||||
{24 * 8, 0 * 16, 6 * 8, 16}};
|
||||
{10 * 8, 1 * 16, 7 * 8, 16}};
|
||||
|
||||
Text text_duration{
|
||||
{11 * 8, 1 * 16, 6 * 8, 16}};
|
||||
/*v making there's 1px line (instead of two) between two progress bars,
|
||||
* by letting 1px overlapped.
|
||||
* So, since they overlapped 1px, they are visually same, and looks better.
|
||||
*/
|
||||
|
||||
ProgressBar progressbar_track{
|
||||
{18 * 8, 1 * 16, 12 * 8, 8}};
|
||||
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
|
||||
|
||||
ProgressBar progressbar_transmit{
|
||||
{18 * 8, 3 * 8, 12 * 8, 8}};
|
||||
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
|
||||
|
||||
Text text_frequency{
|
||||
{0 * 8, 2 * 16, 9 * 8, 16}};
|
||||
Text text_duration{
|
||||
{0 * 8, 2 * 16, 5 * 8, 16}};
|
||||
|
||||
// TODO: delay duration field.
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
74, 1 * 8, SHORT_UI // x(columns), y (line) position.
|
||||
};
|
||||
9 * 8, 1 * 8, SHORT_UI};
|
||||
|
||||
Checkbox check_loop{
|
||||
{21 * 8, 2 * 16},
|
||||
@@ -144,7 +152,43 @@ class PlaylistView : public View {
|
||||
Text text_track{
|
||||
{0 * 8, 3 * 16, 30 * 8, 16}};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
NewButton button_prev{
|
||||
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_left,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_next{
|
||||
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_add{
|
||||
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_new_file,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_delete{
|
||||
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_delete,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_open{
|
||||
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_load,
|
||||
Color::dark_blue()};
|
||||
|
||||
NewButton button_save{
|
||||
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_save,
|
||||
Color::dark_blue()};
|
||||
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -36,8 +36,19 @@ void ReconView::set_loop_config(bool v) {
|
||||
persistent_memory::set_recon_continuous(continuous);
|
||||
}
|
||||
|
||||
void ReconView::recon_stop_recording() {
|
||||
if (is_recording) {
|
||||
button_audio_app.set_style(&Styles::white);
|
||||
record_view->stop();
|
||||
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
|
||||
is_recording = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ReconView::clear_freqlist_for_ui_action() {
|
||||
audio::output::stop();
|
||||
recon_stop_recording();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
// flag to detect and reload frequency_list
|
||||
if (!manual_mode) {
|
||||
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
|
||||
@@ -232,8 +243,8 @@ bool ReconView::recon_save_config_to_sd() {
|
||||
|
||||
make_new_directory(u"SETTINGS");
|
||||
|
||||
auto result = settings_file.create(RECON_CFG_FILE);
|
||||
if (result.is_valid())
|
||||
auto error = settings_file.create(RECON_CFG_FILE);
|
||||
if (error)
|
||||
return false;
|
||||
settings_file.write_line(input_file);
|
||||
settings_file.write_line(output_file);
|
||||
@@ -246,7 +257,8 @@ bool ReconView::recon_save_config_to_sd() {
|
||||
}
|
||||
|
||||
void ReconView::audio_output_start() {
|
||||
audio::output::start();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::start();
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||
}
|
||||
|
||||
@@ -279,10 +291,6 @@ void ReconView::recon_redraw() {
|
||||
} else if (freq_lock >= recon_lock_nb_match) {
|
||||
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) {
|
||||
recon_save_freq(freq_file_path, current_index, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last_db != db || last_list_size != frequency_list.size()) {
|
||||
@@ -354,16 +362,21 @@ void ReconView::focus() {
|
||||
}
|
||||
|
||||
ReconView::~ReconView() {
|
||||
// Save recon config.
|
||||
recon_stop_recording();
|
||||
recon_save_config_to_sd();
|
||||
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
ReconView::ReconView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
chrono_start = chTimeNow();
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
add_children({&labels,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -399,7 +412,14 @@ ReconView::ReconView(NavigationView& nav)
|
||||
&button_dir,
|
||||
&button_restart,
|
||||
&button_mic_app,
|
||||
&button_remove});
|
||||
&button_remove,
|
||||
record_view.get()});
|
||||
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
def_step = 0;
|
||||
// HELPER: Pre-setting a manual range, based on stored frequency
|
||||
@@ -424,6 +444,7 @@ 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();
|
||||
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
||||
|
||||
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
|
||||
clear_freqlist_for_ui_action();
|
||||
@@ -680,7 +701,8 @@ ReconView::ReconView(NavigationView& nav)
|
||||
} else if (frequency_range.min > frequency_range.max) {
|
||||
nav_.display_modal("Error", "END freq\nis lower than START");
|
||||
} else {
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
|
||||
// swap is the only way to achieve a perfect memory liberation
|
||||
std::vector<freqman_entry>().swap(frequency_list);
|
||||
@@ -760,6 +782,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||
file_name.set_style(&Styles::yellow);
|
||||
}
|
||||
};
|
||||
|
||||
button_add.on_select = [this](ButtonWithEncoder&) { // frequency_list[current_index]
|
||||
@@ -795,8 +820,11 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_config.on_select = [this, &nav](Button&) {
|
||||
if (is_recording) // disabling config while recording
|
||||
return;
|
||||
clear_freqlist_for_ui_action();
|
||||
|
||||
freq_lock = 0;
|
||||
timer = 0;
|
||||
auto open_view = nav.push<ReconSetupView>(input_file, output_file);
|
||||
open_view->on_changed = [this](std::vector<std::string> result) {
|
||||
input_file = result[0];
|
||||
@@ -811,6 +839,7 @@ 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();
|
||||
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
||||
|
||||
frequency_file_load(false);
|
||||
freqlist_cleared_for_ui_action = false;
|
||||
@@ -830,6 +859,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
|
||||
field_wait.on_change = [this](int32_t v) {
|
||||
wait = v;
|
||||
// replacing -100 by 200 else it's freezing the device
|
||||
if (wait == -100)
|
||||
wait = -200;
|
||||
colorize_waits();
|
||||
};
|
||||
|
||||
@@ -888,7 +920,8 @@ ReconView::ReconView(NavigationView& nav)
|
||||
|
||||
void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
(void)(stop_all_before);
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
def_step = step_mode.selected_index(); // use def_step from manual selector
|
||||
std::string file_input = input_file; // default recon mode
|
||||
@@ -896,29 +929,27 @@ void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
file_input = output_file;
|
||||
file_name.set_style(&Styles::red);
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
desc_cycle.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCANNER");
|
||||
} else {
|
||||
file_name.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
desc_cycle.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
desc_cycle.set_style(&Styles::white);
|
||||
if (!load_freqman_file(file_input, frequency_list, load_freqs, load_ranges, load_hamradios)) {
|
||||
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 + "=>" + output_file);
|
||||
if (frequency_list.size() == 0) {
|
||||
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(&Styles::yellow);
|
||||
desc_cycle.set_style(&Styles::yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,6 +1023,14 @@ void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
}
|
||||
|
||||
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
systime_t time_interval = 100;
|
||||
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||
chrono_end = chTimeNow();
|
||||
time_interval = chrono_end - chrono_start;
|
||||
chrono_start = chrono_end;
|
||||
}
|
||||
|
||||
// hack to reload the list if it was cleared by going into CONFIG
|
||||
if (freqlist_cleared_for_ui_action) {
|
||||
if (!manual_mode) {
|
||||
@@ -1018,7 +1057,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (status != 1) {
|
||||
status = 1;
|
||||
if (wait != 0) {
|
||||
audio::output::stop();
|
||||
recon_stop_recording();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
}
|
||||
}
|
||||
if (db > squelch) // MATCHING LEVEL
|
||||
@@ -1040,8 +1081,24 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (status != 2) {
|
||||
continuous_lock = false;
|
||||
status = 2;
|
||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
|
||||
recon_save_freq(freq_file_path, current_index, false);
|
||||
}
|
||||
if (wait != 0) {
|
||||
audio_output_start();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio_output_start();
|
||||
// contents of a possible recon_start_recording(), but not yet since it's only called once
|
||||
if (auto_record_locked && !is_recording) {
|
||||
button_audio_app.set_style(&Styles::red);
|
||||
record_view->start();
|
||||
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
|
||||
is_recording = true;
|
||||
}
|
||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
|
||||
recon_save_freq(freq_file_path, current_index, false);
|
||||
}
|
||||
}
|
||||
if (wait >= 0) {
|
||||
timer = wait;
|
||||
@@ -1060,8 +1117,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
text_timer.set("TIMER: " + to_string_dec_int(timer));
|
||||
}
|
||||
if (timer) {
|
||||
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE)
|
||||
timer -= STATS_UPDATE_INTERVAL;
|
||||
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE) {
|
||||
timer -= time_interval;
|
||||
}
|
||||
if (timer < 0) {
|
||||
timer = 0;
|
||||
}
|
||||
@@ -1072,7 +1130,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (frequency_list.size() > 0) {
|
||||
has_looped = false;
|
||||
entry_has_changed = false;
|
||||
|
||||
if (recon || stepper != 0 || index_stepper != 0) {
|
||||
if (index_stepper == 0) {
|
||||
/* we are doing a range */
|
||||
@@ -1172,7 +1229,8 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
|
||||
entry_has_changed = true;
|
||||
|
||||
if (!recon) // for some motive, audio output gets stopped.
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio_output_start();
|
||||
}
|
||||
// reload entry if changed
|
||||
@@ -1224,7 +1282,8 @@ void ReconView::recon_pause() {
|
||||
continuous_lock = false;
|
||||
recon = false;
|
||||
|
||||
audio_output_start();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio_output_start();
|
||||
|
||||
big_display.set_style(&Styles::white);
|
||||
button_pause.set_text("<RESUME>"); // PAUSED, show resume
|
||||
@@ -1236,7 +1295,8 @@ void ReconView::recon_resume() {
|
||||
continuous_lock = false;
|
||||
recon = true;
|
||||
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
big_display.set_style(&Styles::white);
|
||||
button_pause.set_text("<PAUSE>");
|
||||
@@ -1277,10 +1337,28 @@ void ReconView::on_stepper_delta(int32_t v) {
|
||||
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) {};
|
||||
|
||||
recon_stop_recording();
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
audio::output::stop();
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_RAW_", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
}
|
||||
add_child(record_view.get());
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [this](std::string message) {
|
||||
nav_.display_modal("Error", message);
|
||||
};
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
size_t recording_sampling_rate = 0;
|
||||
switch (new_mod) {
|
||||
case AM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
@@ -1291,6 +1369,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
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); };
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 12000;
|
||||
break;
|
||||
case NFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
@@ -1300,6 +1379,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
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); };
|
||||
recording_sampling_rate = 24000;
|
||||
break;
|
||||
case WFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
@@ -1310,18 +1390,35 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
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); };
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 48000;
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
// bw 200k (0) default
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
record_view->set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
};
|
||||
text_ctcss.set(" ");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (new_mod != SPEC_MODULATION)
|
||||
record_view->set_sampling_rate(recording_sampling_rate);
|
||||
field_mode.set_selected_index(new_mod);
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v != -1) {
|
||||
change_mode(v);
|
||||
}
|
||||
};
|
||||
|
||||
if (!recon) // for some motive, audio output gets stopped.
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::start(); // so if recon was stopped we resume audio
|
||||
receiver_model.enable();
|
||||
|
||||
@@ -1329,32 +1426,20 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
|
||||
void ReconView::handle_coded_squelch(const uint32_t value) {
|
||||
float diff{0.0};
|
||||
float min_diff{(float)value};
|
||||
size_t min_idx{0};
|
||||
size_t c{0};
|
||||
if (field_mode.selected_index() == NFM_MODULATION) {
|
||||
tone_index idx = tone_key_index_by_value(value);
|
||||
|
||||
if (field_mode.selected_index() != NFM_MODULATION) {
|
||||
text_ctcss.set(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
if ((last_squelch_index < 0) || (last_squelch_index != idx)) {
|
||||
last_squelch_index = idx;
|
||||
if (idx >= 0) {
|
||||
text_ctcss.set("T: " + tone_key_string(idx));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (last_squelch_index < 0 || (unsigned)last_squelch_index != min_idx) {
|
||||
last_squelch_index = min_idx;
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("T: " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -84,6 +84,8 @@ class ReconView : public View {
|
||||
bool recon_load_config_from_sd();
|
||||
bool recon_save_config_to_sd();
|
||||
bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
|
||||
// placeholder for possible void recon_start_recording();
|
||||
void recon_stop_recording();
|
||||
|
||||
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
|
||||
int32_t squelch{0};
|
||||
@@ -108,6 +110,8 @@ class ReconView : public View {
|
||||
bool fwd{true};
|
||||
bool recon{true};
|
||||
bool user_pause{false};
|
||||
bool auto_record_locked{false};
|
||||
bool is_recording{false};
|
||||
uint32_t recon_lock_nb_match{3};
|
||||
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
|
||||
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
|
||||
@@ -118,10 +122,7 @@ class ReconView : public View {
|
||||
int32_t index_stepper{0};
|
||||
int64_t freq{0};
|
||||
uint32_t step{0};
|
||||
freqman_index_t def_modulation{0};
|
||||
freqman_index_t def_bandwidth{0};
|
||||
freqman_index_t def_step{0};
|
||||
tone_index tone{0};
|
||||
freqman_entry last_entry{};
|
||||
bool entry_has_changed{false};
|
||||
uint32_t freq_lock{0};
|
||||
@@ -138,16 +139,20 @@ class ReconView : public View {
|
||||
int8_t last_rssi_med{-127};
|
||||
int8_t last_rssi_max{-127};
|
||||
int32_t last_index{-1};
|
||||
int32_t last_squelch_index{-1};
|
||||
tone_index last_squelch_index{-1};
|
||||
int64_t last_freq{0};
|
||||
std::string freq_file_path{};
|
||||
systime_t chrono_start{};
|
||||
systime_t chrono_end{};
|
||||
|
||||
std::unique_ptr<RecordView> record_view{};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
|
||||
{{3 * 8, 8 * 16}, "START END", Color::light_grey()},
|
||||
{{0 * 8, (22 * 8)}, " S: ", Color::light_grey()},
|
||||
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Color::light_grey()},
|
||||
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
|
||||
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{4 * 8, 0 * 16}};
|
||||
@@ -272,11 +277,11 @@ class ReconView : public View {
|
||||
|
||||
OptionsField field_mode{
|
||||
{6 * 8, (26 * 8) + 4},
|
||||
3,
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField field_bw{
|
||||
{10 * 8, (26 * 8) + 4},
|
||||
{11 * 8, (26 * 8) + 4},
|
||||
6,
|
||||
{}};
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ void ReconSetupViewMore::save() {
|
||||
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
||||
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
|
||||
};
|
||||
|
||||
void ReconSetupViewMain::focus() {
|
||||
@@ -117,12 +118,14 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
add_children({&checkbox_load_freqs,
|
||||
&checkbox_load_ranges,
|
||||
&checkbox_load_hamradios,
|
||||
&checkbox_update_ranges_when_recon});
|
||||
&checkbox_update_ranges_when_recon,
|
||||
&checkbox_auto_record_locked});
|
||||
|
||||
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
|
||||
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
|
||||
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
|
||||
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
|
||||
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
|
||||
};
|
||||
|
||||
void ReconSetupViewMore::focus() {
|
||||
|
||||
@@ -132,6 +132,10 @@ class ReconSetupViewMore : public View {
|
||||
{1 * 8, 102},
|
||||
3,
|
||||
"auto update m-ranges"};
|
||||
Checkbox checkbox_auto_record_locked{
|
||||
{1 * 8, 132},
|
||||
3,
|
||||
"auto record locked periods"};
|
||||
};
|
||||
|
||||
class ReconSetupView : public View {
|
||||
|
||||
@@ -447,6 +447,16 @@ ScannerView::ScannerView(
|
||||
|
||||
// Mode field was changed (AM/NFM/WFM)
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
static freqman_index_t last_mode = AM_MODULATION;
|
||||
// unsupported SPEC mode fix
|
||||
if (v == SPEC_MODULATION) {
|
||||
if (last_mode == AM_MODULATION)
|
||||
v = WFM_MODULATION;
|
||||
else
|
||||
v = AM_MODULATION;
|
||||
field_mode.set_selected_index(v);
|
||||
}
|
||||
last_mode = v;
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
change_mode((freqman_index_t)v);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "ui_settings.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_touch_calibration.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -32,16 +33,24 @@ using namespace lpc43xx;
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
using portapack::receiver_model;
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "freqman.hpp"
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* Sends a UI refresh message to cause the status bar to redraw. */
|
||||
static void send_system_refresh() {
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
|
||||
/* SetDateTimeView ***************************************/
|
||||
|
||||
SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav) {
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
@@ -105,13 +114,15 @@ SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
.second = static_cast<uint8_t>(field_second.value())};
|
||||
}
|
||||
|
||||
/* SetRadioView ******************************************/
|
||||
|
||||
SetRadioView::SetRadioView(
|
||||
NavigationView& nav) {
|
||||
button_cancel.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
const auto reference = portapack::clock_manager.get_reference();
|
||||
const auto reference = clock_manager.get_reference();
|
||||
|
||||
std::string source_name("---");
|
||||
switch (reference.source) {
|
||||
@@ -156,19 +167,18 @@ SetRadioView::SetRadioView(
|
||||
&button_cancel});
|
||||
|
||||
SetFrequencyCorrectionModel model{
|
||||
static_cast<int8_t>(portapack::persistent_memory::correction_ppb() / 1000), 0};
|
||||
static_cast<int8_t>(pmem::correction_ppb() / 1000), 0};
|
||||
|
||||
form_init(model);
|
||||
|
||||
check_clkout.set_value(portapack::persistent_memory::clkout_enabled());
|
||||
check_clkout.set_value(pmem::clkout_enabled());
|
||||
check_clkout.on_select = [this](Checkbox&, bool v) {
|
||||
clock_manager.enable_clock_output(v);
|
||||
portapack::persistent_memory::set_clkout_enabled(v);
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
pmem::set_clkout_enabled(v);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
|
||||
field_clkout_freq.set_value(pmem::clkout_freq());
|
||||
value_freq_step.set_style(&Styles::light_grey);
|
||||
|
||||
field_clkout_freq.on_select = [this](NumberField&) {
|
||||
@@ -193,9 +203,9 @@ SetRadioView::SetRadioView(
|
||||
field_clkout_freq.set_step(pow(10, freq_step_khz));
|
||||
};
|
||||
|
||||
check_bias.set_value(portapack::get_antenna_bias());
|
||||
check_bias.set_value(get_antenna_bias());
|
||||
check_bias.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::set_antenna_bias(v);
|
||||
set_antenna_bias(v);
|
||||
|
||||
// Update the radio.
|
||||
receiver_model.set_antenna_bias();
|
||||
@@ -205,15 +215,14 @@ SetRadioView::SetRadioView(
|
||||
if (!v)
|
||||
radio::set_antenna_bias(false);
|
||||
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&) {
|
||||
const auto model = this->form_collect();
|
||||
portapack::persistent_memory::set_correction_ppb(model.ppm * 1000);
|
||||
portapack::persistent_memory::set_clkout_freq(model.freq);
|
||||
clock_manager.enable_clock_output(portapack::persistent_memory::clkout_enabled());
|
||||
pmem::set_correction_ppb(model.ppm * 1000);
|
||||
pmem::set_clkout_freq(model.freq);
|
||||
clock_manager.enable_clock_output(pmem::clkout_enabled());
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
@@ -233,6 +242,8 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
|
||||
};
|
||||
}
|
||||
|
||||
/* SetUIView *********************************************/
|
||||
|
||||
SetUIView::SetUIView(NavigationView& nav) {
|
||||
add_children({&checkbox_disable_touchscreen,
|
||||
&checkbox_bloff,
|
||||
@@ -241,39 +252,76 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&checkbox_showclock,
|
||||
&options_clockformat,
|
||||
&checkbox_guireturnflag,
|
||||
&labels,
|
||||
&toggle_camera,
|
||||
&toggle_sleep,
|
||||
&toggle_stealth,
|
||||
&toggle_converter,
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_disable_touchscreen.set_value(persistent_memory::disable_touchscreen());
|
||||
checkbox_showsplash.set_value(persistent_memory::config_splash());
|
||||
checkbox_showclock.set_value(!persistent_memory::hide_clock());
|
||||
checkbox_guireturnflag.set_value(persistent_memory::show_gui_return_icon());
|
||||
// Display "Disable speaker" option only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported()) {
|
||||
add_child(&toggle_speaker);
|
||||
}
|
||||
|
||||
const auto backlight_config = persistent_memory::config_backlight_timer();
|
||||
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
|
||||
checkbox_showsplash.set_value(pmem::config_splash());
|
||||
checkbox_showclock.set_value(!pmem::hide_clock());
|
||||
checkbox_guireturnflag.set_value(pmem::show_gui_return_icon());
|
||||
|
||||
const auto backlight_config = pmem::config_backlight_timer();
|
||||
checkbox_bloff.set_value(backlight_config.timeout_enabled());
|
||||
options_bloff.set_by_value(backlight_config.timeout_enum());
|
||||
|
||||
if (persistent_memory::clock_with_date()) {
|
||||
if (pmem::clock_with_date()) {
|
||||
options_clockformat.set_selected_index(1);
|
||||
} else {
|
||||
options_clockformat.set_selected_index(0);
|
||||
}
|
||||
|
||||
// NB: Invert so "active" == "not hidden"
|
||||
toggle_camera.set_value(!pmem::ui_hide_camera());
|
||||
toggle_sleep.set_value(!pmem::ui_hide_sleep());
|
||||
toggle_stealth.set_value(!pmem::ui_hide_stealth());
|
||||
toggle_converter.set_value(!pmem::ui_hide_converter());
|
||||
toggle_bias_tee.set_value(!pmem::ui_hide_bias_tee());
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_config_backlight_timer({(persistent_memory::backlight_timeout_t)options_bloff.selected_index_value(),
|
||||
checkbox_bloff.value()});
|
||||
pmem::set_config_backlight_timer({(pmem::backlight_timeout_t)options_bloff.selected_index_value(),
|
||||
checkbox_bloff.value()});
|
||||
|
||||
if (checkbox_showclock.value()) {
|
||||
if (options_clockformat.selected_index() == 1)
|
||||
persistent_memory::set_clock_with_date(true);
|
||||
pmem::set_clock_with_date(true);
|
||||
else
|
||||
persistent_memory::set_clock_with_date(false);
|
||||
pmem::set_clock_with_date(false);
|
||||
}
|
||||
|
||||
persistent_memory::set_config_splash(checkbox_showsplash.value());
|
||||
persistent_memory::set_clock_hidden(!checkbox_showclock.value());
|
||||
persistent_memory::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
persistent_memory::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
pmem::set_config_splash(checkbox_showsplash.value());
|
||||
pmem::set_clock_hidden(!checkbox_showclock.value());
|
||||
pmem::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
pmem::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
|
||||
pmem::set_ui_hide_camera(!toggle_camera.value());
|
||||
pmem::set_ui_hide_sleep(!toggle_sleep.value());
|
||||
pmem::set_ui_hide_stealth(!toggle_stealth.value());
|
||||
pmem::set_ui_hide_converter(!toggle_converter.value());
|
||||
pmem::set_ui_hide_bias_tee(!toggle_bias_tee.value());
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
send_system_refresh();
|
||||
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
@@ -285,21 +333,20 @@ void SetUIView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Appl. Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetAppSettingsView ************************************/
|
||||
|
||||
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
|
||||
add_children({&checkbox_load_app_settings,
|
||||
&checkbox_save_app_settings,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
|
||||
checkbox_load_app_settings.set_value(pmem::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(pmem::save_app_settings());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
pmem::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
pmem::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
@@ -311,9 +358,8 @@ void SetAppSettingsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Converter Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetConverterSettingsView ******************************/
|
||||
|
||||
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
|
||||
add_children({&check_show_converter,
|
||||
&check_converter,
|
||||
@@ -321,46 +367,43 @@ SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
|
||||
&button_converter_freq,
|
||||
&button_return});
|
||||
|
||||
check_show_converter.set_value(!portapack::persistent_memory::config_hide_converter());
|
||||
check_show_converter.set_value(!pmem::ui_hide_converter());
|
||||
check_show_converter.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::persistent_memory::set_config_hide_converter(!v);
|
||||
pmem::set_ui_hide_converter(!v);
|
||||
if (!v) {
|
||||
check_converter.set_value(false);
|
||||
}
|
||||
// Retune to take converter change in account.
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
// Refresh status bar with/out converter
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
check_converter.set_value(portapack::persistent_memory::config_converter());
|
||||
check_converter.set_value(pmem::config_converter());
|
||||
check_converter.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) {
|
||||
check_show_converter.set_value(true);
|
||||
portapack::persistent_memory::set_config_hide_converter(false);
|
||||
pmem::set_ui_hide_converter(false);
|
||||
}
|
||||
portapack::persistent_memory::set_config_converter(v);
|
||||
pmem::set_config_converter(v);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
// Refresh status bar with/out converter
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
converter_mode.set_by_value(portapack::persistent_memory::config_updown_converter());
|
||||
converter_mode.set_by_value(pmem::config_updown_converter());
|
||||
converter_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_config_updown_converter(v);
|
||||
pmem::set_config_updown_converter(v);
|
||||
// Refresh status bar with icon up or down
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
button_converter_freq.set_text(to_string_short_freq(portapack::persistent_memory::config_converter_freq()) + "MHz");
|
||||
button_converter_freq.set_text(to_string_short_freq(pmem::config_converter_freq()) + "MHz");
|
||||
button_converter_freq.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
portapack::persistent_memory::set_config_converter_freq(f);
|
||||
pmem::set_config_converter_freq(f);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
button_converter_freq.set_text("<" + to_string_short_freq(f) + " MHz>");
|
||||
@@ -376,9 +419,8 @@ void SetConverterSettingsView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Frequency Correction Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetFrequencyCorrectionView ****************************/
|
||||
|
||||
SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
|
||||
add_children({&text_freqCorrection_about,
|
||||
&frequency_rx_correction_mode,
|
||||
@@ -387,36 +429,36 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
|
||||
&button_freq_tx_correction,
|
||||
&button_return});
|
||||
|
||||
frequency_rx_correction_mode.set_by_value(portapack::persistent_memory::config_freq_rx_correction_updown());
|
||||
frequency_rx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
|
||||
frequency_rx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_freq_rx_correction_updown(v);
|
||||
pmem::set_freq_rx_correction_updown(v);
|
||||
};
|
||||
|
||||
frequency_tx_correction_mode.set_by_value(portapack::persistent_memory::config_freq_rx_correction_updown());
|
||||
frequency_tx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
|
||||
frequency_tx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_freq_tx_correction_updown(v);
|
||||
pmem::set_freq_tx_correction_updown(v);
|
||||
};
|
||||
|
||||
button_freq_rx_correction.set_text(to_string_short_freq(portapack::persistent_memory::config_freq_rx_correction()) + "MHz (Rx)");
|
||||
button_freq_rx_correction.set_text(to_string_short_freq(pmem::config_freq_rx_correction()) + "MHz (Rx)");
|
||||
button_freq_rx_correction.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
if (f >= MAX_FREQ_CORRECTION)
|
||||
f = MAX_FREQ_CORRECTION;
|
||||
portapack::persistent_memory::set_config_freq_rx_correction(f);
|
||||
pmem::set_config_freq_rx_correction(f);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
button_freq_rx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
|
||||
};
|
||||
};
|
||||
|
||||
button_freq_tx_correction.set_text(to_string_short_freq(portapack::persistent_memory::config_freq_tx_correction()) + "MHz (Tx)");
|
||||
button_freq_tx_correction.set_text(to_string_short_freq(pmem::config_freq_tx_correction()) + "MHz (Tx)");
|
||||
button_freq_tx_correction.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
if (f >= MAX_FREQ_CORRECTION)
|
||||
f = MAX_FREQ_CORRECTION;
|
||||
portapack::persistent_memory::set_config_freq_tx_correction(f);
|
||||
pmem::set_config_freq_tx_correction(f);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
button_freq_tx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
|
||||
@@ -432,9 +474,8 @@ void SetFrequencyCorrectionView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Persistent Memory Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetPersistentMemoryView *******************************/
|
||||
|
||||
SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
add_children({&text_pmem_about,
|
||||
&text_pmem_informations,
|
||||
@@ -445,7 +486,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
&button_load_mem_defaults,
|
||||
&button_return});
|
||||
|
||||
check_use_sdcard_for_pmem.set_value(portapack::persistent_memory::should_use_sdcard_for_pmem());
|
||||
check_use_sdcard_for_pmem.set_value(pmem::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 = PMEM_FILEFLAG;
|
||||
@@ -472,7 +513,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
};
|
||||
|
||||
button_save_mem_to_file.on_select = [&nav, this](Button&) {
|
||||
if (!portapack::persistent_memory::save_persistent_settings_to_file()) {
|
||||
if (!pmem::save_persistent_settings_to_file()) {
|
||||
text_pmem_status.set("!problem saving settings!");
|
||||
} else {
|
||||
text_pmem_status.set("settings saved");
|
||||
@@ -480,13 +521,12 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
};
|
||||
|
||||
button_load_mem_from_file.on_select = [&nav, this](Button&) {
|
||||
if (!portapack::persistent_memory::load_persistent_settings_from_file()) {
|
||||
if (!pmem::load_persistent_settings_from_file()) {
|
||||
text_pmem_status.set("!problem loading settings!");
|
||||
} else {
|
||||
text_pmem_status.set("settings loaded");
|
||||
// Refresh status bar with icon up or down
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -497,7 +537,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
pmem::cache::defaults();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -511,22 +551,18 @@ void SetPersistentMemoryView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Audio Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetAudioView ******************************************/
|
||||
|
||||
SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_tone_mix,
|
||||
&checkbox_speaker_disable,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_tone_mix.set_value(persistent_memory::tone_mix());
|
||||
checkbox_speaker_disable.set_value(persistent_memory::config_speaker_disable());
|
||||
field_tone_mix.set_value(pmem::tone_mix());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_tone_mix(field_tone_mix.value());
|
||||
persistent_memory::set_config_speaker_disable(checkbox_speaker_disable.value());
|
||||
pmem::set_tone_mix(field_tone_mix.value());
|
||||
audio::output::update_audio_mute();
|
||||
nav.pop();
|
||||
};
|
||||
@@ -540,18 +576,17 @@ void SetAudioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// QR Code Settings
|
||||
// ------------------------------------------------------
|
||||
/* SetQRCodeView *****************************************/
|
||||
|
||||
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
|
||||
add_children({&checkbox_bigger_qr,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_bigger_qr.set_value(persistent_memory::show_bigger_qr_code());
|
||||
checkbox_bigger_qr.set_value(pmem::show_bigger_qr_code());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
pmem::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -564,19 +599,18 @@ void SetQRCodeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Rotary Encoder Dial Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetEncoderDialView ************************************/
|
||||
|
||||
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_encoder_dial_sensitivity,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(persistent_memory::config_encoder_dial_sensitivity());
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -589,11 +623,10 @@ void SetEncoderDialView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Settings main menu
|
||||
// ---------------------------------------------------------
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ff.h"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define MAX_FREQ_CORRECTION 4294967295 // maximum possible for an uint32_t
|
||||
#define MAX_FREQ_CORRECTION INT32_MAX
|
||||
|
||||
struct SetDateTimeModel {
|
||||
uint16_t year;
|
||||
@@ -208,16 +209,16 @@ class SetUIView : public View {
|
||||
|
||||
private:
|
||||
Checkbox checkbox_disable_touchscreen{
|
||||
{3 * 8, 2 * 16},
|
||||
{3 * 8, 1 * 16},
|
||||
20,
|
||||
"Disable touchscreen"};
|
||||
|
||||
Checkbox checkbox_bloff{
|
||||
{3 * 8, 4 * 16},
|
||||
{3 * 8, 3 * 16},
|
||||
20,
|
||||
"Backlight off after:"};
|
||||
OptionsField options_bloff{
|
||||
{60, 5 * 16 + 8},
|
||||
{60, 4 * 16 + 8},
|
||||
20,
|
||||
{
|
||||
{"5 seconds", backlight_timeout_t::Timeout5Sec},
|
||||
@@ -231,25 +232,65 @@ class SetUIView : public View {
|
||||
}};
|
||||
|
||||
Checkbox checkbox_showsplash{
|
||||
{3 * 8, 7 * 16},
|
||||
{3 * 8, 6 * 16},
|
||||
20,
|
||||
"Show splash"};
|
||||
|
||||
Checkbox checkbox_showclock{
|
||||
{3 * 8, 9 * 16},
|
||||
{3 * 8, 8 * 16},
|
||||
20,
|
||||
"Show clock with:"};
|
||||
|
||||
OptionsField options_clockformat{
|
||||
{60, 10 * 16 + 8},
|
||||
{60, 9 * 16 + 8},
|
||||
20,
|
||||
{{"time only", 0},
|
||||
{"time and date", 1}}};
|
||||
|
||||
Checkbox checkbox_guireturnflag{
|
||||
{3 * 8, 12 * 16},
|
||||
25,
|
||||
"Show return icon in GUI"};
|
||||
{3 * 8, 11 * 16},
|
||||
20,
|
||||
"Back button in menu"};
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 13 * 16}, "Show/Hide Status Icons", Color::light_grey()},
|
||||
};
|
||||
|
||||
ImageToggle toggle_camera{
|
||||
{7 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_camera};
|
||||
|
||||
ImageToggle toggle_sleep{
|
||||
{9 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_sleep};
|
||||
|
||||
ImageToggle toggle_stealth{
|
||||
{11 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_stealth};
|
||||
|
||||
ImageToggle toggle_converter{
|
||||
{13 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_upconvert};
|
||||
|
||||
ImageToggle toggle_bias_tee{
|
||||
{15 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_biast_off};
|
||||
|
||||
ImageToggle toggle_clock{
|
||||
{17 * 8, 14 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_mute{
|
||||
{18 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{20 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{22 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
@@ -388,11 +429,6 @@ class SetAudioView : public View {
|
||||
1,
|
||||
'0'};
|
||||
|
||||
Checkbox checkbox_speaker_disable{
|
||||
{2 * 8, 6 * 16},
|
||||
25,
|
||||
"Disable AK speaker amp"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
@@ -57,6 +57,8 @@ SpectrumPainterView::SpectrumPainterView(
|
||||
input_image.set_parent_rect(view_rect);
|
||||
input_text.set_parent_rect(view_rect);
|
||||
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
|
||||
field_frequency.set_step(5000);
|
||||
|
||||
tx_gain = transmitter_model.tx_gain();
|
||||
|
||||
@@ -127,7 +127,7 @@ class SpectrumPainterView : public View {
|
||||
OptionsField option_bandwidth{
|
||||
{4 * 8, footer_location + 2 * 16},
|
||||
5,
|
||||
{BW_OPTIONS}};
|
||||
{}};
|
||||
|
||||
NumberField field_duration{
|
||||
{13 * 8, footer_location + 2 * 16},
|
||||
|
||||
@@ -42,10 +42,9 @@ namespace ui {
|
||||
/* TextViewer *******************************************************/
|
||||
|
||||
TextViewer::TextViewer(Rect parent_rect)
|
||||
: Widget(parent_rect),
|
||||
max_line{static_cast<uint8_t>(parent_rect.height() / char_height)},
|
||||
max_col{static_cast<uint8_t>(parent_rect.width() / char_width)} {
|
||||
: Widget(parent_rect) {
|
||||
set_focusable(true);
|
||||
set_font_zoom(false);
|
||||
}
|
||||
|
||||
void TextViewer::paint(Painter& painter) {
|
||||
@@ -138,6 +137,16 @@ uint32_t TextViewer::offset() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TextViewer::cursor_home() {
|
||||
cursor_.col = 0;
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_end() {
|
||||
cursor_.col = line_length() - 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
@@ -158,18 +167,14 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
||||
++new_line;
|
||||
}
|
||||
|
||||
// Snap to first/last to make navigating easier.
|
||||
if (new_line < 0 && new_col > 0) {
|
||||
// Snap to first/last line to make navigating easier.
|
||||
if (new_line < 0 && cursor_.line > 0) {
|
||||
new_line = 0;
|
||||
new_col = 0;
|
||||
} else if (new_line >= (int32_t)file_->line_count()) {
|
||||
auto last_line = file_->line_count() - 1;
|
||||
int32_t last_col = file_->line_length(last_line) - 1;
|
||||
|
||||
if (new_col < last_col) {
|
||||
if (cursor_.line < last_line)
|
||||
new_line = last_line;
|
||||
new_col = last_col;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_line < 0 || (uint32_t)new_line >= file_->line_count())
|
||||
@@ -177,7 +182,6 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
||||
|
||||
new_line_length = file_->line_length(new_line);
|
||||
|
||||
// TODO: don't wrap with encoder?
|
||||
// Wrap or clamp column.
|
||||
if (new_line_length == 0)
|
||||
new_col = 0;
|
||||
@@ -207,7 +211,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
if (result && *result > 0)
|
||||
painter.draw_string(
|
||||
{0, r.top() + (int)i * char_height},
|
||||
Styles::white_small, {buffer, *result});
|
||||
style(), {buffer, *result});
|
||||
|
||||
// Clear empty line sections. This is less visually jarring than full clear.
|
||||
int32_t clear_width = max_col - (result ? *result : 0);
|
||||
@@ -216,7 +220,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
{(max_col - clear_width) * char_width,
|
||||
r.top() + (int)i * char_height,
|
||||
clear_width * char_width, char_height},
|
||||
Styles::white_small.background);
|
||||
style().background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,8 +241,8 @@ void TextViewer::paint_cursor(Painter& painter) {
|
||||
};
|
||||
|
||||
// Clear old cursor. CONSIDER: XOR cursor?
|
||||
draw_cursor(paint_state_.line, paint_state_.col, Styles::white_small.background);
|
||||
draw_cursor(cursor_.line, cursor_.col, Styles::white_small.foreground);
|
||||
draw_cursor(paint_state_.line, paint_state_.col, style().background);
|
||||
draw_cursor(cursor_.line, cursor_.col, style().foreground);
|
||||
paint_state_.line = cursor_.line;
|
||||
paint_state_.col = cursor_.col;
|
||||
}
|
||||
@@ -252,6 +256,15 @@ void TextViewer::reset_file(FileWrapper* file) {
|
||||
redraw(true);
|
||||
}
|
||||
|
||||
void TextViewer::set_font_zoom(bool zoom) {
|
||||
font_zoom = zoom;
|
||||
font_style = font_zoom ? &Styles::white : &Styles::white_small;
|
||||
char_height = style().font.line_height();
|
||||
char_width = style().font.char_width();
|
||||
max_line = (uint8_t)(parent_rect().height() / char_height);
|
||||
max_col = (uint8_t)(parent_rect().width() / char_width);
|
||||
}
|
||||
|
||||
/* TextEditorMenu ***************************************************/
|
||||
|
||||
TextEditorMenu::TextEditorMenu()
|
||||
@@ -259,9 +272,9 @@ TextEditorMenu::TextEditorMenu()
|
||||
add_children(
|
||||
{
|
||||
&rect_frame,
|
||||
&button_cut,
|
||||
&button_paste,
|
||||
&button_copy,
|
||||
&button_home,
|
||||
&button_end,
|
||||
&button_zoom,
|
||||
&button_delline,
|
||||
&button_edit,
|
||||
&button_addline,
|
||||
@@ -310,14 +323,20 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
};
|
||||
|
||||
menu.hidden(true);
|
||||
menu.on_cut() = [this]() {
|
||||
show_nyi();
|
||||
menu.on_home() = [this]() {
|
||||
viewer.cursor_home();
|
||||
hide_menu(true);
|
||||
};
|
||||
menu.on_paste() = [this]() {
|
||||
show_nyi();
|
||||
|
||||
menu.on_end() = [this]() {
|
||||
viewer.cursor_end();
|
||||
hide_menu(true);
|
||||
};
|
||||
menu.on_copy() = [this]() {
|
||||
show_nyi();
|
||||
|
||||
menu.on_zoom() = [this]() {
|
||||
viewer.toggle_font_zoom();
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
menu.on_delete_line() = [this]() {
|
||||
@@ -490,10 +509,6 @@ void TextEditorView::show_edit_line() {
|
||||
});
|
||||
}
|
||||
|
||||
void TextEditorView::show_nyi() {
|
||||
nav_.display_modal("Soon...", "Coming soon.");
|
||||
}
|
||||
|
||||
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
|
||||
if (!file_dirty_) {
|
||||
if (continuation)
|
||||
|
||||
@@ -72,15 +72,23 @@ class TextViewer : public Widget {
|
||||
uint32_t col() const { return cursor_.col; }
|
||||
uint32_t offset() const;
|
||||
|
||||
void cursor_home();
|
||||
void cursor_end();
|
||||
|
||||
// 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;
|
||||
const Style& style() { return *font_style; }
|
||||
void set_font_zoom(bool zoom);
|
||||
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
|
||||
|
||||
const uint8_t max_line = 32;
|
||||
const uint8_t max_col = 48;
|
||||
private:
|
||||
bool font_zoom{};
|
||||
const Style* font_style{};
|
||||
int8_t char_width{};
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
@@ -120,14 +128,12 @@ class TextEditorMenu : public View {
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
std::function<void()>& on_cut() { return button_cut.on_select; }
|
||||
std::function<void()>& on_paste() { return button_paste.on_select; }
|
||||
std::function<void()>& on_copy() { return button_copy.on_select; }
|
||||
|
||||
std::function<void()>& on_home() { return button_home.on_select; }
|
||||
std::function<void()>& on_end() { return button_end.on_select; }
|
||||
std::function<void()>& on_zoom() { return button_zoom.on_select; }
|
||||
std::function<void()>& on_delete_line() { return button_delline.on_select; }
|
||||
std::function<void()>& on_edit_line() { return button_edit.on_select; }
|
||||
std::function<void()>& on_add_line() { return button_addline.on_select; }
|
||||
|
||||
std::function<void()>& on_open() { return button_open.on_select; }
|
||||
std::function<void()>& on_save() { return button_save.on_select; }
|
||||
std::function<void()>& on_exit() { return button_exit.on_select; }
|
||||
@@ -139,23 +145,23 @@ class TextEditorMenu : public View {
|
||||
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_cut{
|
||||
NewButton button_home{
|
||||
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Cut",
|
||||
&bitmap_icon_cut,
|
||||
"Home",
|
||||
&bitmap_arrow_left,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_paste{
|
||||
NewButton button_end{
|
||||
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Paste",
|
||||
&bitmap_icon_paste,
|
||||
"End",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_copy{
|
||||
NewButton button_zoom{
|
||||
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Copy",
|
||||
&bitmap_icon_copy,
|
||||
Color::dark_grey()};
|
||||
"Zoom",
|
||||
&bitmap_icon_search,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_delline{
|
||||
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
|
||||
@@ -220,7 +226,6 @@ class TextEditorView : public View {
|
||||
void hide_menu(bool hidden = true);
|
||||
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();
|
||||
@@ -258,4 +263,4 @@ class TextEditorView : public View {
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // __UI_TEXT_EDITOR_H__
|
||||
#endif // __UI_TEXT_EDITOR_H__
|
||||
|
||||
@@ -184,6 +184,9 @@ void speaker_unmute() {
|
||||
|
||||
void update_audio_mute() {
|
||||
cfg_speaker_disable = portapack::persistent_memory::config_speaker_disable();
|
||||
if (cfg_speaker_disable) {
|
||||
audio_codec->speaker_disable();
|
||||
}
|
||||
|
||||
if (portapack::persistent_memory::config_audio_mute())
|
||||
speaker_mute();
|
||||
@@ -269,4 +272,8 @@ void set_rate(const Rate rate) {
|
||||
clock_manager.set_base_audio_clock_divider(toUType(rate));
|
||||
}
|
||||
|
||||
bool speaker_disable_supported() {
|
||||
return audio_codec->speaker_disable_supported();
|
||||
}
|
||||
|
||||
} /* namespace audio */
|
||||
|
||||
@@ -43,6 +43,7 @@ class Codec {
|
||||
|
||||
virtual void speaker_enable() = 0;
|
||||
virtual void speaker_disable() = 0;
|
||||
virtual bool speaker_disable_supported() const = 0;
|
||||
|
||||
virtual void headphone_enable() = 0;
|
||||
virtual void headphone_disable() = 0;
|
||||
@@ -107,6 +108,7 @@ size_t reg_bits();
|
||||
|
||||
void init(audio::Codec* const codec);
|
||||
void shutdown();
|
||||
bool speaker_disable_supported();
|
||||
|
||||
enum class Rate {
|
||||
Hz_12000 = 4,
|
||||
|
||||
@@ -5341,6 +5341,234 @@ static constexpr Bitmap bitmap_icon_new_dir{
|
||||
{16, 16},
|
||||
bitmap_icon_new_dir_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_paint_data[] = {
|
||||
0xFE,
|
||||
0x3F,
|
||||
0xFF,
|
||||
0x3F,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xBF,
|
||||
0xFE,
|
||||
0xBF,
|
||||
0x00,
|
||||
0x80,
|
||||
0x80,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x00,
|
||||
0x80,
|
||||
0x00,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_paint{
|
||||
{16, 16},
|
||||
bitmap_icon_paint_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_touchtunes_data[] = {
|
||||
0xE0,
|
||||
0x07,
|
||||
0x30,
|
||||
0x0C,
|
||||
0x7C,
|
||||
0x3E,
|
||||
0xC4,
|
||||
0x23,
|
||||
0x26,
|
||||
0x64,
|
||||
0x12,
|
||||
0x48,
|
||||
0x0F,
|
||||
0xF3,
|
||||
0x09,
|
||||
0x95,
|
||||
0x0F,
|
||||
0xF1,
|
||||
0x09,
|
||||
0x91,
|
||||
0x0F,
|
||||
0xF1,
|
||||
0xC9,
|
||||
0x91,
|
||||
0xE9,
|
||||
0x91,
|
||||
0xC9,
|
||||
0x90,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0xFF,
|
||||
0xFF,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_touchtunes{
|
||||
{16, 16},
|
||||
bitmap_icon_touchtunes_data};
|
||||
|
||||
static constexpr uint8_t bitmap_arrow_left_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x38,
|
||||
0x00,
|
||||
0xFC,
|
||||
0x7F,
|
||||
0xFE,
|
||||
0x7F,
|
||||
0xFC,
|
||||
0x7F,
|
||||
0x38,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_arrow_left{
|
||||
{16, 16},
|
||||
bitmap_arrow_left_data};
|
||||
|
||||
static constexpr uint8_t bitmap_arrow_right_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x00,
|
||||
0x1C,
|
||||
0xFE,
|
||||
0x3F,
|
||||
0xFE,
|
||||
0x7F,
|
||||
0xFE,
|
||||
0x3F,
|
||||
0x00,
|
||||
0x1C,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_arrow_right{
|
||||
{16, 16},
|
||||
bitmap_arrow_right_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_speaker_and_headphones_data[] = {
|
||||
0x20,
|
||||
0x10,
|
||||
0x30,
|
||||
0x20,
|
||||
0x38,
|
||||
0x44,
|
||||
0x3E,
|
||||
0x48,
|
||||
0x3E,
|
||||
0x91,
|
||||
0x3E,
|
||||
0x92,
|
||||
0x38,
|
||||
0x92,
|
||||
0x30,
|
||||
0x92,
|
||||
0x20,
|
||||
0x92,
|
||||
0x00,
|
||||
0x92,
|
||||
0x30,
|
||||
0x91,
|
||||
0x48,
|
||||
0x48,
|
||||
0x84,
|
||||
0x44,
|
||||
0x84,
|
||||
0x20,
|
||||
0x86,
|
||||
0x11,
|
||||
0x86,
|
||||
0x01,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_speaker_and_headphones{
|
||||
{16, 16},
|
||||
bitmap_icon_speaker_and_headphones_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_speaker_and_headphones_mute_data[] = {
|
||||
0x40,
|
||||
0x00,
|
||||
0x60,
|
||||
0x44,
|
||||
0x70,
|
||||
0x6C,
|
||||
0x7C,
|
||||
0x38,
|
||||
0x7C,
|
||||
0x10,
|
||||
0x7C,
|
||||
0x38,
|
||||
0x70,
|
||||
0x6C,
|
||||
0x60,
|
||||
0x44,
|
||||
0x40,
|
||||
0x00,
|
||||
0x00,
|
||||
0x44,
|
||||
0x30,
|
||||
0x6C,
|
||||
0x48,
|
||||
0x38,
|
||||
0x84,
|
||||
0x10,
|
||||
0x84,
|
||||
0x38,
|
||||
0x86,
|
||||
0x6D,
|
||||
0x86,
|
||||
0x45,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
|
||||
{16, 16},
|
||||
bitmap_icon_speaker_and_headphones_mute_data};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__BITMAP_HPP__*/
|
||||
|
||||
@@ -488,6 +488,24 @@ path operator/(const path& lhs, const path& rhs) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool path_iequal(
|
||||
const path& lhs,
|
||||
const path& rhs) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
std::filesystem::path path,
|
||||
std::filesystem::path wild)
|
||||
|
||||
@@ -168,6 +168,9 @@ bool operator>(const path& lhs, const path& rhs);
|
||||
path operator+(const path& lhs, const path& rhs);
|
||||
path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
/* Case insensitive path equality on underlying "native" string. */
|
||||
bool path_iequal(const path& lhs, const path& rhs);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");
|
||||
|
||||
@@ -30,7 +30,8 @@ using options_t = std::vector<option_t>;
|
||||
options_t freqman_entry_modulations = {
|
||||
{"AM", 0},
|
||||
{"NFM", 1},
|
||||
{"WFM", 2}};
|
||||
{"WFM", 2},
|
||||
{"SPEC", 3}};
|
||||
|
||||
options_t freqman_entry_bandwidths[4] = {
|
||||
{// AM
|
||||
@@ -48,6 +49,25 @@ options_t freqman_entry_bandwidths[4] = {
|
||||
{"40k", 2},
|
||||
{"180k", 1},
|
||||
{"200k", 0},
|
||||
},
|
||||
{
|
||||
// SPEC
|
||||
{"8k5", 8500},
|
||||
{"11k", 11000},
|
||||
{"16k", 16000},
|
||||
{"25k", 25000},
|
||||
{"50k", 50000},
|
||||
{"100k", 100000},
|
||||
{"250k", 250000},
|
||||
{"500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/
|
||||
{"600k", 600000}, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */
|
||||
{"650k", 650000},
|
||||
{"750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is decimated, (not real file size) */
|
||||
{"1100k", 1100000},
|
||||
{"1750k", 1750000},
|
||||
{"2000k", 2000000},
|
||||
{"2500k", 2500000},
|
||||
{"2750k", 2750000}, // That is our max Capture option, to keep using later / 8 decimation (22Mhz sampling ADC)
|
||||
}};
|
||||
|
||||
options_t freqman_entry_steps = {
|
||||
|
||||
@@ -59,10 +59,11 @@ enum freqman_entry_type : int8_t {
|
||||
NOTYPE // undetected
|
||||
};
|
||||
|
||||
enum freqman_entry_modulation : int8_t {
|
||||
enum freqman_entry_modulation : uint8_t {
|
||||
AM_MODULATION = 0,
|
||||
NFM_MODULATION,
|
||||
WFM_MODULATION,
|
||||
SPEC_MODULATION,
|
||||
};
|
||||
|
||||
// Entry step placed for AlainD freqman version (or any other enhanced version)
|
||||
|
||||
@@ -30,7 +30,7 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
// "Repeat" handling - simulated button release
|
||||
if (repeat_ctr_) {
|
||||
// Make sure the button is still being held continuously
|
||||
if (history_ == 0xFF) {
|
||||
if ((history_ == 0xFF) && !long_press_enabled_) {
|
||||
// Simulate button press every REPEAT_SUBSEQUENT_DELAY ticks
|
||||
if (--repeat_ctr_ == 0) {
|
||||
state_ = !state_;
|
||||
@@ -49,29 +49,59 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
if ((history_ & DEBOUNCE_MASK) == DEBOUNCE_MASK) {
|
||||
state_ = 1;
|
||||
held_time_ = 0;
|
||||
|
||||
// If long_press_enabled_, state() function masks the button press until it's released
|
||||
// or until LONG_PRESS_DELAY is reached
|
||||
if (long_press_enabled_) {
|
||||
pulse_upon_release_ = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Previous button state was 1 (pressed);
|
||||
// Has button been released for DEBOUNCE_COUNT ticks?
|
||||
if ((history_ & DEBOUNCE_MASK) == 0) {
|
||||
state_ = 0;
|
||||
// Button has been released when long_press_enabled_ and before LONG_PRESS_DELAY was reached;
|
||||
// allow state() function to finally return a single press indication
|
||||
// (in long press mode, apps won't see button press until the button is released)
|
||||
if (pulse_upon_release_) {
|
||||
// leaving state_==1 for one cycle
|
||||
pulse_upon_release_ = 0;
|
||||
} else {
|
||||
state_ = 0;
|
||||
}
|
||||
|
||||
// Reset long_press_occurred_ flag after button is released
|
||||
long_press_occurred_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Repeat support is limited to the 4 directional buttons
|
||||
if (repeat_enabled_) {
|
||||
// Has button been held continuously for DEBOUNCE_REPEAT_DELAY?
|
||||
if (history_ == 0xFF) {
|
||||
if (++held_time_ == REPEAT_INITIAL_DELAY) {
|
||||
// Has button been held continuously?
|
||||
if (history_ == 0xFF) {
|
||||
held_time_++;
|
||||
if (pulse_upon_release_) {
|
||||
// Button is being held down and long_press support is enabled for this key:
|
||||
// if LONG_PRESS_DELAY is reached then finally report that switch is pressed and set flag
|
||||
// indicating it was a LONG press
|
||||
// (note that repease_support and long_press support are mutually exclusive)
|
||||
if (held_time_ == LONG_PRESS_DELAY) {
|
||||
long_press_occurred_ = true;
|
||||
pulse_upon_release_ = 0;
|
||||
held_time_ = 0;
|
||||
return true;
|
||||
}
|
||||
} else if (repeat_enabled_ && !long_press_enabled_) {
|
||||
// Repeat support -- 4 directional buttons only (unless long_press is enabled)
|
||||
if (held_time_ == REPEAT_INITIAL_DELAY) {
|
||||
// Delay reached; trigger repeat code on NEXT tick
|
||||
repeat_ctr_ = 1;
|
||||
held_time_ = 0;
|
||||
}
|
||||
} else {
|
||||
// Button not continuously pressed; reset counter
|
||||
held_time_ = 0;
|
||||
}
|
||||
} else {
|
||||
// Button not continuously pressed; reset counter
|
||||
held_time_ = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -31,25 +31,39 @@
|
||||
// # of timer0 ticks before a held button starts being counted as repeated presses
|
||||
#define REPEAT_INITIAL_DELAY 250
|
||||
#define REPEAT_SUBSEQUENT_DELAY 92
|
||||
#define LONG_PRESS_DELAY 1000
|
||||
|
||||
class Debounce {
|
||||
public:
|
||||
bool feed(const uint8_t bit);
|
||||
|
||||
uint8_t state() const {
|
||||
return state_;
|
||||
return (pulse_upon_release_) ? 0 : state_;
|
||||
}
|
||||
|
||||
void enable_repeat() {
|
||||
repeat_enabled_ = true;
|
||||
}
|
||||
|
||||
void set_long_press_support(bool v) {
|
||||
long_press_enabled_ = v;
|
||||
}
|
||||
|
||||
bool long_press_occurred() {
|
||||
bool v = long_press_occurred_;
|
||||
long_press_occurred_ = false;
|
||||
return v;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t history_{0};
|
||||
uint8_t state_{0};
|
||||
bool repeat_enabled_{0};
|
||||
uint16_t repeat_ctr_{0};
|
||||
uint16_t held_time_{0};
|
||||
bool pulse_upon_release_{0};
|
||||
bool long_press_enabled_{0};
|
||||
bool long_press_occurred_{0};
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -195,16 +195,33 @@ void controls_init() {
|
||||
|
||||
SwitchesState get_switches_state() {
|
||||
SwitchesState result;
|
||||
|
||||
// Right, Left, Down, Up, & Select switches
|
||||
for (size_t i = 0; i < result.size() - 1; i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
result[i] = switch_debounce[i].state();
|
||||
}
|
||||
|
||||
result[result.size() - 1] = switch_debounce[switch_debounce.size() - 1].state();
|
||||
|
||||
// Grab Dfu switch from bit 7 and return in bit 5 so that all switches are grouped together in this 6-bit result
|
||||
result[(size_t)Switch::Dfu] = switch_debounce[7].state();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Configure which switches support long press (note those switches will not support Repeat function)
|
||||
void switches_long_press_enable(SwitchesState switches_long_press_enabled) {
|
||||
// Right, Left, Down, Up, & Select switches
|
||||
for (size_t i = 0; i < switches_long_press_enabled.size() - 1; i++) {
|
||||
switch_debounce[i].set_long_press_support(switches_long_press_enabled[i]);
|
||||
}
|
||||
|
||||
// Dfu switch
|
||||
switch_debounce[7].set_long_press_support(switches_long_press_enabled[(size_t)Switch::Dfu]);
|
||||
}
|
||||
|
||||
bool switch_long_press_occurred(size_t v) {
|
||||
return (v == (size_t)Switch::Dfu) ? switch_debounce[7].long_press_occurred() : switch_debounce[v].long_press_occurred();
|
||||
}
|
||||
|
||||
EncoderPosition get_encoder_position() {
|
||||
return encoder_position;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "touch.hpp"
|
||||
|
||||
// Must be same values as in ui::KeyEvent
|
||||
enum class Switch {
|
||||
Right = 0,
|
||||
Left = 1,
|
||||
@@ -44,6 +45,8 @@ void controls_init();
|
||||
SwitchesState get_switches_state();
|
||||
EncoderPosition get_encoder_position();
|
||||
touch::Frame get_touch_frame();
|
||||
void switches_long_press_enable(SwitchesState v);
|
||||
bool switch_long_press_occurred(size_t v);
|
||||
|
||||
namespace control {
|
||||
namespace debug {
|
||||
|
||||
@@ -286,7 +286,7 @@ static const char* whitespace_str = " \t\r\n";
|
||||
std::string trim(std::string_view str) {
|
||||
auto first = str.find_first_not_of(whitespace_str);
|
||||
auto last = str.find_last_not_of(whitespace_str);
|
||||
return std::string{str.substr(first, last - first)};
|
||||
return std::string{str.substr(first, last - first + 1)};
|
||||
}
|
||||
|
||||
std::string trimr(std::string_view str) {
|
||||
@@ -296,4 +296,4 @@ std::string trimr(std::string_view str) {
|
||||
|
||||
std::string truncate(std::string_view str, size_t length) {
|
||||
return std::string{str.length() <= length ? str : str.substr(0, length)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,9 @@ const tone_key_t tone_keys = {
|
||||
{"34 M3", 218.100},
|
||||
{"35 M4", 225.700},
|
||||
{"49 9Z", 229.100},
|
||||
{"36 --", 233.600},
|
||||
{"37 --", 241.800},
|
||||
{"38 --", 250.300},
|
||||
{"36 M5", 233.600},
|
||||
{"37 M6", 241.800},
|
||||
{"38 M7", 250.300},
|
||||
{"50 0Z", 254.100},
|
||||
{"Axient 28kHz", 28000.0},
|
||||
{"Senn. 32.768k", 32768.0},
|
||||
@@ -113,6 +113,36 @@ std::string tone_key_string(tone_index index) {
|
||||
return tone_keys[index].first;
|
||||
}
|
||||
|
||||
std::string tone_key_string_by_value(uint32_t value) {
|
||||
return tone_key_string(tone_key_index_by_value(value));
|
||||
}
|
||||
|
||||
tone_index tone_key_index_by_value(uint32_t value) {
|
||||
float diff;
|
||||
float min_diff{(float)value};
|
||||
float fvalue{(float)((min_diff + 50.0) / 100.0)};
|
||||
tone_index min_idx{-1};
|
||||
tone_index idx;
|
||||
|
||||
// Find nearest match
|
||||
for (idx = 0; idx < (tone_index)tone_keys.size(); idx++) {
|
||||
diff = abs(fvalue - tone_keys[idx].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = idx;
|
||||
min_diff = diff;
|
||||
} else {
|
||||
// list is sorted in frequency order; if diff is getting larger than we've passed it
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (min_diff < 40.0)
|
||||
return min_idx;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
tone_index tone_key_index_by_string(char* str) {
|
||||
if (!str)
|
||||
return -1;
|
||||
@@ -123,9 +153,4 @@ tone_index tone_key_index_by_string(char* str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* tone_index tone_key_index_by_value( int32_t freq )
|
||||
{
|
||||
return -1 ;
|
||||
} */
|
||||
|
||||
} // namespace tonekey
|
||||
|
||||
@@ -30,7 +30,7 @@ using namespace ui;
|
||||
|
||||
namespace tonekey {
|
||||
|
||||
typedef int16_t tone_index;
|
||||
typedef int32_t tone_index;
|
||||
|
||||
using tone_key_t = std::vector<std::pair<std::string, float>>;
|
||||
|
||||
@@ -40,8 +40,8 @@ void tone_keys_populate(OptionsField& field);
|
||||
float tone_key_frequency(const tone_index index);
|
||||
|
||||
std::string tone_key_string(const tone_index index);
|
||||
tone_index tone_key_index_by_string(char* str);
|
||||
// tone_index tone_key_index_by_value( int32_t freq );
|
||||
std::string tone_key_string_by_value(uint32_t value);
|
||||
tone_index tone_key_index_by_value(uint32_t value);
|
||||
|
||||
} // namespace tonekey
|
||||
|
||||
|
||||
@@ -37,9 +37,13 @@ AlphanumView::AlphanumView(
|
||||
: TextEntryView(nav, str, max_length) {
|
||||
size_t n;
|
||||
|
||||
add_children({&button_mode,
|
||||
&text_raw,
|
||||
&field_raw});
|
||||
add_children({
|
||||
&labels,
|
||||
&field_raw,
|
||||
&text_raw_to_char,
|
||||
&button_delete,
|
||||
&button_mode,
|
||||
});
|
||||
|
||||
const auto button_fn = [this](Button& button) {
|
||||
this->on_button(button);
|
||||
@@ -65,10 +69,19 @@ AlphanumView::AlphanumView(
|
||||
set_mode(mode + 1);
|
||||
};
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
char_delete();
|
||||
};
|
||||
|
||||
field_raw.set_value('0');
|
||||
field_raw.on_select = [this](NumberField&) {
|
||||
char_add(field_raw.value());
|
||||
};
|
||||
|
||||
// make text_raw_to_char widget display the char value from field_raw
|
||||
field_raw.on_change = [this](auto) {
|
||||
text_raw_to_char.set(std::string{static_cast<char>(field_raw.value())});
|
||||
};
|
||||
}
|
||||
|
||||
void AlphanumView::set_mode(const uint32_t new_mode) {
|
||||
@@ -96,11 +109,7 @@ void AlphanumView::set_mode(const uint32_t new_mode) {
|
||||
|
||||
void AlphanumView::on_button(Button& button) {
|
||||
const auto c = button.text()[0];
|
||||
|
||||
if (c == '<')
|
||||
char_delete();
|
||||
else
|
||||
char_add(c);
|
||||
char_add(c);
|
||||
}
|
||||
|
||||
bool AlphanumView::on_encoder(const EncoderEvent delta) {
|
||||
|
||||
@@ -43,14 +43,14 @@ 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_digit = "0123456789!\"#'()*+-/:;=>?@[\\]<";
|
||||
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] = {
|
||||
{"Upper", keys_upper},
|
||||
{"Lower", keys_lower},
|
||||
{"Digit", keys_digit}};
|
||||
{"ABC", keys_upper},
|
||||
{"abc", keys_lower},
|
||||
{"123", keys_digit}};
|
||||
|
||||
int16_t focused_button = 0;
|
||||
uint32_t mode = 0; // Uppercase
|
||||
@@ -60,13 +60,10 @@ class AlphanumView : public TextEntryView {
|
||||
|
||||
std::array<Button, 30> buttons{};
|
||||
|
||||
Button button_mode{
|
||||
{21 * 8, 33 * 8, 8 * 8, 32},
|
||||
""};
|
||||
Labels labels{
|
||||
{{1 * 8, 33 * 8}, "Raw:", Color::light_grey()},
|
||||
{{1 * 8, 35 * 8}, "AKA:", Color::light_grey()}};
|
||||
|
||||
Text text_raw{
|
||||
{1 * 8, 33 * 8, 4 * 8, 16},
|
||||
"Raw:"};
|
||||
NumberField field_raw{
|
||||
{5 * 8, 33 * 8},
|
||||
3,
|
||||
@@ -74,9 +71,17 @@ class AlphanumView : public TextEntryView {
|
||||
1,
|
||||
'0'};
|
||||
|
||||
Button button_ok{
|
||||
{10 * 8, 33 * 8, 9 * 8, 32},
|
||||
"OK"};
|
||||
Text text_raw_to_char{
|
||||
{5 * 8, 35 * 8, 4 * 8, 16},
|
||||
"0"};
|
||||
|
||||
Button button_delete{
|
||||
{9 * 8, 33 * 8, 6 * 8, 32},
|
||||
"<DEL"};
|
||||
|
||||
Button button_mode{
|
||||
{16 * 8, 33 * 8, 5 * 8, 32},
|
||||
""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -36,7 +36,7 @@ using namespace portapack;
|
||||
namespace ui {
|
||||
namespace spectrum {
|
||||
|
||||
/* AudioSpectrumView******************************************************/
|
||||
/* AudioSpectrumView ******************************************************/
|
||||
|
||||
AudioSpectrumView::AudioSpectrumView(
|
||||
const Rect parent_rect)
|
||||
@@ -247,28 +247,25 @@ void FrequencyScale::on_tick_second() {
|
||||
_blink = !_blink;
|
||||
}
|
||||
|
||||
/* WaterfallView *********************************************************/
|
||||
/* WaterfallWidget *********************************************************/
|
||||
// TODO: buffer and use "paint" instead of immediate drawing would help with
|
||||
// preventing flicker from drawing. Would use more RAM however.
|
||||
|
||||
void WaterfallView::on_show() {
|
||||
void WaterfallWidget::on_show() {
|
||||
clear();
|
||||
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
}
|
||||
|
||||
void WaterfallView::on_hide() {
|
||||
void WaterfallWidget::on_hide() {
|
||||
/* TODO: Clear region to eliminate brief flash of content at un-shifted
|
||||
* position?
|
||||
*/
|
||||
display.scroll_disable();
|
||||
}
|
||||
|
||||
void WaterfallView::paint(Painter& painter) {
|
||||
// Do nothing.
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void WaterfallView::on_channel_spectrum(
|
||||
void WaterfallWidget::on_channel_spectrum(
|
||||
const ChannelSpectrum& spectrum) {
|
||||
/* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */
|
||||
|
||||
@@ -290,16 +287,16 @@ void WaterfallView::on_channel_spectrum(
|
||||
pixel_row);
|
||||
}
|
||||
|
||||
void WaterfallView::clear() {
|
||||
void WaterfallWidget::clear() {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Color::black());
|
||||
}
|
||||
|
||||
/* WaterfallWidget *******************************************************/
|
||||
/* WaterfallView *******************************************************/
|
||||
|
||||
WaterfallWidget::WaterfallWidget(const bool cursor) {
|
||||
add_children({&waterfall_view,
|
||||
WaterfallView::WaterfallView(const bool cursor) {
|
||||
add_children({&waterfall_widget,
|
||||
&frequency_scale});
|
||||
|
||||
frequency_scale.set_focusable(cursor);
|
||||
@@ -310,17 +307,17 @@ WaterfallWidget::WaterfallWidget(const bool cursor) {
|
||||
};
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_show() {
|
||||
void WaterfallView::on_show() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_hide() {
|
||||
void WaterfallView::on_hide() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_stop();
|
||||
}
|
||||
|
||||
void WaterfallWidget::show_audio_spectrum_view(const bool show) {
|
||||
void WaterfallView::show_audio_spectrum_view(const bool show) {
|
||||
if ((audio_spectrum_view && show) || (!audio_spectrum_view && !show)) return;
|
||||
|
||||
if (show) {
|
||||
@@ -335,18 +332,18 @@ void WaterfallWidget::show_audio_spectrum_view(const bool show) {
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallWidget::update_widgets_rect() {
|
||||
void WaterfallView::update_widgets_rect() {
|
||||
if (audio_spectrum_view) {
|
||||
frequency_scale.set_parent_rect({0, audio_spectrum_height, screen_rect().width(), scale_height});
|
||||
waterfall_view.set_parent_rect(waterfall_reduced_rect);
|
||||
waterfall_widget.set_parent_rect(waterfall_reduced_rect);
|
||||
} else {
|
||||
frequency_scale.set_parent_rect({0, 0, screen_rect().width(), scale_height});
|
||||
waterfall_view.set_parent_rect(waterfall_normal_rect);
|
||||
waterfall_widget.set_parent_rect(waterfall_normal_rect);
|
||||
}
|
||||
waterfall_view.on_show();
|
||||
waterfall_widget.on_show();
|
||||
}
|
||||
|
||||
void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||
void WaterfallView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
waterfall_normal_rect = {0, scale_height, new_parent_rect.width(), new_parent_rect.height() - scale_height};
|
||||
@@ -355,13 +352,8 @@ void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||
update_widgets_rect();
|
||||
}
|
||||
|
||||
void WaterfallWidget::paint(Painter& painter) {
|
||||
// TODO:
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
waterfall_view.on_channel_spectrum(spectrum);
|
||||
void WaterfallView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
waterfall_widget.on_channel_spectrum(spectrum);
|
||||
sampling_rate = spectrum.sampling_rate;
|
||||
frequency_scale.set_spectrum_sampling_rate(sampling_rate);
|
||||
frequency_scale.set_channel_filter(
|
||||
@@ -370,9 +362,40 @@ void WaterfallWidget::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
spectrum.channel_filter_transition);
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_audio_spectrum() {
|
||||
void WaterfallView::on_audio_spectrum() {
|
||||
audio_spectrum_view->on_audio_spectrum(audio_spectrum_data);
|
||||
}
|
||||
|
||||
} /* namespace spectrum */
|
||||
|
||||
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) {
|
||||
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
|
||||
return 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
|
||||
case 4000000 ... 6000000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz).
|
||||
return 2500000; // In some IC, MAX2837 appears as 2250000, but both work similarly.
|
||||
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz. (1Mhz showed slightly higher noise background).
|
||||
return 3500000;
|
||||
|
||||
case 14000000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
return 5000000;
|
||||
|
||||
case 16000000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
return 6000000;
|
||||
|
||||
case 20000000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
return 7000000;
|
||||
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz = 22Mhz max ADC sampling and others.
|
||||
// We tested also 9Mhz FPB slightly too much noise floor, better at 8Mhz.
|
||||
return 8000000;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -108,12 +108,11 @@ class FrequencyScale : public Widget {
|
||||
* If the baseband is shutdown or otherwise not running when interacting
|
||||
* with these, they will almost certainly hang the device. */
|
||||
|
||||
class WaterfallView : public Widget {
|
||||
class WaterfallWidget : public Widget {
|
||||
public:
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void paint(Painter&) override {}
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
|
||||
@@ -121,16 +120,16 @@ class WaterfallView : public Widget {
|
||||
void clear();
|
||||
};
|
||||
|
||||
class WaterfallWidget : public View {
|
||||
class WaterfallView : public View {
|
||||
public:
|
||||
std::function<void(int32_t offset)> on_select{};
|
||||
|
||||
WaterfallWidget(const bool cursor = false);
|
||||
WaterfallView(const bool cursor = false);
|
||||
|
||||
WaterfallWidget(const WaterfallWidget&) = delete;
|
||||
WaterfallWidget(WaterfallWidget&&) = delete;
|
||||
WaterfallWidget& operator=(const WaterfallWidget&) = delete;
|
||||
WaterfallWidget& operator=(WaterfallWidget&&) = delete;
|
||||
WaterfallView(const WaterfallView&) = delete;
|
||||
WaterfallView(WaterfallView&&) = delete;
|
||||
WaterfallView& operator=(const WaterfallView&) = delete;
|
||||
WaterfallView& operator=(WaterfallView&&) = delete;
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
@@ -139,8 +138,6 @@ class WaterfallWidget : public View {
|
||||
|
||||
void show_audio_spectrum_view(const bool show);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
void update_widgets_rect();
|
||||
|
||||
@@ -148,7 +145,7 @@ class WaterfallWidget : public View {
|
||||
static constexpr Dim audio_spectrum_height = 16 * 2 + 20;
|
||||
static constexpr Dim scale_height = 20;
|
||||
|
||||
WaterfallView waterfall_view{};
|
||||
WaterfallWidget waterfall_widget{};
|
||||
FrequencyScale frequency_scale{};
|
||||
|
||||
ChannelSpectrumFIFO* channel_fifo{nullptr};
|
||||
@@ -195,6 +192,10 @@ class WaterfallWidget : public View {
|
||||
};
|
||||
|
||||
} /* namespace spectrum */
|
||||
|
||||
/* Calculates the best anti_alias_baseband_bandwidth_filter for the given sampling rate. */
|
||||
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate);
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_SPECTRUM_H__*/
|
||||
|
||||
@@ -50,7 +50,7 @@ class TextEntryView : public View {
|
||||
|
||||
TextField text_input;
|
||||
Button button_ok{
|
||||
{10 * 8, 33 * 8, 9 * 8, 32},
|
||||
{22 * 8, 33 * 8, 7 * 8, 32},
|
||||
"OK"};
|
||||
};
|
||||
|
||||
|
||||
@@ -98,9 +98,51 @@
|
||||
|
||||
using portapack::receiver_model;
|
||||
using portapack::transmitter_model;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* StatusTray ************************************************************/
|
||||
|
||||
StatusTray::StatusTray(Point pos)
|
||||
: View{{pos, {0, height}}},
|
||||
pos_(pos) {
|
||||
set_focusable(false);
|
||||
}
|
||||
|
||||
void StatusTray::add(Widget* child) {
|
||||
width_ += child->parent_rect().width();
|
||||
add_child(child);
|
||||
}
|
||||
|
||||
void StatusTray::update_layout() {
|
||||
// Widen the tray's parent rect.
|
||||
auto rect = parent_rect();
|
||||
set_parent_rect({{rect.left() - width_, rect.top()}, {rect.right() + width_, height}});
|
||||
|
||||
// Update the children.
|
||||
auto x = 0;
|
||||
for (auto child : children()) {
|
||||
auto size = child->parent_rect().size();
|
||||
child->set_parent_rect({{x, 0}, size});
|
||||
x += size.width();
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void StatusTray::clear() {
|
||||
// More efficient than 'remove_children'.
|
||||
for (auto child : children())
|
||||
child->set_parent(nullptr);
|
||||
children_.clear();
|
||||
width_ = 0;
|
||||
set_parent_rect({pos_, {width_, height}});
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void StatusTray::paint(Painter&) {
|
||||
}
|
||||
|
||||
/* SystemStatusView ******************************************************/
|
||||
|
||||
SystemStatusView::SystemStatusView(
|
||||
@@ -111,48 +153,19 @@ SystemStatusView::SystemStatusView(
|
||||
&button_back,
|
||||
&title,
|
||||
&button_title,
|
||||
&button_converter,
|
||||
&button_speaker,
|
||||
&button_stealth,
|
||||
//&button_textentry,
|
||||
&button_camera,
|
||||
&button_sleep,
|
||||
&button_bias_tee,
|
||||
&button_clock_status,
|
||||
&sd_card_status_view,
|
||||
&status_icons,
|
||||
});
|
||||
|
||||
if (portapack::persistent_memory::should_use_sdcard_for_pmem()) {
|
||||
portapack::persistent_memory::load_persistent_settings_from_file();
|
||||
}
|
||||
|
||||
if (portapack::persistent_memory::config_hide_converter()) {
|
||||
button_converter.hidden(true);
|
||||
} else {
|
||||
button_converter.hidden(false);
|
||||
if (portapack::persistent_memory::config_converter()) {
|
||||
button_converter.set_foreground(Color::red());
|
||||
} else {
|
||||
button_converter.set_foreground(Color::light_grey());
|
||||
}
|
||||
if (pmem::should_use_sdcard_for_pmem()) {
|
||||
pmem::load_persistent_settings_from_file();
|
||||
}
|
||||
|
||||
button_back.id = -1; // Special ID used by FocusManager
|
||||
title.set_style(&Styles::bg_dark_grey);
|
||||
|
||||
if (portapack::persistent_memory::stealth_mode())
|
||||
button_stealth.set_foreground(ui::Color::green());
|
||||
|
||||
/*if (!portapack::persistent_memory::ui_config_textentry())
|
||||
button_textentry.set_bitmap(&bitmap_icon_keyboard);
|
||||
else
|
||||
button_textentry.set_bitmap(&bitmap_icon_unistroke);*/
|
||||
|
||||
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 (pmem::should_use_sdcard_for_pmem()) {
|
||||
pmem::save_persistent_settings_to_file();
|
||||
}
|
||||
if (this->on_back)
|
||||
this->on_back();
|
||||
@@ -170,6 +183,10 @@ SystemStatusView::SystemStatusView(
|
||||
this->on_speaker();
|
||||
};
|
||||
|
||||
button_mute.on_select = [this](ImageButton&) {
|
||||
this->on_mute();
|
||||
};
|
||||
|
||||
button_stealth.on_select = [this](ImageButton&) {
|
||||
this->on_stealth();
|
||||
};
|
||||
@@ -178,10 +195,6 @@ SystemStatusView::SystemStatusView(
|
||||
this->on_bias_tee();
|
||||
};
|
||||
|
||||
/*button_textentry.on_select = [this](ImageButton&) {
|
||||
this->on_textentry();
|
||||
};*/
|
||||
|
||||
button_camera.on_select = [this](ImageButton&) {
|
||||
this->on_camera();
|
||||
};
|
||||
@@ -196,26 +209,31 @@ SystemStatusView::SystemStatusView(
|
||||
};
|
||||
|
||||
audio::output::update_audio_mute();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::refresh() {
|
||||
if (portapack::persistent_memory::config_hide_converter()) {
|
||||
button_converter.hidden(true);
|
||||
} else {
|
||||
if (portapack::persistent_memory::config_updown_converter()) {
|
||||
button_converter.set_bitmap(&bitmap_icon_downconvert);
|
||||
} else {
|
||||
button_converter.set_bitmap(&bitmap_icon_upconvert);
|
||||
}
|
||||
button_converter.hidden(false);
|
||||
if (portapack::persistent_memory::config_converter()) {
|
||||
button_converter.set_foreground(Color::red());
|
||||
} else {
|
||||
button_converter.set_foreground(Color::light_grey());
|
||||
}
|
||||
}
|
||||
// NB: Order of insertion is the display order Left->Right.
|
||||
// TODO: Might be better to support hide and only add once.
|
||||
status_icons.clear();
|
||||
if (!pmem::ui_hide_camera()) status_icons.add(&button_camera);
|
||||
if (!pmem::ui_hide_sleep()) status_icons.add(&button_sleep);
|
||||
if (!pmem::ui_hide_stealth()) status_icons.add(&button_stealth);
|
||||
if (!pmem::ui_hide_converter()) status_icons.add(&button_converter);
|
||||
if (!pmem::ui_hide_bias_tee()) status_icons.add(&button_bias_tee);
|
||||
if (!pmem::ui_hide_clock()) status_icons.add(&button_clock_status);
|
||||
if (!pmem::ui_hide_mute()) status_icons.add(&button_mute);
|
||||
|
||||
if (portapack::persistent_memory::config_audio_mute()) {
|
||||
// Display "Disable speaker" icon only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker())
|
||||
status_icons.add(&button_speaker);
|
||||
|
||||
if (!pmem::ui_hide_sd_card()) status_icons.add(&sd_card_status_view);
|
||||
status_icons.update_layout();
|
||||
|
||||
// Update icon display (try to keep all in on place).
|
||||
// Speaker Enable/Disable (AK4951 boards only)
|
||||
if (pmem::config_speaker_disable()) {
|
||||
button_speaker.set_foreground(Color::light_grey());
|
||||
button_speaker.set_bitmap(&bitmap_icon_speaker_mute);
|
||||
} else {
|
||||
@@ -223,25 +241,38 @@ void SystemStatusView::refresh() {
|
||||
button_speaker.set_bitmap(&bitmap_icon_speaker);
|
||||
}
|
||||
|
||||
// Audio Mute (both headphones & speaker)
|
||||
if (pmem::config_audio_mute()) {
|
||||
button_mute.set_foreground(Color::light_grey());
|
||||
button_mute.set_bitmap(&bitmap_icon_speaker_and_headphones_mute);
|
||||
} else {
|
||||
button_mute.set_foreground(Color::green());
|
||||
button_mute.set_bitmap(&bitmap_icon_speaker_and_headphones);
|
||||
}
|
||||
|
||||
// Clock status
|
||||
bool external_clk = portapack::clock_manager.get_reference().source == ClockManager::ReferenceSource::External;
|
||||
button_clock_status.set_bitmap(external_clk ? &bitmap_icon_clk_ext : &bitmap_icon_clk_int);
|
||||
button_clock_status.set_foreground(
|
||||
pmem::clkout_enabled() ? Color::green() : Color::light_grey());
|
||||
|
||||
// Antenna DC Bias
|
||||
if (portapack::get_antenna_bias()) {
|
||||
button_bias_tee.set_bitmap(&bitmap_icon_biast_on);
|
||||
button_bias_tee.set_foreground(ui::Color::yellow());
|
||||
button_bias_tee.set_foreground(Color::yellow());
|
||||
} else {
|
||||
button_bias_tee.set_bitmap(&bitmap_icon_biast_off);
|
||||
button_bias_tee.set_foreground(ui::Color::light_grey());
|
||||
button_bias_tee.set_foreground(Color::light_grey());
|
||||
}
|
||||
|
||||
if (portapack::clock_manager.get_reference().source == ClockManager::ReferenceSource::External) {
|
||||
button_clock_status.set_bitmap(&bitmap_icon_clk_ext);
|
||||
} else {
|
||||
button_clock_status.set_bitmap(&bitmap_icon_clk_int);
|
||||
}
|
||||
// Converter
|
||||
button_converter.set_bitmap(
|
||||
pmem::config_updown_converter() ? &bitmap_icon_downconvert : &bitmap_icon_upconvert);
|
||||
button_converter.set_foreground(pmem::config_converter() ? Color::red() : Color::light_grey());
|
||||
|
||||
if (portapack::persistent_memory::clkout_enabled()) {
|
||||
button_clock_status.set_foreground(ui::Color::green());
|
||||
} else {
|
||||
button_clock_status.set_foreground(ui::Color::light_grey());
|
||||
}
|
||||
// Stealth
|
||||
button_stealth.set_foreground(
|
||||
pmem::stealth_mode() ? Color::green() : Color::light_grey());
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
@@ -266,49 +297,53 @@ void SystemStatusView::set_title(const std::string new_value) {
|
||||
if (new_value.empty()) {
|
||||
title.set(default_title);
|
||||
} else {
|
||||
title.set(new_value);
|
||||
// Limit length of title string to prevent partial characters if too many StatusView icons
|
||||
size_t max_len = (status_icons.parent_rect().left() - title.parent_rect().left()) / 8;
|
||||
title.set(truncate(new_value, max_len));
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::on_converter() {
|
||||
if (!portapack::persistent_memory::config_converter()) {
|
||||
portapack::persistent_memory::set_config_converter(true);
|
||||
button_converter.set_foreground(Color::red());
|
||||
} else {
|
||||
portapack::persistent_memory::set_config_converter(false);
|
||||
button_converter.set_foreground(Color::light_grey());
|
||||
}
|
||||
pmem::set_config_converter(!pmem::config_converter());
|
||||
|
||||
// Poke to update tuning
|
||||
// NOTE: Code assumes here that a TX app isn't active, since RX & TX have diff tuning offsets
|
||||
// (and there's only one tuner in the radio so can't update tuner for both).
|
||||
// TODO: Maybe expose the 'enabled_' flag on models.
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::on_speaker() {
|
||||
portapack::persistent_memory::set_config_audio_mute(!portapack::persistent_memory::config_audio_mute());
|
||||
pmem::set_config_speaker_disable(!pmem::config_speaker_disable());
|
||||
audio::output::update_audio_mute();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::on_mute() {
|
||||
pmem::set_config_audio_mute(!pmem::config_audio_mute());
|
||||
audio::output::update_audio_mute();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::on_stealth() {
|
||||
bool mode = not portapack::persistent_memory::stealth_mode();
|
||||
|
||||
portapack::persistent_memory::set_stealth_mode(mode);
|
||||
|
||||
button_stealth.set_foreground(mode ? Color::green() : Color::light_grey());
|
||||
pmem::set_stealth_mode(!pmem::stealth_mode());
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::on_bias_tee() {
|
||||
if (!portapack::get_antenna_bias()) {
|
||||
nav_.display_modal("Bias voltage", "Enable DC voltage on\nantenna connector?", YESNO, [this](bool v) {
|
||||
if (v) {
|
||||
portapack::set_antenna_bias(true);
|
||||
receiver_model.set_antenna_bias();
|
||||
transmitter_model.set_antenna_bias();
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
nav_.display_modal("Bias voltage",
|
||||
"Enable DC voltage on\nantenna connector?",
|
||||
YESNO,
|
||||
[this](bool v) {
|
||||
if (v) {
|
||||
portapack::set_antenna_bias(true);
|
||||
receiver_model.set_antenna_bias();
|
||||
transmitter_model.set_antenna_bias();
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
portapack::set_antenna_bias(false);
|
||||
receiver_model.set_antenna_bias();
|
||||
@@ -321,48 +356,28 @@ void SystemStatusView::on_bias_tee() {
|
||||
}
|
||||
}
|
||||
|
||||
/*void SystemStatusView::on_textentry() {
|
||||
uint8_t cfg;
|
||||
|
||||
cfg = portapack::persistent_memory::ui_config_textentry();
|
||||
portapack::persistent_memory::set_config_textentry(cfg ^ 1);
|
||||
|
||||
if (!cfg)
|
||||
button_textentry.set_bitmap(&bitmap_icon_unistroke);
|
||||
else
|
||||
button_textentry.set_bitmap(&bitmap_icon_keyboard);
|
||||
}*/
|
||||
|
||||
void SystemStatusView::on_camera() {
|
||||
ensure_directory("SCREENSHOTS");
|
||||
auto path = next_filename_matching_pattern(u"SCREENSHOTS/SCR_????.PNG");
|
||||
|
||||
if (path.empty()) {
|
||||
if (path.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
PNGWriter png;
|
||||
auto create_error = png.create(path);
|
||||
if (create_error.is_valid()) {
|
||||
auto error = png.create(path);
|
||||
if (error)
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 320; i++) {
|
||||
std::array<ColorRGB888, 240> row;
|
||||
portapack::display.read_pixels({0, i, 240, 1}, row);
|
||||
for (int i = 0; i < screen_height; i++) {
|
||||
std::array<ColorRGB888, screen_width> row;
|
||||
portapack::display.read_pixels({0, i, screen_width, 1}, row);
|
||||
png.write_scanline(row);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::on_clk() {
|
||||
bool v = portapack::persistent_memory::clkout_enabled();
|
||||
if (v) {
|
||||
v = false;
|
||||
} else {
|
||||
v = true;
|
||||
}
|
||||
portapack::clock_manager.enable_clock_output(v);
|
||||
portapack::persistent_memory::set_clkout_enabled(v);
|
||||
pmem::set_clkout_enabled(!pmem::clkout_enabled());
|
||||
portapack::clock_manager.enable_clock_output(pmem::clkout_enabled());
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -390,17 +405,17 @@ InformationView::InformationView(
|
||||
|
||||
version.set_style(&style_infobar);
|
||||
|
||||
ltime.set_hide_clock(portapack::persistent_memory::hide_clock());
|
||||
ltime.set_hide_clock(pmem::hide_clock());
|
||||
ltime.set_style(&style_infobar);
|
||||
ltime.set_seconds_enabled(true);
|
||||
ltime.set_date_enabled(portapack::persistent_memory::clock_with_date());
|
||||
ltime.set_date_enabled(pmem::clock_with_date());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void InformationView::refresh() {
|
||||
ltime.set_hide_clock(portapack::persistent_memory::hide_clock());
|
||||
ltime.set_hide_clock(pmem::hide_clock());
|
||||
ltime.set_seconds_enabled(true);
|
||||
ltime.set_date_enabled(portapack::persistent_memory::clock_with_date());
|
||||
ltime.set_date_enabled(pmem::clock_with_date());
|
||||
}
|
||||
|
||||
/* Navigation ************************************************************/
|
||||
@@ -510,36 +525,36 @@ bool NavigationView::set_on_pop(std::function<void()> on_pop) {
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{
|
||||
"ADS-B",
|
||||
ui::Color::green(),
|
||||
Color::green(),
|
||||
&bitmap_icon_adsb,
|
||||
[&nav]() { nav.push<ADSBRxView>(); },
|
||||
},
|
||||
//{ "ACARS", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }, },
|
||||
{"AIS Boats", ui::Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
|
||||
{"AFSK", ui::Color::yellow(), &bitmap_icon_modem, [&nav]() { nav.push<AFSKRxView>(); }},
|
||||
{"BTLE", ui::Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push<BTLERxView>(); }},
|
||||
{"NRF", ui::Color::yellow(), &bitmap_icon_nrf, [&nav]() { nav.push<NRFRxView>(); }},
|
||||
{"Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav]() { nav.push<AnalogAudioView>(); }},
|
||||
{"Analog TV", ui::Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push<AnalogTvView>(); }},
|
||||
{"ERT Meter", ui::Color::green(), &bitmap_icon_ert, [&nav]() { nav.push<ERTAppView>(); }},
|
||||
{"POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGAppView>(); }},
|
||||
{"Radiosnde", ui::Color::green(), &bitmap_icon_sonde, [&nav]() { nav.push<SondeView>(); }},
|
||||
{"TPMS Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
|
||||
{"Recon", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
|
||||
{"Level", ui::Color::green(), &bitmap_icon_options_radio, [&nav]() { nav.push<LevelView>(); }},
|
||||
{"APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSRXView>(); }}
|
||||
//{ "ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }, },
|
||||
{"AIS Boats", Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
|
||||
{"AFSK", Color::yellow(), &bitmap_icon_modem, [&nav]() { nav.push<AFSKRxView>(); }},
|
||||
{"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push<BTLERxView>(); }},
|
||||
{"NRF", Color::yellow(), &bitmap_icon_nrf, [&nav]() { nav.push<NRFRxView>(); }},
|
||||
{"Audio", Color::green(), &bitmap_icon_speaker, [&nav]() { nav.push<AnalogAudioView>(); }},
|
||||
{"Analog TV", Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push<AnalogTvView>(); }},
|
||||
{"ERT Meter", Color::green(), &bitmap_icon_ert, [&nav]() { nav.push<ERTAppView>(); }},
|
||||
{"POCSAG", Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGAppView>(); }},
|
||||
{"Radiosnde", Color::green(), &bitmap_icon_sonde, [&nav]() { nav.push<SondeView>(); }},
|
||||
{"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
|
||||
{"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
|
||||
{"Level", Color::green(), &bitmap_icon_options_radio, [&nav]() { nav.push<LevelView>(); }},
|
||||
{"APRS", Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSRXView>(); }}
|
||||
/*
|
||||
{ "DMR", ui::Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SIGFOX", ui::Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); } }, // SIGFRXView
|
||||
{ "LoRa", ui::Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SSTV", ui::Color::dark_grey(), &bitmap_icon_sstv, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "TETRA", ui::Color::dark_grey(), &bitmap_icon_tetra, [&nav](){ nav.push<NotImplementedView>(); } },*/
|
||||
{ "DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); } }, // SIGFRXView
|
||||
{ "LoRa", Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SSTV", Color::dark_grey(), &bitmap_icon_sstv, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "TETRA", Color::dark_grey(), &bitmap_icon_tetra, [&nav](){ nav.push<NotImplementedView>(); } },*/
|
||||
});
|
||||
|
||||
// set_highlighted(0); // Default selection is "Audio"
|
||||
@@ -548,14 +563,14 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
/* TransmittersMenuView **************************************************/
|
||||
|
||||
TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"ADS-B [S]", ui::Color::yellow(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBTxView>(); }},
|
||||
{"ADS-B [S]", ui::Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBTxView>(); }},
|
||||
{"APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSTXView>(); }},
|
||||
{"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push<BHTView>(); }},
|
||||
{"GPS Sim", ui::Color::yellow(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }},
|
||||
{"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }},
|
||||
{"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push<JammerView>(); }},
|
||||
//{ "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push<KeyfobView>(); } },
|
||||
{"LGE tool", ui::Color::yellow(), &bitmap_icon_lge, [&nav]() { nav.push<LGEView>(); }},
|
||||
@@ -568,9 +583,9 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
{"Soundbrd", ui::Color::green(), &bitmap_icon_soundboard, [&nav]() { nav.push<SoundBoardView>(); }},
|
||||
{"SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav]() { nav.push<SSTVTXView>(); }},
|
||||
{"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }},
|
||||
{"TouchTune", ui::Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<TouchTunesView>(); }},
|
||||
{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"S.Painter", ui::Color::orange(), &bitmap_icon_morse, [&nav]() { nav.push<SpectrumPainterView>(); }},
|
||||
{"TouchTune", ui::Color::green(), &bitmap_icon_touchtunes, [&nav]() { nav.push<TouchTunesView>(); }},
|
||||
//{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"S.Painter", ui::Color::orange(), &bitmap_icon_paint, [&nav]() { nav.push<SpectrumPainterView>(); }},
|
||||
//{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push<RemoteView>(); } },
|
||||
});
|
||||
}
|
||||
@@ -578,22 +593,22 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
/* UtilitiesMenuView *****************************************************/
|
||||
|
||||
UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
|
||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
//{ "Test app", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); } },
|
||||
{"Freq. manager", ui::Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
|
||||
{"File manager", ui::Color::yellow(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
|
||||
{"Notepad", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
|
||||
{"Signal gen", ui::Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
|
||||
//{ "Tone search", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); } },
|
||||
{"Wav viewer", ui::Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }},
|
||||
{"Antenna length", ui::Color::green(), &bitmap_icon_tools_antenna, [&nav]() { nav.push<WhipCalcView>(); }},
|
||||
//{ "Test app", Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); } },
|
||||
{"Freq. manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
|
||||
{"File manager", Color::green(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
|
||||
{"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
|
||||
{"Signal gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
|
||||
//{ "Tone search", Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); } },
|
||||
{"Wav viewer", Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }},
|
||||
{"Antenna length", Color::green(), &bitmap_icon_tools_antenna, [&nav]() { nav.push<WhipCalcView>(); }},
|
||||
|
||||
{"Wipe SD card", ui::Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
|
||||
{"Flash Utility", ui::Color::red(), &bitmap_icon_temperature, [&nav]() { nav.push<FlashUtilityView>(); }},
|
||||
{"SD over USB", ui::Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
|
||||
{"Wipe SD card", Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
|
||||
{"Flash Utility", Color::red(), &bitmap_icon_temperature, [&nav]() { nav.push<FlashUtilityView>(); }},
|
||||
{"SD over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -611,20 +626,20 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) {
|
||||
|
||||
SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
add_items({
|
||||
//{ "Play dead", ui::Color::red(), &bitmap_icon_playdead, [&nav](){ nav.push<PlayDeadView>(); } },
|
||||
{"Receive", ui::Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
|
||||
{"Transmit", ui::Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
|
||||
{"Capture", ui::Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
|
||||
{"Replay", ui::Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<ReplayAppView>(); }},
|
||||
{"Search", ui::Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
|
||||
{"Scanner", ui::Color::yellow(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
|
||||
{"Microphone", ui::Color::yellow(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},
|
||||
{"Looking Glass", ui::Color::yellow(), &bitmap_icon_looking, [&nav]() { nav.push<GlassView>(); }},
|
||||
{"Utilities", ui::Color::cyan(), &bitmap_icon_utilities, [&nav]() { nav.push<UtilitiesMenuView>(); }},
|
||||
{"Settings", ui::Color::cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SettingsMenuView>(); }},
|
||||
{"Debug", ui::Color::light_grey(), &bitmap_icon_debug, [&nav]() { nav.push<DebugMenuView>(); }},
|
||||
{"HackRF", ui::Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }},
|
||||
//{ "About", ui::Color::cyan(), nullptr, [&nav](){ nav.push<AboutView>(); } }
|
||||
//{ "Play dead", Color::red(), &bitmap_icon_playdead, [&nav](){ nav.push<PlayDeadView>(); } },
|
||||
{"Receive", Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
|
||||
{"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
|
||||
{"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
|
||||
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
|
||||
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
|
||||
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},
|
||||
{"Looking Glass", Color::green(), &bitmap_icon_looking, [&nav]() { nav.push<GlassView>(); }},
|
||||
{"Utilities", Color::cyan(), &bitmap_icon_utilities, [&nav]() { nav.push<UtilitiesMenuView>(); }},
|
||||
{"Settings", Color::cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SettingsMenuView>(); }},
|
||||
{"Debug", Color::light_grey(), &bitmap_icon_debug, [&nav]() { nav.push<DebugMenuView>(); }},
|
||||
{"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }},
|
||||
//{ "About", Color::cyan(), nullptr, [&nav](){ nav.push<AboutView>(); } }
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
set_arrow_enabled(false);
|
||||
@@ -638,10 +653,10 @@ SystemView::SystemView(
|
||||
const Rect parent_rect)
|
||||
: View{parent_rect},
|
||||
context_(context) {
|
||||
set_style(&ui::Styles::white);
|
||||
set_style(&Styles::white);
|
||||
|
||||
constexpr ui::Dim status_view_height = 16;
|
||||
constexpr ui::Dim info_view_height = 16;
|
||||
constexpr Dim status_view_height = 16;
|
||||
constexpr Dim info_view_height = 16;
|
||||
|
||||
add_child(&status_view);
|
||||
status_view.set_parent_rect({{0, 0},
|
||||
@@ -652,7 +667,7 @@ SystemView::SystemView(
|
||||
|
||||
add_child(&navigation_view);
|
||||
navigation_view.set_parent_rect({{0, status_view_height},
|
||||
{parent_rect.width(), static_cast<ui::Dim>(parent_rect.height() - status_view_height)}});
|
||||
{parent_rect.width(), static_cast<Dim>(parent_rect.height() - status_view_height)}});
|
||||
|
||||
add_child(&info_view);
|
||||
info_view.set_parent_rect({{0, 19 * 16},
|
||||
@@ -672,17 +687,17 @@ SystemView::SystemView(
|
||||
this->status_view.set_dirty();
|
||||
};
|
||||
|
||||
// portapack::persistent_memory::set_playdead_sequence(0x8D1);
|
||||
// pmem::set_playdead_sequence(0x8D1);
|
||||
|
||||
// Initial view
|
||||
/*if ((portapack::persistent_memory::playing_dead() == 0x5920C1DF) || // Enable code
|
||||
(portapack::persistent_memory::ui_config() & 16)) { // Login option
|
||||
/*if ((pmem::playing_dead() == 0x5920C1DF) || // Enable code
|
||||
(pmem::ui_config() & 16)) { // Login option
|
||||
navigation_view.push<PlayDeadView>();
|
||||
} else {*/
|
||||
|
||||
navigation_view.push<SystemMenuView>();
|
||||
|
||||
if (portapack::persistent_memory::config_splash()) {
|
||||
if (pmem::config_splash()) {
|
||||
navigation_view.push<BMPView>();
|
||||
}
|
||||
status_view.set_back_enabled(false);
|
||||
@@ -699,20 +714,27 @@ Context& SystemView::context() const {
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
if (overlay_active) {
|
||||
this->remove_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
} else {
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
switch (++overlay_active) {
|
||||
case 1:
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
break;
|
||||
case 2:
|
||||
this->remove_child(&this->overlay);
|
||||
this->add_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
break;
|
||||
case 3:
|
||||
this->remove_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
overlay_active = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
overlay_active = !overlay_active;
|
||||
}
|
||||
|
||||
void SystemView::paint_overlay() {
|
||||
@@ -723,7 +745,10 @@ void SystemView::paint_overlay() {
|
||||
return;
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
this->overlay.set_dirty();
|
||||
if (overlay_active == 1)
|
||||
this->overlay.set_dirty();
|
||||
else
|
||||
this->overlay2.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,32 @@ class NavigationView : public View {
|
||||
View* push_view(std::unique_ptr<View> new_view);
|
||||
};
|
||||
|
||||
/* Holds widgets and grows dynamically toward the left.
|
||||
* 16px tall fixed and right-aligns all children in the
|
||||
* order in which they were added. */
|
||||
// TODO: Could make this a generic "StackPanel" control.
|
||||
class StatusTray : public View {
|
||||
public:
|
||||
StatusTray(Point pos);
|
||||
|
||||
StatusTray(const StatusTray&) = delete;
|
||||
StatusTray& operator=(const StatusTray&) = delete;
|
||||
|
||||
void add_button(ImageButton* child);
|
||||
void add(Widget* child);
|
||||
void update_layout();
|
||||
void clear();
|
||||
void paint(Painter& painter) override;
|
||||
uint8_t width() { return width_; };
|
||||
|
||||
private:
|
||||
static constexpr uint8_t height = 16;
|
||||
// This control grow to the left, so keep
|
||||
// track of the right edge.
|
||||
const Point pos_{};
|
||||
uint8_t width_{};
|
||||
};
|
||||
|
||||
class SystemStatusView : public View {
|
||||
public:
|
||||
std::function<void(void)> on_back{};
|
||||
@@ -136,7 +162,7 @@ class SystemStatusView : public View {
|
||||
NavigationView& nav_;
|
||||
|
||||
Rectangle backdrop{
|
||||
{0 * 8, 0 * 16, 240, 16},
|
||||
{0 * 8, 0 * 16, ui::screen_width, 16},
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_back{
|
||||
@@ -156,63 +182,65 @@ class SystemStatusView : public View {
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
StatusTray status_icons{{screen_width, 0}};
|
||||
|
||||
// TODO: Convert to ImageToggle buttons.
|
||||
ImageButton button_speaker{
|
||||
{15 * 8, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker_mute,
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_mute{
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker_and_headphones,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_converter{
|
||||
{17 * 8, 0, 2 * 8, 1 * 16},
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_upconvert,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_stealth{
|
||||
{19 * 8, 0, 2 * 8, 1 * 16},
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_stealth,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
/*ImageButton button_textentry {
|
||||
{ 170, 0, 2 * 8, 1 * 16 },
|
||||
&bitmap_icon_unistroke,
|
||||
Color::white(),
|
||||
Color::dark_grey()
|
||||
};*/
|
||||
|
||||
ImageButton button_camera{
|
||||
{21 * 8, 0, 2 * 8, 1 * 16},
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_camera,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_sleep{
|
||||
{23 * 8, 0, 2 * 8, 1 * 16},
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_sleep,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_bias_tee{
|
||||
{25 * 8, 0, 12, 1 * 16},
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_biast_off,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_clock_status{
|
||||
{27 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
{0, 0 * 16, 8, 1 * 16},
|
||||
&bitmap_icon_clk_int,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
SDCardStatusView sd_card_status_view{
|
||||
{28 * 8, 0 * 16, 2 * 8, 1 * 16}};
|
||||
{0, 0 * 16, 2 * 8, 1 * 16}};
|
||||
|
||||
void on_converter();
|
||||
void on_speaker();
|
||||
void on_mute();
|
||||
void on_stealth();
|
||||
void on_bias_tee();
|
||||
// void on_textentry();
|
||||
void on_camera();
|
||||
void on_title();
|
||||
void refresh();
|
||||
@@ -300,11 +328,12 @@ class SystemView : public View {
|
||||
void paint_overlay();
|
||||
|
||||
private:
|
||||
bool overlay_active{false};
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
SystemStatusView status_view{navigation_view};
|
||||
InformationView info_view{navigation_view};
|
||||
DfuMenu overlay{navigation_view};
|
||||
DfuMenu2 overlay2{navigation_view};
|
||||
NavigationView navigation_view{};
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
@@ -837,6 +837,10 @@ class AK4951 : public audio::Codec {
|
||||
void speaker_enable();
|
||||
void speaker_disable();
|
||||
|
||||
bool speaker_disable_supported() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_headphone_volume(const volume_t volume) override;
|
||||
void headphone_mute();
|
||||
|
||||
|
||||
@@ -34,6 +34,17 @@ class Optional {
|
||||
constexpr Optional(T&& value)
|
||||
: value_{std::move(value)}, valid_{true} {}
|
||||
|
||||
constexpr Optional& operator=(const T& value) {
|
||||
value_ = value;
|
||||
valid_ = true;
|
||||
return *this;
|
||||
}
|
||||
constexpr Optional& operator=(T&& value) {
|
||||
value_ = std::move(value);
|
||||
valid_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_valid() const { return valid_; }
|
||||
operator bool() const { return valid_; }
|
||||
|
||||
|
||||
@@ -89,26 +89,26 @@ enum data_structure_version_enum : uint32_t {
|
||||
|
||||
static const uint32_t TOUCH_CALIBRATION_MAGIC = 0x074af82f;
|
||||
|
||||
enum bits_t {
|
||||
BacklightTimeoutLSB = 0,
|
||||
BacklightTimeoutEnable = 3,
|
||||
ClkoutFreqLSB = 4,
|
||||
ShowGUIReturnIcon = 20,
|
||||
LoadAppSettings = 21,
|
||||
SaveAppSettings = 22,
|
||||
ShowBiggerQRCode = 23,
|
||||
DisableTouchscreen = 24,
|
||||
HideClock = 25,
|
||||
ClockWithDate = 26,
|
||||
ClkOutEnabled = 27,
|
||||
ConfigSpeakerHidden = 28, // unused since Speaker icon modifications
|
||||
StealthMode = 29,
|
||||
ConfigLogin = 30,
|
||||
ConfigSplash = 31,
|
||||
};
|
||||
|
||||
struct ui_config_t {
|
||||
private:
|
||||
enum bits_t {
|
||||
BacklightTimeoutLSB = 0,
|
||||
BacklightTimeoutEnable = 3,
|
||||
ClkoutFreqLSB = 4,
|
||||
ShowGUIReturnIcon = 20,
|
||||
LoadAppSettings = 21,
|
||||
SaveAppSettings = 22,
|
||||
ShowBiggerQRCode = 23,
|
||||
DisableTouchscreen = 24,
|
||||
HideClock = 25,
|
||||
ClockWithDate = 26,
|
||||
ClkOutEnabled = 27,
|
||||
UNUSED = 28,
|
||||
StealthMode = 29,
|
||||
ConfigLogin = 30,
|
||||
ConfigSplash = 31,
|
||||
};
|
||||
|
||||
enum bits_mask_t : uint32_t {
|
||||
BacklightTimeoutMask = ((1 << 3) - 1) << bits_t::BacklightTimeoutLSB,
|
||||
ClkoutFreqMask = ((1 << 16) - 1) << bits_t::ClkoutFreqLSB,
|
||||
@@ -252,6 +252,27 @@ struct ui_config_t {
|
||||
}
|
||||
};
|
||||
|
||||
/* Additional UI config.
|
||||
* NB: Will be default init - override in defaults(). */
|
||||
struct ui_config2_t {
|
||||
/* Top icon bar */
|
||||
bool hide_speaker : 1;
|
||||
bool hide_converter : 1;
|
||||
bool hide_stealth : 1;
|
||||
bool hide_camera : 1;
|
||||
bool hide_sleep : 1;
|
||||
bool hide_bias_tee : 1;
|
||||
bool hide_clock : 1;
|
||||
bool hide_sd_card : 1;
|
||||
|
||||
bool hide_mute : 1;
|
||||
bool UNUSED : 7;
|
||||
|
||||
uint8_t placeholder_1;
|
||||
uint8_t placeholder_2;
|
||||
};
|
||||
static_assert(sizeof(ui_config2_t) == sizeof(uint32_t));
|
||||
|
||||
struct misc_config_t {
|
||||
private:
|
||||
enum bits_t {
|
||||
@@ -321,7 +342,7 @@ struct data_t {
|
||||
uint32_t playing_dead;
|
||||
uint32_t playdead_sequence;
|
||||
|
||||
// UI
|
||||
// UI Config
|
||||
ui_config_t ui_config;
|
||||
|
||||
uint32_t pocsag_last_address;
|
||||
@@ -335,8 +356,8 @@ struct data_t {
|
||||
// Recon App
|
||||
uint64_t recon_config;
|
||||
|
||||
// converter: show or hide icon. Hiding cause auto disable to avoid mistakes
|
||||
bool hide_converter;
|
||||
bool placeholder_0;
|
||||
|
||||
// enable or disable converter
|
||||
bool converter;
|
||||
// set up converter (false) or down converter (true) converter
|
||||
@@ -353,12 +374,15 @@ struct data_t {
|
||||
// Rotary encoder dial sensitivity (encoder.cpp/hpp)
|
||||
uint8_t encoder_dial_sensitivity;
|
||||
|
||||
// Headphone volume in centibels.
|
||||
// Headphone volume in centibels. (Only really needs 10 bits)
|
||||
int32_t headphone_volume_cb;
|
||||
|
||||
// Misc flags
|
||||
misc_config_t misc_config;
|
||||
|
||||
// Additional UI settings.
|
||||
ui_config2_t ui_config2;
|
||||
|
||||
constexpr data_t()
|
||||
: structure_version(data_structure_version_enum::VERSION_CURRENT),
|
||||
target_frequency(target_frequency_reset_value),
|
||||
@@ -387,7 +411,7 @@ struct data_t {
|
||||
|
||||
hardware_config(0),
|
||||
recon_config(0),
|
||||
hide_converter(0),
|
||||
placeholder_0(0),
|
||||
converter(0),
|
||||
updown_converter(0),
|
||||
converter_frequency_offset(0),
|
||||
@@ -397,7 +421,8 @@ struct data_t {
|
||||
updown_frequency_tx_correction(0),
|
||||
encoder_dial_sensitivity(0),
|
||||
headphone_volume_cb(-600),
|
||||
misc_config() {
|
||||
misc_config(),
|
||||
ui_config2() {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -470,6 +495,11 @@ struct backup_ram_t {
|
||||
static_assert(sizeof(backup_ram_t) == memory::map::backup_ram.size());
|
||||
static_assert(sizeof(data_t) <= sizeof(backup_ram_t) - sizeof(uint32_t));
|
||||
|
||||
/* Uncomment to get a compiler error with the data_t size. */
|
||||
// template <size_t N>
|
||||
// struct ShowSize;
|
||||
// ShowSize<sizeof(data_t)> __data_t_size;
|
||||
|
||||
static backup_ram_t* const backup_ram = reinterpret_cast<backup_ram_t*>(memory::map::backup_ram.base());
|
||||
|
||||
static backup_ram_t cached_backup_ram;
|
||||
@@ -479,9 +509,6 @@ 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);
|
||||
@@ -745,18 +772,6 @@ void set_config_backlight_timer(const backlight_config_t& new_value) {
|
||||
data->ui_config.set_config_backlight_timer(new_value);
|
||||
}
|
||||
|
||||
/*void set_config_textentry(uint8_t new_value) {
|
||||
data->ui_config = (data->ui_config & ~0b100) | ((new_value & 1) << 2);
|
||||
}
|
||||
|
||||
uint8_t ui_config_textentry() {
|
||||
return ((data->ui_config >> 2) & 1);
|
||||
}*/
|
||||
|
||||
/*void set_ui_config(const uint32_t new_value) {
|
||||
data->ui_config = new_value;
|
||||
}*/
|
||||
|
||||
uint32_t pocsag_last_address() {
|
||||
return data->pocsag_last_address;
|
||||
}
|
||||
@@ -781,6 +796,7 @@ void set_clkout_freq(uint32_t freq) {
|
||||
data->ui_config.set_clkout_freq(freq);
|
||||
}
|
||||
|
||||
/* Recon app */
|
||||
bool recon_autosave_freqs() {
|
||||
return (data->recon_config & 0x80000000UL) ? true : false;
|
||||
}
|
||||
@@ -808,6 +824,9 @@ bool recon_load_hamradios() {
|
||||
bool recon_match_mode() {
|
||||
return (data->recon_config & 0x00800000UL) ? true : false;
|
||||
}
|
||||
bool recon_auto_record_locked() {
|
||||
return (data->recon_config & 0x00400000UL) ? true : false;
|
||||
}
|
||||
|
||||
void set_recon_autosave_freqs(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x80000000UL) | (v << 31);
|
||||
@@ -836,9 +855,70 @@ void set_recon_load_hamradios(const bool v) {
|
||||
void set_recon_match_mode(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x00800000UL) | (v << 23);
|
||||
}
|
||||
bool config_hide_converter() {
|
||||
return data->hide_converter;
|
||||
void set_recon_auto_record_locked(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x00400000UL) | (v << 22);
|
||||
}
|
||||
|
||||
/* UI Config 2 */
|
||||
bool ui_hide_speaker() {
|
||||
return data->ui_config2.hide_speaker;
|
||||
}
|
||||
bool ui_hide_mute() {
|
||||
return data->ui_config2.hide_mute;
|
||||
}
|
||||
bool ui_hide_converter() {
|
||||
return data->ui_config2.hide_converter;
|
||||
}
|
||||
bool ui_hide_stealth() {
|
||||
return data->ui_config2.hide_stealth;
|
||||
}
|
||||
bool ui_hide_camera() {
|
||||
return data->ui_config2.hide_camera;
|
||||
}
|
||||
bool ui_hide_sleep() {
|
||||
return data->ui_config2.hide_sleep;
|
||||
}
|
||||
bool ui_hide_bias_tee() {
|
||||
return data->ui_config2.hide_bias_tee;
|
||||
}
|
||||
bool ui_hide_clock() {
|
||||
return data->ui_config2.hide_clock;
|
||||
}
|
||||
bool ui_hide_sd_card() {
|
||||
return data->ui_config2.hide_sd_card;
|
||||
}
|
||||
|
||||
void set_ui_hide_speaker(bool v) {
|
||||
data->ui_config2.hide_speaker = v;
|
||||
}
|
||||
void set_ui_hide_mute(bool v) {
|
||||
data->ui_config2.hide_mute = v;
|
||||
}
|
||||
void set_ui_hide_converter(bool v) {
|
||||
data->ui_config2.hide_converter = v;
|
||||
if (v)
|
||||
data->converter = false;
|
||||
}
|
||||
void set_ui_hide_stealth(bool v) {
|
||||
data->ui_config2.hide_stealth = v;
|
||||
}
|
||||
void set_ui_hide_camera(bool v) {
|
||||
data->ui_config2.hide_camera = v;
|
||||
}
|
||||
void set_ui_hide_sleep(bool v) {
|
||||
data->ui_config2.hide_sleep = v;
|
||||
}
|
||||
void set_ui_hide_bias_tee(bool v) {
|
||||
data->ui_config2.hide_bias_tee = v;
|
||||
}
|
||||
void set_ui_hide_clock(bool v) {
|
||||
data->ui_config2.hide_clock = v;
|
||||
}
|
||||
void set_ui_hide_sd_card(bool v) {
|
||||
data->ui_config2.hide_sd_card = v;
|
||||
}
|
||||
|
||||
/* Converter */
|
||||
bool config_converter() {
|
||||
return data->converter;
|
||||
}
|
||||
@@ -849,12 +929,6 @@ int64_t config_converter_freq() {
|
||||
return data->converter_frequency_offset;
|
||||
}
|
||||
|
||||
void set_config_hide_converter(bool v) {
|
||||
data->hide_converter = v;
|
||||
if (v) {
|
||||
data->converter = false;
|
||||
}
|
||||
}
|
||||
void set_config_converter(bool v) {
|
||||
data->converter = v;
|
||||
}
|
||||
@@ -865,25 +939,26 @@ void set_config_converter_freq(int64_t v) {
|
||||
data->converter_frequency_offset = v;
|
||||
}
|
||||
|
||||
// frequency correction settings
|
||||
// Frequency correction settings
|
||||
|
||||
bool config_freq_tx_correction_updown() {
|
||||
return data->updown_frequency_tx_correction;
|
||||
}
|
||||
void set_freq_tx_correction_updown(bool v) {
|
||||
data->updown_frequency_tx_correction = v;
|
||||
}
|
||||
bool config_freq_rx_correction_updown() {
|
||||
return data->updown_frequency_rx_correction;
|
||||
}
|
||||
void set_freq_rx_correction_updown(bool v) {
|
||||
data->updown_frequency_rx_correction = v;
|
||||
}
|
||||
uint32_t config_freq_tx_correction() {
|
||||
return data->frequency_tx_correction;
|
||||
}
|
||||
uint32_t config_freq_rx_correction() {
|
||||
return data->frequency_rx_correction;
|
||||
}
|
||||
void set_freq_tx_correction_updown(bool v) {
|
||||
data->updown_frequency_tx_correction = v;
|
||||
}
|
||||
void set_freq_rx_correction_updown(bool v) {
|
||||
data->updown_frequency_rx_correction = v;
|
||||
}
|
||||
void set_config_freq_tx_correction(uint32_t v) {
|
||||
data->frequency_tx_correction = v;
|
||||
}
|
||||
@@ -891,7 +966,8 @@ void set_config_freq_rx_correction(uint32_t v) {
|
||||
data->frequency_rx_correction = v;
|
||||
}
|
||||
|
||||
// rotary encoder dial settings
|
||||
// Rotary encoder dial settings
|
||||
|
||||
uint8_t config_encoder_dial_sensitivity() {
|
||||
return data->encoder_dial_sensitivity;
|
||||
}
|
||||
@@ -899,11 +975,12 @@ void set_encoder_dial_sensitivity(uint8_t v) {
|
||||
data->encoder_dial_sensitivity = v;
|
||||
}
|
||||
|
||||
// PMem to sdcard settings
|
||||
|
||||
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 = PMEM_SETTING_FILE;
|
||||
delete_file(filename);
|
||||
@@ -927,10 +1004,14 @@ int load_persistent_settings_from_file() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pmem size helper
|
||||
|
||||
size_t data_size() {
|
||||
return sizeof(data_t);
|
||||
}
|
||||
|
||||
// Dump pmem, receiver and transmitter models internals in human readable format
|
||||
|
||||
bool debug_dump() {
|
||||
ui::Painter painter{};
|
||||
std::string debug_dir = "DEBUG";
|
||||
@@ -940,6 +1021,7 @@ bool debug_dump() {
|
||||
make_new_directory(debug_dir);
|
||||
filename = next_filename_matching_pattern(debug_dir + "/DEBUG_DUMP_????.TXT");
|
||||
if (filename.empty()) {
|
||||
painter.draw_string({0, 320 - 16}, ui::Styles::red, "COULD NOT GET DUMP NAME !");
|
||||
return false;
|
||||
}
|
||||
// dump data fo filename
|
||||
@@ -948,11 +1030,13 @@ bool debug_dump() {
|
||||
painter.draw_string({0, 320 - 16}, ui::Styles::red, "ERROR DUMPING " + filename.filename().string() + " !");
|
||||
return false;
|
||||
}
|
||||
pmem_dump_file.write_line("FW version " VERSION_STRING);
|
||||
|
||||
// write persistent memory
|
||||
pmem_dump_file.write_line("[Persistent Memory]");
|
||||
pmem_dump_file.write_line("\n[Persistent Memory]");
|
||||
|
||||
// full variables
|
||||
pmem_dump_file.write_line("structure_version: " + to_string_dec_uint(data->structure_version));
|
||||
pmem_dump_file.write_line("structure_version: 0x" + to_string_hex(data->structure_version, 8));
|
||||
pmem_dump_file.write_line("target_frequency: " + to_string_dec_int(data->target_frequency));
|
||||
pmem_dump_file.write_line("correction_ppb: " + to_string_dec_int(data->correction_ppb));
|
||||
pmem_dump_file.write_line("modem_def_index: " + to_string_dec_uint(data->modem_def_index));
|
||||
@@ -970,17 +1054,20 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("playdead_sequence: " + to_string_dec_uint(data->playdead_sequence));
|
||||
pmem_dump_file.write_line("pocsag_last_address: " + to_string_dec_uint(data->pocsag_last_address));
|
||||
pmem_dump_file.write_line("pocsag_ignore_address: " + to_string_dec_uint(data->pocsag_ignore_address));
|
||||
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
|
||||
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
|
||||
pmem_dump_file.write_line("recon_config: " + to_string_dec_uint(data->recon_config));
|
||||
pmem_dump_file.write_line("hide_converter: " + to_string_dec_int(data->tone_mix));
|
||||
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->tone_mix));
|
||||
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->tone_mix));
|
||||
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("placeholder_0: " + to_string_dec_int(data->placeholder_0));
|
||||
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
|
||||
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
|
||||
pmem_dump_file.write_line("converter_frequency_offset: " + to_string_dec_int(data->converter_frequency_offset));
|
||||
pmem_dump_file.write_line("frequency_rx_correction: " + to_string_dec_uint(data->frequency_rx_correction));
|
||||
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
|
||||
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(data->frequency_tx_correction));
|
||||
pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(data->updown_frequency_tx_correction));
|
||||
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
|
||||
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
|
||||
|
||||
// ui_config bits
|
||||
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
|
||||
pmem_dump_file.write_line("ui_config backlight_timer.timeout_enabled: " + to_string_dec_uint(backlight_timer.timeout_enabled()));
|
||||
@@ -997,12 +1084,23 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("ui_config stealth_mode: " + to_string_dec_uint(data->ui_config.stealth_mode()));
|
||||
pmem_dump_file.write_line("ui_config config_login: " + to_string_dec_uint(data->ui_config.config_login()));
|
||||
pmem_dump_file.write_line("ui_config config_splash: " + to_string_dec_uint(data->ui_config.config_splash()));
|
||||
|
||||
// ui_config2 bits
|
||||
pmem_dump_file.write_line("ui_config2 hide_speaker: " + to_string_dec_uint(data->ui_config2.hide_speaker));
|
||||
pmem_dump_file.write_line("ui_config2 hide_converter: " + to_string_dec_uint(data->ui_config2.hide_converter));
|
||||
pmem_dump_file.write_line("ui_config2 hide_stealth: " + to_string_dec_uint(data->ui_config2.hide_stealth));
|
||||
pmem_dump_file.write_line("ui_config2 hide_camera: " + to_string_dec_uint(data->ui_config2.hide_camera));
|
||||
pmem_dump_file.write_line("ui_config2 hide_sleep: " + to_string_dec_uint(data->ui_config2.hide_sleep));
|
||||
pmem_dump_file.write_line("ui_config2 hide_bias_tee: " + to_string_dec_uint(data->ui_config2.hide_bias_tee));
|
||||
pmem_dump_file.write_line("ui_config2 hide_clock: " + to_string_dec_uint(data->ui_config2.hide_clock));
|
||||
pmem_dump_file.write_line("ui_config2 hide_sd_card: " + to_string_dec_uint(data->ui_config2.hide_sd_card));
|
||||
pmem_dump_file.write_line("ui_config2 hide_mute: " + to_string_dec_uint(data->ui_config2.hide_mute));
|
||||
|
||||
// misc_config bits
|
||||
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
|
||||
pmem_dump_file.write_line("misc_config config_speaker_disable: " + to_string_dec_int(config_speaker_disable()));
|
||||
|
||||
// receiver_model
|
||||
pmem_dump_file.write_line("[Receiver Model]");
|
||||
pmem_dump_file.write_line("\n[Receiver Model]");
|
||||
pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(receiver_model.target_frequency()));
|
||||
pmem_dump_file.write_line("frequency_step: " + to_string_dec_uint(receiver_model.frequency_step()));
|
||||
pmem_dump_file.write_line("lna: " + to_string_dec_int(receiver_model.lna()));
|
||||
@@ -1030,13 +1128,14 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("modulation: !!unknown mode!!");
|
||||
break;
|
||||
}
|
||||
pmem_dump_file.write_line("headphone_volume.centibel: " + to_string_dec_uint(receiver_model.headphone_volume().centibel()));
|
||||
pmem_dump_file.write_line("headphone_volume.centibel: " + to_string_dec_int(receiver_model.headphone_volume().centibel()));
|
||||
pmem_dump_file.write_line("normalized_headphone_volume: " + to_string_dec_uint(receiver_model.normalized_headphone_volume()));
|
||||
pmem_dump_file.write_line("am_configuration: " + to_string_dec_uint(receiver_model.am_configuration()));
|
||||
pmem_dump_file.write_line("nbfm_configuration: " + to_string_dec_uint(receiver_model.nbfm_configuration()));
|
||||
pmem_dump_file.write_line("wfm_configuration: " + to_string_dec_uint(receiver_model.wfm_configuration()));
|
||||
|
||||
// transmitter_model
|
||||
pmem_dump_file.write_line("[Transmitter Model]");
|
||||
pmem_dump_file.write_line("\n[Transmitter Model]");
|
||||
pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(transmitter_model.target_frequency()));
|
||||
pmem_dump_file.write_line("lna: " + to_string_dec_int(transmitter_model.lna()));
|
||||
pmem_dump_file.write_line("vga: " + to_string_dec_int(transmitter_model.vga()));
|
||||
@@ -1045,9 +1144,8 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("sampling_rate: " + to_string_dec_uint(transmitter_model.sampling_rate()));
|
||||
pmem_dump_file.write_line("tx_gain: " + to_string_dec_int(transmitter_model.tx_gain()));
|
||||
pmem_dump_file.write_line("channel_bandwidth: " + to_string_dec_uint(transmitter_model.channel_bandwidth()));
|
||||
|
||||
// on screen information
|
||||
painter.draw_string({0, 320 - 16}, ui::Styles::green, filename.filename().string() + " DUMPED !");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,6 @@ uint8_t config_cpld();
|
||||
void set_config_cpld(uint8_t i);
|
||||
|
||||
bool config_splash();
|
||||
bool config_hide_converter();
|
||||
bool config_converter();
|
||||
bool config_updown_converter();
|
||||
int64_t config_converter_freq();
|
||||
@@ -194,11 +193,9 @@ void set_load_app_settings(bool v);
|
||||
void set_save_app_settings(bool v);
|
||||
void set_show_bigger_qr_code(bool v);
|
||||
void set_config_splash(bool v);
|
||||
bool config_hide_converter();
|
||||
bool config_converter();
|
||||
bool config_updown_converter();
|
||||
int64_t config_converter_freq();
|
||||
void set_config_hide_converter(bool v);
|
||||
void set_config_converter(bool v);
|
||||
void set_config_updown_converter(bool v);
|
||||
void set_config_converter_freq(int64_t v);
|
||||
@@ -220,9 +217,6 @@ void set_disable_touchscreen(bool v);
|
||||
uint8_t config_encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
|
||||
// uint8_t ui_config_textentry();
|
||||
// void set_config_textentry(uint8_t new_value);
|
||||
|
||||
uint32_t pocsag_last_address();
|
||||
void set_pocsag_last_address(uint32_t address);
|
||||
|
||||
@@ -242,6 +236,7 @@ bool recon_clear_output();
|
||||
bool recon_load_freqs();
|
||||
bool recon_load_ranges();
|
||||
bool recon_update_ranges_when_recon();
|
||||
bool recon_auto_record_locked();
|
||||
bool recon_load_hamradios();
|
||||
bool recon_match_mode();
|
||||
void set_recon_autosave_freqs(const bool v);
|
||||
@@ -251,9 +246,30 @@ void set_recon_clear_output(const bool v);
|
||||
void set_recon_load_freqs(const bool v);
|
||||
void set_recon_load_ranges(const bool v);
|
||||
void set_recon_update_ranges_when_recon(const bool v);
|
||||
void set_recon_auto_record_locked(const bool v);
|
||||
void set_recon_load_hamradios(const bool v);
|
||||
void set_recon_match_mode(const bool v);
|
||||
|
||||
/* UI Config 2 */
|
||||
bool ui_hide_speaker();
|
||||
bool ui_hide_mute();
|
||||
bool ui_hide_converter();
|
||||
bool ui_hide_stealth();
|
||||
bool ui_hide_camera();
|
||||
bool ui_hide_sleep();
|
||||
bool ui_hide_bias_tee();
|
||||
bool ui_hide_clock();
|
||||
bool ui_hide_sd_card();
|
||||
void set_ui_hide_speaker(bool v);
|
||||
void set_ui_hide_mute(bool v);
|
||||
void set_ui_hide_converter(bool v);
|
||||
void set_ui_hide_stealth(bool v);
|
||||
void set_ui_hide_camera(bool v);
|
||||
void set_ui_hide_sleep(bool v);
|
||||
void set_ui_hide_bias_tee(bool v);
|
||||
void set_ui_hide_clock(bool v);
|
||||
void set_ui_hide_sd_card(bool v);
|
||||
|
||||
// sd persisting settings
|
||||
bool should_use_sdcard_for_pmem();
|
||||
int save_persistent_settings_to_file();
|
||||
|
||||
+15
-4
@@ -31,6 +31,9 @@ namespace ui {
|
||||
using Coord = int16_t;
|
||||
using Dim = int16_t;
|
||||
|
||||
constexpr uint16_t screen_width = 240;
|
||||
constexpr uint16_t screen_height = 320;
|
||||
|
||||
struct Color {
|
||||
uint16_t v; // rrrrrGGGGGGbbbbb
|
||||
|
||||
@@ -53,15 +56,20 @@ struct Color {
|
||||
}
|
||||
|
||||
uint8_t to_greyscale() {
|
||||
uint32_t r = ((v >> 11) & 31U) << 3;
|
||||
uint32_t g = ((v >> 5) & 63U) << 2;
|
||||
uint32_t b = (v & 31U) << 3;
|
||||
uint32_t r = (v >> 8) & 0xf8;
|
||||
uint32_t g = (v >> 3) & 0xfc;
|
||||
uint32_t b = (v << 3) & 0xf8;
|
||||
|
||||
uint32_t grey = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
uint32_t grey = ((r * 306) + (g * 601) + (b * 117)) >> 10;
|
||||
|
||||
return (uint8_t)grey;
|
||||
}
|
||||
|
||||
uint16_t dark() {
|
||||
// stripping bits 4 & 5 from each of the colors R/G/B
|
||||
return (v & ((0xc8 << 8) | (0xcc << 3) | (0xc8 >> 3)));
|
||||
}
|
||||
|
||||
Color operator-() const {
|
||||
return (v ^ 0xffff);
|
||||
}
|
||||
@@ -132,6 +140,9 @@ struct Color {
|
||||
static constexpr Color dark_grey() {
|
||||
return {63, 63, 63};
|
||||
}
|
||||
static constexpr Color darker_grey() {
|
||||
return {31, 31, 31};
|
||||
}
|
||||
|
||||
static constexpr Color purple() {
|
||||
return {204, 0, 102};
|
||||
|
||||
@@ -79,6 +79,10 @@ int Painter::draw_string(
|
||||
}
|
||||
|
||||
void Painter::draw_bitmap(Point p, const Bitmap& bitmap, Color foreground, Color background) {
|
||||
// If bright foreground colors on white background, darken the foreground color to improve visibility
|
||||
if ((background.v == ui::Color::white().v) && (foreground.to_greyscale() > 146))
|
||||
foreground = foreground.dark();
|
||||
|
||||
display.draw_bitmap(p, bitmap.size, bitmap.data, foreground, background);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ Dim Font::line_height() const {
|
||||
return h;
|
||||
}
|
||||
|
||||
Dim Font::char_width() const {
|
||||
return w;
|
||||
}
|
||||
|
||||
Size Font::size_of(const std::string s) const {
|
||||
Size size;
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ class Font {
|
||||
Glyph glyph(const char c) const;
|
||||
|
||||
Dim line_height() const;
|
||||
Dim char_width() const;
|
||||
|
||||
Size size_of(const std::string s) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -177,6 +177,8 @@ const std::vector<Widget*>& Widget::children() const {
|
||||
}
|
||||
|
||||
Context& Widget::context() const {
|
||||
chDbgAssert(parent_, "parent_ is null",
|
||||
"Check that parent isn't null before deref.");
|
||||
return parent()->context();
|
||||
}
|
||||
|
||||
@@ -1323,6 +1325,68 @@ bool ImageButton::on_touch(const TouchEvent event) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ImageToggle ***********************************************************/
|
||||
ImageToggle::ImageToggle(
|
||||
Rect parent_rect,
|
||||
const Bitmap* bitmap_)
|
||||
: ImageToggle{parent_rect,
|
||||
bitmap_,
|
||||
Color::green(),
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()} {}
|
||||
|
||||
ImageToggle::ImageToggle(
|
||||
Rect parent_rect,
|
||||
const Bitmap* bitmap_,
|
||||
Color foreground_true,
|
||||
Color foreground_false,
|
||||
Color background_)
|
||||
: ImageToggle{parent_rect,
|
||||
bitmap_,
|
||||
bitmap_,
|
||||
foreground_true,
|
||||
background_,
|
||||
foreground_false,
|
||||
background_} {}
|
||||
|
||||
ImageToggle::ImageToggle(
|
||||
Rect parent_rect,
|
||||
const Bitmap* bitmap_true,
|
||||
const Bitmap* bitmap_false,
|
||||
Color foreground_true,
|
||||
Color background_true,
|
||||
Color foreground_false,
|
||||
Color background_false)
|
||||
: ImageButton{parent_rect, bitmap_false, foreground_false, background_false},
|
||||
bitmap_true_{bitmap_true},
|
||||
bitmap_false_{bitmap_false},
|
||||
foreground_true_{foreground_true},
|
||||
background_true_{background_true},
|
||||
foreground_false_{foreground_false},
|
||||
background_false_{background_false},
|
||||
value_{false} {
|
||||
ImageButton::on_select = [this](ImageButton&) {
|
||||
set_value(!value());
|
||||
};
|
||||
}
|
||||
|
||||
bool ImageToggle::value() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
void ImageToggle::set_value(bool b) {
|
||||
if (b == value_)
|
||||
return;
|
||||
|
||||
value_ = b;
|
||||
set_bitmap(b ? bitmap_true_ : bitmap_false_);
|
||||
set_foreground(b ? foreground_true_ : foreground_false_);
|
||||
set_background(b ? background_true_ : background_false_);
|
||||
|
||||
if (on_change)
|
||||
on_change(b);
|
||||
}
|
||||
|
||||
/* ImageOptionsField *****************************************************/
|
||||
|
||||
ImageOptionsField::ImageOptionsField(
|
||||
|
||||
@@ -448,7 +448,7 @@ class ButtonWithEncoder : public Widget {
|
||||
class NewButton : public Widget {
|
||||
public:
|
||||
std::function<void(void)> on_select{};
|
||||
// std::function<void(NewButton&)> on_select { };
|
||||
// std::function<void(NewButton&)> on_select{};
|
||||
std::function<bool(NewButton&, KeyEvent)> on_dir{};
|
||||
std::function<void(NewButton&)> on_highlight{};
|
||||
|
||||
@@ -500,6 +500,8 @@ class Image : public Widget {
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
const Bitmap& bitmap() & { return *bitmap_; }
|
||||
|
||||
private:
|
||||
const Bitmap* bitmap_;
|
||||
Color foreground_;
|
||||
@@ -520,6 +522,49 @@ class ImageButton : public Image {
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
};
|
||||
|
||||
/* A button that toggles between two images when set. */
|
||||
class ImageToggle : public ImageButton {
|
||||
public:
|
||||
std::function<void(bool value)> on_change{};
|
||||
|
||||
ImageToggle(
|
||||
Rect parent_rect,
|
||||
const Bitmap* bitmap_);
|
||||
|
||||
ImageToggle(
|
||||
Rect parent_rect,
|
||||
const Bitmap* bitmap_,
|
||||
Color foreground_true,
|
||||
Color foreground_false,
|
||||
Color background_);
|
||||
|
||||
ImageToggle(
|
||||
Rect parent_rect,
|
||||
const Bitmap* bitmap_true,
|
||||
const Bitmap* bitmap_false,
|
||||
Color foreground_true,
|
||||
Color background_true,
|
||||
Color foreground_false,
|
||||
Color background_false);
|
||||
|
||||
ImageToggle(const ImageToggle&) = delete;
|
||||
ImageToggle& operator=(const ImageToggle&) = delete;
|
||||
|
||||
bool value() const;
|
||||
void set_value(bool b);
|
||||
|
||||
private:
|
||||
// Hide this field.
|
||||
using ImageButton::on_select;
|
||||
const Bitmap* bitmap_true_;
|
||||
const Bitmap* bitmap_false_;
|
||||
const Color foreground_true_;
|
||||
const Color background_true_;
|
||||
const Color foreground_false_;
|
||||
const Color background_false_;
|
||||
bool value_;
|
||||
};
|
||||
|
||||
class ImageOptionsField : public Widget {
|
||||
public:
|
||||
using value_t = int32_t;
|
||||
|
||||
@@ -345,6 +345,9 @@ class WM8731 : public audio::Codec {
|
||||
|
||||
void speaker_enable(){};
|
||||
void speaker_disable(){};
|
||||
bool speaker_disable_supported() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void microphone_enable(int8_t wm8731_boost_GUI) override {
|
||||
microphone_mute(true); // c/m to reduce "plop noise" when changing wm8731_boost_GUI.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 252 B |
Binary file not shown.
|
After Width: | Height: | Size: 252 B |
Binary file not shown.
|
After Width: | Height: | Size: 152 B |
Binary file not shown.
|
After Width: | Height: | Size: 183 B |
Binary file not shown.
|
After Width: | Height: | Size: 185 B |
Binary file not shown.
|
After Width: | Height: | Size: 195 B |
@@ -48,7 +48,6 @@ def convert_png(file):
|
||||
name = path.basename(file).split(".")[0].lower();
|
||||
|
||||
f.write("static constexpr uint8_t bitmap_" + name + "_data[] = {\n")
|
||||
f.write(' ') # Tab
|
||||
|
||||
for i in range(rgb_im.size[1]):
|
||||
for j in range(rgb_im.size[0]):
|
||||
@@ -60,17 +59,12 @@ def convert_png(file):
|
||||
data += 128
|
||||
|
||||
if j % 8 == 7:
|
||||
f.write("0x%0.2X, " % data)
|
||||
f.write(" 0x%0.2X,\n" % data)
|
||||
data = 0
|
||||
|
||||
f.write("\n")
|
||||
if i < rgb_im.size[1] - 1:
|
||||
f.write(' ') # Tab
|
||||
|
||||
f.write("};\n")
|
||||
f.write("static constexpr Bitmap bitmap_" + name + " {\n")
|
||||
f.write(" { " + str(rgb_im.size[0]) + ", " + str(rgb_im.size[1]) + " }, bitmap_" + name+ "_data\n")
|
||||
f.write("};\n\n")
|
||||
f.write("static constexpr Bitmap bitmap_" + name + "{\n")
|
||||
f.write(" {" + str(rgb_im.size[0]) + ", " + str(rgb_im.size[1]) + "},\n bitmap_" + name+ "_data};\n\n")
|
||||
return
|
||||
|
||||
count = 0
|
||||
@@ -108,7 +102,6 @@ for file in listdir(sys.argv[1]):
|
||||
convert_png(sys.argv[1] + file)
|
||||
count += 1
|
||||
|
||||
f.write("\n")
|
||||
f.write("} /* namespace ui */\n\n")
|
||||
f.write("#endif/*__BITMAP_HPP__*/\n")
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
f=310000000,m=SPEC,bw=650k
|
||||
f=315000000,m=SPEC,bw=650k
|
||||
f=318000000,m=SPEC,bw=650k
|
||||
f=418000000,m=SPEC,bw=650k
|
||||
f=433920000,m=SPEC,bw=650k
|
||||
f=868350000,m=SPEC,bw=650k
|
||||
@@ -0,0 +1,55 @@
|
||||
f=300000000,m=SPEC,bw=650k,d=300 - 348 01
|
||||
f=302757000,m=SPEC,bw=650k,d=300 - 348 02
|
||||
f=303875000,m=SPEC,bw=650k,d=300 - 348 03
|
||||
f=304250000,m=SPEC,bw=650k,d=300 - 348 04
|
||||
f=307000000,m=SPEC,bw=650k,d=300 - 348 05
|
||||
f=307500000,m=SPEC,bw=650k,d=300 - 348 06
|
||||
f=307800000,m=SPEC,bw=650k,d=300 - 348 07
|
||||
f=309000000,m=SPEC,bw=650k,d=300 - 348 08
|
||||
f=310000000,m=SPEC,bw=650k,d=300 - 348 09
|
||||
f=312000000,m=SPEC,bw=650k,d=300 - 348 10
|
||||
f=312100000,m=SPEC,bw=650k,d=300 - 348 11
|
||||
f=312200000,m=SPEC,bw=650k,d=300 - 348 12
|
||||
f=313000000,m=SPEC,bw=650k,d=300 - 348 13
|
||||
f=313850000,m=SPEC,bw=650k,d=300 - 348 14
|
||||
f=314000000,m=SPEC,bw=650k,d=300 - 348 15
|
||||
f=314350000,m=SPEC,bw=650k,d=300 - 348 16
|
||||
f=314980000,m=SPEC,bw=650k,d=300 - 348 17
|
||||
f=315000000,m=SPEC,bw=650k,d=300 - 348 18
|
||||
f=318000000,m=SPEC,bw=650k,d=300 - 348 19
|
||||
f=330000000,m=SPEC,bw=650k,d=300 - 348 20
|
||||
f=345000000,m=SPEC,bw=650k,d=300 - 348 21
|
||||
f=348000000,m=SPEC,bw=650k,d=300 - 348 22
|
||||
f=350000000,m=SPEC,bw=650k,d=300 - 348 23
|
||||
f=387000000,m=SPEC,bw=650k,d=387 - 464 24
|
||||
f=390000000,m=SPEC,bw=650k,d=387 - 464 25
|
||||
f=418000000,m=SPEC,bw=650k,d=387 - 464 26
|
||||
f=430000000,m=SPEC,bw=650k,d=387 - 464 27
|
||||
f=431000000,m=SPEC,bw=650k,d=387 - 464 28
|
||||
f=431500000,m=SPEC,bw=650k,d=387 - 464 29
|
||||
f=433075000,m=SPEC,bw=650k,d=LPD433 01 FIRST
|
||||
f=433220000,m=SPEC,bw=650k,d=LPD433 02
|
||||
f=433420000,m=SPEC,bw=650k,d=LPD433 03
|
||||
f=433657070,m=SPEC,bw=650k,d=LPD433 04
|
||||
f=433889000,m=SPEC,bw=650k,d=LPD433 05
|
||||
f=433920000,m=SPEC,bw=650k,d=LPD433 06 MIDDLE
|
||||
f=434075000,m=SPEC,bw=650k,d=LPD433 07
|
||||
f=434176948,m=SPEC,bw=650k,d=LPD433 08
|
||||
f=434190000,m=SPEC,bw=650k,d=LPD433 09
|
||||
f=434390000,m=SPEC,bw=650k,d=LPD433 10
|
||||
f=434420000,m=SPEC,bw=650k,d=LPD433 11
|
||||
f=434620000,m=SPEC,bw=650k,d=LPD433 12
|
||||
f=434775000,m=SPEC,bw=650k,d=LPD433 13 LAST CHANS
|
||||
f=438900000,m=SPEC,bw=650k,d=LPD433 14 LAST CHANS
|
||||
f=440175000,m=SPEC,bw=650k,d=LPD433 15 LAST CHANS
|
||||
f=464000000,m=SPEC,bw=650k,d=LPD433 16 LAST CHANS
|
||||
f=467750000,m=SPEC,bw=650k,d=LPD433 17 LAST CHANS
|
||||
f=779000000,m=SPEC,bw=650k,d=779 - 928 01
|
||||
f=868350000,m=SPEC,bw=650k,d=779 - 928 02
|
||||
f=868400000,m=SPEC,bw=650k,d=779 - 928 03
|
||||
f=868800000,m=SPEC,bw=650k,d=779 - 928 04
|
||||
f=868950000,m=SPEC,bw=650k,d=779 - 928 05
|
||||
f=906400000,m=SPEC,bw=650k,d=779 - 928 06
|
||||
f=915000000,m=SPEC,bw=650k,d=779 - 928 07
|
||||
f=925000000,m=SPEC,bw=650k,d=779 - 928 08
|
||||
f=928000000,m=SPEC,bw=650k,d=779 - 928 09
|
||||
@@ -1,2 +0,0 @@
|
||||
315000000,SAMPLES/TeslaChargePort_US.C16,500000,0
|
||||
433920000,SAMPLES/TeslaChargePort_EU_AU.C16,500000,10
|
||||
@@ -0,0 +1,4 @@
|
||||
# Playlist file example
|
||||
# capture path, pre-delay milliseconds (optional)
|
||||
/SAMPLES/TeslaChargePort_US.C16
|
||||
/SAMPLES/TeslaChargePort_EU_AU.C16,100
|
||||
Reference in New Issue
Block a user