mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4df2e66be | |||
| 47e95c0c47 | |||
| 3b5890d0aa | |||
| bee2dc17af | |||
| d6b0173e7a | |||
| 8eafe27955 | |||
| 2af95456a2 | |||
| 8ce48dbcf6 | |||
| 92072b4225 | |||
| 4e823dd3a3 | |||
| 53d9cecdaf | |||
| 680f1f49ff | |||
| 6574272ca8 | |||
| f5c4aa2be2 | |||
| 2498861003 | |||
| 25923e82a4 | |||
| 61cb57e48d | |||
| 4985d836ea | |||
| e1a79a0ff1 | |||
| d72d935607 |
@@ -179,6 +179,7 @@ set(CPPSRC
|
||||
file.cpp
|
||||
freqman_db.cpp
|
||||
freqman.cpp
|
||||
io_convert.cpp
|
||||
io_file.cpp
|
||||
io_wave.cpp
|
||||
irq_controls.cpp
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "capture_app.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -30,7 +31,6 @@ 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({
|
||||
@@ -43,6 +43,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&option_bandwidth,
|
||||
&option_format,
|
||||
&record_view,
|
||||
&waterfall,
|
||||
});
|
||||
@@ -50,28 +51,32 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
field_frequency_step.set_by_value(receiver_model.frequency_step());
|
||||
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_frequency_step(v);
|
||||
this->field_frequency.set_step(v);
|
||||
};
|
||||
|
||||
option_format.set_selected_index(0); // Default to C16
|
||||
option_format.on_change = [this](size_t, uint32_t file_type) {
|
||||
record_view.set_file_type((RecordView::FileType)file_type);
|
||||
};
|
||||
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
|
||||
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* 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();
|
||||
auto sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side.
|
||||
|
||||
/* 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);
|
||||
|
||||
waterfall.stop();
|
||||
record_view.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
|
||||
waterfall.on_show();
|
||||
waterfall.start();
|
||||
};
|
||||
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
receiver_model.enable();
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
@@ -83,11 +88,6 @@ CaptureAppView::~CaptureAppView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void CaptureAppView::on_hide() {
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void CaptureAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -40,7 +39,6 @@ class CaptureAppView : public View {
|
||||
CaptureAppView(NavigationView& nav);
|
||||
~CaptureAppView();
|
||||
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
@@ -55,10 +53,9 @@ class CaptureAppView : public View {
|
||||
"rx_capture", app_settings::Mode::RX,
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
uint32_t sampling_rate = 0;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
|
||||
{{11 * 8, 1 * 16}, "Format:", Color::light_grey()},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
@@ -88,6 +85,12 @@ class CaptureAppView : public View {
|
||||
5,
|
||||
{}};
|
||||
|
||||
OptionsField option_format{
|
||||
{18 * 8, 1 * 16},
|
||||
3,
|
||||
{{"C16", RecordView::FileType::RawS16},
|
||||
{"C8", RecordView::FileType::RawS8}}};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
u"BBD_????.*",
|
||||
|
||||
@@ -65,12 +65,12 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
field_frequency.set_value(metadata->center_frequency);
|
||||
sample_rate = metadata->sample_rate;
|
||||
} else {
|
||||
sample_rate = 500000;
|
||||
sample_rate = 2600000;
|
||||
}
|
||||
|
||||
// UI Fixup.
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
|
||||
progressbar.set_max(file_size / 1024);
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 2);
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "portapack.hpp"
|
||||
@@ -75,7 +77,8 @@ void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 4);
|
||||
uint8_t sample_size = capture_file_sample_size(current()->path);
|
||||
auto duration = ms_duration(file_size, sample_rate, sample_size);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
@@ -110,7 +113,7 @@ void ReplayAppView::start() {
|
||||
|
||||
std::unique_ptr<stream::Reader> reader;
|
||||
|
||||
auto p = std::make_unique<FileReader>();
|
||||
auto p = std::make_unique<FileConvertReader>();
|
||||
auto open_error = p->open(file_path);
|
||||
if (open_error.is_valid()) {
|
||||
file_error();
|
||||
@@ -191,7 +194,7 @@ ReplayAppView::ReplayAppView(
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".C16");
|
||||
auto open_view = nav.push<FileLoadView>(".C*");
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
|
||||
@@ -403,9 +403,10 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
|
||||
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
||||
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
||||
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -501,6 +502,56 @@ DebugFontsView::DebugFontsView(NavigationView& nav)
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
/* DebugScreenTest ****************************************************/
|
||||
|
||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
Painter painter;
|
||||
switch (key) {
|
||||
case KeyEvent::Select:
|
||||
nav_.pop();
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
|
||||
break;
|
||||
case KeyEvent::Left:
|
||||
pen_color = semirand();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_encoder(EncoderEvent delta) {
|
||||
pen_size = clip<int32_t>(pen_size + delta, 1, screen_width);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_touch(const TouchEvent event) {
|
||||
Painter painter;
|
||||
pen_pos = event.point;
|
||||
painter.fill_rectangle({pen_pos.x() - pen_size / 2, pen_pos.y() - pen_size / 2, pen_size, pen_size}, pen_color);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t DebugScreenTest::semirand() {
|
||||
static uint64_t seed{0x31415926DEADBEEF};
|
||||
seed = seed * 137;
|
||||
seed = (seed >> 1) | ((seed & 0x01) << 63);
|
||||
return (uint16_t)seed;
|
||||
}
|
||||
|
||||
void DebugScreenTest::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::white());
|
||||
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
|
||||
pen_color = semirand();
|
||||
}
|
||||
|
||||
/* DebugLCRView *******************************************************/
|
||||
|
||||
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
|
||||
|
||||
@@ -321,6 +321,22 @@ class DebugFontsView : public View {
|
||||
NavigationView& nav_;
|
||||
};
|
||||
|
||||
class DebugScreenTest : public View {
|
||||
public:
|
||||
DebugScreenTest(NavigationView& nav);
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
uint16_t semirand();
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Point pen_pos{};
|
||||
Color pen_color{0};
|
||||
int16_t pen_size{10};
|
||||
};
|
||||
|
||||
/*class DebugLCRView : public View {
|
||||
public:
|
||||
DebugLCRView(NavigationView& nav, std::string lcrstring);
|
||||
|
||||
@@ -39,7 +39,10 @@ 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 c8_ext{u".C8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path c32_ext{u".C32"};
|
||||
static const fs::path cxx_ext{u".C*"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
static const fs::path bmp_ext{u".BMP"};
|
||||
} // namespace ui
|
||||
@@ -78,14 +81,17 @@ fs::path get_partner_file(fs::path path) {
|
||||
return {};
|
||||
auto ext = path.extension();
|
||||
|
||||
if (path_iequal(ext, txt_ext))
|
||||
ext = c16_ext;
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
ext = txt_ext;
|
||||
else
|
||||
if (is_cxx_capture_file(path))
|
||||
path.replace_extension(txt_ext);
|
||||
else if (path_iequal(ext, txt_ext)) {
|
||||
path.replace_extension(c8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(c16_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(c32_ext);
|
||||
} else
|
||||
return {};
|
||||
|
||||
path.replace_extension(ext);
|
||||
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{};
|
||||
}
|
||||
|
||||
@@ -141,6 +147,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
current_path = dir_path;
|
||||
entry_list.clear();
|
||||
auto filtering = !extension_filter.empty();
|
||||
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
@@ -150,7 +157,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
continue;
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
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});
|
||||
@@ -497,7 +504,7 @@ bool FileManagerView::handle_file_open() {
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
return true;
|
||||
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
|
||||
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
nav_.push<PlaylistView>(path);
|
||||
return true;
|
||||
|
||||
@@ -76,6 +76,7 @@ class FileManBaseView : public View {
|
||||
{u".BMP", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".C32", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
|
||||
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // PPL is the file extension for playlist app
|
||||
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
|
||||
|
||||
@@ -26,13 +26,42 @@
|
||||
#include "event_m0.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// TODO: Clean up after moving to better lookup tables.
|
||||
using options_t = OptionsField::options_t;
|
||||
extern options_t freqman_modulations;
|
||||
extern options_t freqman_bandwidths[4];
|
||||
extern options_t freqman_steps;
|
||||
extern options_t freqman_steps_short;
|
||||
|
||||
/* Set options. */
|
||||
void freqman_set_modulation_option(OptionsField& option) {
|
||||
option.set_options(freqman_modulations);
|
||||
}
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||
if (is_valid(modulation))
|
||||
option.set_options(freqman_bandwidths[modulation]);
|
||||
}
|
||||
|
||||
void freqman_set_step_option(OptionsField& option) {
|
||||
option.set_options(freqman_steps);
|
||||
}
|
||||
|
||||
void freqman_set_step_option_short(OptionsField& option) {
|
||||
option.set_options(freqman_steps_short);
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* FreqManBaseView ***************************************/
|
||||
@@ -140,14 +169,14 @@ FrequencySaveView::FrequencySaveView(
|
||||
|
||||
button_edit.on_select = [this, &nav](Button&) {
|
||||
temp_buffer_ = entry_.description;
|
||||
text_prompt(nav_, temp_buffer_, 30, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
|
||||
entry_.description = new_desc;
|
||||
refresh_ui();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&) {
|
||||
db_.insert_entry(entry_, db_.entry_count());
|
||||
db_.insert_entry(db_.entry_count(), entry_);
|
||||
nav_.pop();
|
||||
};
|
||||
}
|
||||
@@ -188,8 +217,15 @@ FrequencyLoadView::FrequencyLoadView(
|
||||
|
||||
/* FrequencyManagerView **********************************/
|
||||
|
||||
void FrequencyManagerView::on_edit_entry() {
|
||||
auto edit_view = nav_.push<FrequencyEditView>(current_entry());
|
||||
edit_view->on_save = [this](const freqman_entry& entry) {
|
||||
db_.replace_entry(current_index(), entry);
|
||||
freqlist_view.set_dirty();
|
||||
};
|
||||
}
|
||||
|
||||
void FrequencyManagerView::on_edit_freq() {
|
||||
// TODO: range edit support?
|
||||
auto freq_edit_view = nav_.push<FrequencyKeypadView>(current_entry().frequency_a);
|
||||
freq_edit_view->on_changed = [this](rf::Frequency f) {
|
||||
auto entry = current_entry();
|
||||
@@ -201,7 +237,7 @@ void FrequencyManagerView::on_edit_freq() {
|
||||
|
||||
void FrequencyManagerView::on_edit_desc() {
|
||||
temp_buffer_ = current_entry().description;
|
||||
text_prompt(nav_, temp_buffer_, 28, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
|
||||
auto entry = current_entry();
|
||||
entry.description = std::move(new_desc);
|
||||
db_.replace_entry(current_index(), entry);
|
||||
@@ -211,7 +247,7 @@ void FrequencyManagerView::on_edit_desc() {
|
||||
|
||||
void FrequencyManagerView::on_add_category() {
|
||||
temp_buffer_.clear();
|
||||
text_prompt(nav_, temp_buffer_, 12, [this](std::string& new_name) {
|
||||
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
|
||||
if (!new_name.empty()) {
|
||||
create_freqman_file(new_name);
|
||||
refresh_categories();
|
||||
@@ -240,7 +276,7 @@ void FrequencyManagerView::on_add_entry() {
|
||||
};
|
||||
|
||||
// Add will insert below the currently selected item.
|
||||
db_.insert_entry(entry, current_index() + 1);
|
||||
db_.insert_entry(current_index() + 1, entry);
|
||||
refresh_list(1);
|
||||
}
|
||||
|
||||
@@ -249,7 +285,7 @@ void FrequencyManagerView::on_del_entry() {
|
||||
return;
|
||||
|
||||
nav_.push<ModalMessageView>(
|
||||
"Delete", "Delete" + pretty_string(current_entry(), 23) + "\nAre you sure?", YESNO,
|
||||
"Delete", "Delete " + trim(pretty_string(current_entry(), 23)) + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
db_.delete_entry(current_index());
|
||||
@@ -263,20 +299,21 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
: FreqManBaseView(nav) {
|
||||
add_children(
|
||||
{&freqlist_view,
|
||||
&labels,
|
||||
&button_add_category,
|
||||
&button_del_category,
|
||||
&button_edit_entry,
|
||||
&rect_padding,
|
||||
&button_edit_freq,
|
||||
&button_edit_desc,
|
||||
&button_add_entry,
|
||||
&button_del_entry});
|
||||
|
||||
freqlist_view.on_select = [this](size_t) {
|
||||
button_edit_freq.focus();
|
||||
button_edit_entry.focus();
|
||||
};
|
||||
// Allows for quickly exiting control.
|
||||
freqlist_view.on_leave = [this]() {
|
||||
button_edit_freq.focus();
|
||||
button_edit_entry.focus();
|
||||
};
|
||||
|
||||
button_add_category.on_select = [this]() {
|
||||
@@ -287,6 +324,10 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
on_del_category();
|
||||
};
|
||||
|
||||
button_edit_entry.on_select = [this](Button&) {
|
||||
on_edit_entry();
|
||||
};
|
||||
|
||||
button_edit_freq.on_select = [this](Button&) {
|
||||
on_edit_freq();
|
||||
};
|
||||
@@ -304,4 +345,180 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
};
|
||||
}
|
||||
|
||||
/* FrequencyEditView *************************************/
|
||||
|
||||
FrequencyEditView::FrequencyEditView(
|
||||
NavigationView& nav,
|
||||
freqman_entry entry)
|
||||
: nav_{nav},
|
||||
entry_{std::move(entry)} {
|
||||
add_children({&labels,
|
||||
&field_type,
|
||||
&field_freq_a,
|
||||
&field_freq_b,
|
||||
&field_modulation,
|
||||
&field_bandwidth,
|
||||
&field_step,
|
||||
&field_tone,
|
||||
&field_description,
|
||||
&text_validation,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
freqman_set_modulation_option(field_modulation);
|
||||
populate_bandwidth_options();
|
||||
populate_step_options();
|
||||
populate_tone_options();
|
||||
|
||||
// Add the "invalid/unset" option. Kind of hacky, but...
|
||||
field_modulation.options().insert(
|
||||
field_modulation.options().begin(), {"None", -1});
|
||||
field_step.options().insert(
|
||||
field_step.options().begin(), {"None", -1});
|
||||
|
||||
field_type.set_by_value((int32_t)entry_.type);
|
||||
field_type.on_change = [this](size_t, auto value) {
|
||||
entry_.type = static_cast<freqman_type>(value);
|
||||
refresh_ui();
|
||||
};
|
||||
|
||||
// TODO: this pattern should be able to be wrapped up.
|
||||
field_freq_a.set_value(entry_.frequency_a);
|
||||
field_freq_a.on_change = [this](rf::Frequency f) {
|
||||
entry_.frequency_a = f;
|
||||
refresh_ui();
|
||||
};
|
||||
field_freq_a.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_a.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_freq_a.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_freq_b.set_value(entry_.frequency_b);
|
||||
field_freq_b.on_change = [this](rf::Frequency f) {
|
||||
entry_.frequency_b = f;
|
||||
refresh_ui();
|
||||
};
|
||||
field_freq_b.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_b.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_freq_b.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_modulation.set_by_value((int32_t)entry_.modulation);
|
||||
field_modulation.on_change = [this](size_t, auto value) {
|
||||
entry_.modulation = static_cast<freqman_index_t>(value);
|
||||
populate_bandwidth_options();
|
||||
};
|
||||
|
||||
field_bandwidth.set_by_value((int32_t)entry_.bandwidth);
|
||||
field_bandwidth.on_change = [this](size_t, auto value) {
|
||||
entry_.bandwidth = static_cast<freqman_index_t>(value);
|
||||
};
|
||||
|
||||
field_step.set_by_value((int32_t)entry_.step);
|
||||
field_step.on_change = [this](size_t, auto value) {
|
||||
entry_.step = static_cast<freqman_index_t>(value);
|
||||
};
|
||||
|
||||
field_tone.set_by_value((int32_t)entry_.tone);
|
||||
field_tone.on_change = [this](size_t, auto value) {
|
||||
entry_.tone = static_cast<freqman_index_t>(value);
|
||||
};
|
||||
|
||||
field_description.set_text(entry_.description);
|
||||
field_description.on_change = [this](TextField& tf) {
|
||||
entry_.description = tf.get_text();
|
||||
};
|
||||
field_description.on_select = [this](TextField& tf) {
|
||||
temp_buffer_ = tf.get_text();
|
||||
text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max,
|
||||
[this, &tf](std::string& new_desc) {
|
||||
tf.set_text(new_desc);
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(std::move(entry_));
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_ui();
|
||||
}
|
||||
|
||||
void FrequencyEditView::focus() {
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
void FrequencyEditView::refresh_ui() {
|
||||
// This needs to be called whenever a UI option is changed
|
||||
// in a way that causes fields to be unused or would make the
|
||||
// entry fail validation.
|
||||
|
||||
auto is_range = entry_.type == freqman_type::Range;
|
||||
auto is_ham = entry_.type == freqman_type::HamRadio;
|
||||
auto has_freq_b = is_range || is_ham;
|
||||
|
||||
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
|
||||
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
|
||||
field_tone.set_style(is_ham ? &Styles::white : &Styles::grey);
|
||||
|
||||
if (is_valid(entry_)) {
|
||||
text_validation.set("Valid");
|
||||
text_validation.set_style(&Styles::green);
|
||||
} else {
|
||||
text_validation.set("Error");
|
||||
text_validation.set_style(&Styles::red);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The following functions shouldn't be needed once
|
||||
// freqman_db lookup tables are complete.
|
||||
void FrequencyEditView::populate_bandwidth_options() {
|
||||
OptionsField::options_t options;
|
||||
options.push_back({"None", -1});
|
||||
|
||||
if (entry_.modulation < std::size(freqman_bandwidths)) {
|
||||
auto& bandwidths = freqman_bandwidths[entry_.modulation];
|
||||
for (auto i = 1u; i < bandwidths.size(); ++i) {
|
||||
auto& item = bandwidths[i];
|
||||
options.push_back({item.first, (OptionsField::value_t)i});
|
||||
}
|
||||
}
|
||||
|
||||
field_bandwidth.set_options(std::move(options));
|
||||
}
|
||||
|
||||
void FrequencyEditView::populate_step_options() {
|
||||
OptionsField::options_t options;
|
||||
options.push_back({"None", -1});
|
||||
|
||||
for (auto i = 1u; i < freqman_steps.size(); ++i) {
|
||||
auto& item = freqman_steps[i];
|
||||
options.push_back({item.first, (OptionsField::value_t)i});
|
||||
}
|
||||
|
||||
field_step.set_options(std::move(options));
|
||||
}
|
||||
|
||||
void FrequencyEditView::populate_tone_options() {
|
||||
using namespace tonekey;
|
||||
OptionsField::options_t options;
|
||||
options.push_back({"None", -1});
|
||||
|
||||
for (auto i = 1u; i < tone_keys.size(); ++i) {
|
||||
auto& item = tone_keys[i];
|
||||
options.push_back({fx100_string(item.second), (OptionsField::value_t)i});
|
||||
}
|
||||
|
||||
field_tone.set_options(std::move(options));
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -36,17 +36,18 @@ namespace ui {
|
||||
|
||||
class FreqManBaseView : public View {
|
||||
public:
|
||||
FreqManBaseView(
|
||||
NavigationView& nav);
|
||||
FreqManBaseView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
static constexpr size_t desc_edit_max = 0x80;
|
||||
|
||||
protected:
|
||||
using options_t = OptionsField::options_t;
|
||||
|
||||
NavigationView& nav_;
|
||||
freqman_error error_{NO_ERROR};
|
||||
std::function<void(void)> on_select_frequency{nullptr};
|
||||
std::function<void(void)> on_select_frequency{};
|
||||
|
||||
void change_category(size_t new_index);
|
||||
/* Access the categories directly from the OptionsField.
|
||||
@@ -85,7 +86,7 @@ class FreqManBaseView : public View {
|
||||
class FrequencySaveView : public FreqManBaseView {
|
||||
public:
|
||||
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
|
||||
std::string title() const override { return "Save freq"; };
|
||||
std::string title() const override { return "Save freq"; }
|
||||
|
||||
private:
|
||||
std::string temp_buffer_{};
|
||||
@@ -121,17 +122,18 @@ class FrequencyLoadView : public FreqManBaseView {
|
||||
std::function<void(rf::Frequency, rf::Frequency)> on_range_loaded{};
|
||||
|
||||
FrequencyLoadView(NavigationView& nav);
|
||||
std::string title() const override { return "Load freq"; };
|
||||
std::string title() const override { return "Load freq"; }
|
||||
};
|
||||
|
||||
class FrequencyManagerView : public FreqManBaseView {
|
||||
public:
|
||||
FrequencyManagerView(NavigationView& nav);
|
||||
std::string title() const override { return "Freqman"; };
|
||||
std::string title() const override { return "Freqman"; }
|
||||
|
||||
private:
|
||||
std::string temp_buffer_{};
|
||||
|
||||
void on_edit_entry();
|
||||
void on_edit_freq();
|
||||
void on_edit_desc();
|
||||
void on_add_category();
|
||||
@@ -139,9 +141,6 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
void on_add_entry();
|
||||
void on_del_entry();
|
||||
|
||||
Labels labels{
|
||||
{{5 * 8, 14 * 16 - 4}, "Edit:", Color::light_grey()}};
|
||||
|
||||
NewButton button_add_category{
|
||||
{23 * 8, 0 * 16, 7 * 4, 20},
|
||||
{},
|
||||
@@ -156,6 +155,14 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
Color::red(),
|
||||
true};
|
||||
|
||||
Button button_edit_entry{
|
||||
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||
"Edit"};
|
||||
|
||||
Rectangle rect_padding{
|
||||
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||
Color::grey()};
|
||||
|
||||
Button button_edit_freq{
|
||||
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
|
||||
"Frequency"};
|
||||
@@ -179,4 +186,79 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
true};
|
||||
};
|
||||
|
||||
class FrequencyEditView : public View {
|
||||
public:
|
||||
std::function<void(const freqman_entry&)> on_save{};
|
||||
|
||||
FrequencyEditView(
|
||||
NavigationView& nav,
|
||||
freqman_entry entry);
|
||||
std::string title() const override { return "Freqman Edit"; }
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
freqman_entry entry_;
|
||||
std::string temp_buffer_{};
|
||||
|
||||
void refresh_ui();
|
||||
void populate_bandwidth_options();
|
||||
void populate_step_options();
|
||||
void populate_tone_options();
|
||||
|
||||
Labels labels{
|
||||
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Color::white()},
|
||||
{{0 * 8, 3 * 16}, "Entry Type :", Color::light_grey()},
|
||||
{{0 * 8, 4 * 16}, "Frequency A:", Color::light_grey()},
|
||||
{{0 * 8, 5 * 16}, "Frequency B:", Color::light_grey()},
|
||||
{{0 * 8, 6 * 16}, "Modulation :", Color::light_grey()},
|
||||
{{0 * 8, 7 * 16}, "Bandwidth :", Color::light_grey()},
|
||||
{{0 * 8, 8 * 16}, "Step :", Color::light_grey()},
|
||||
{{0 * 8, 9 * 16}, "Tone Freq :", Color::light_grey()},
|
||||
{{0 * 8, 10 * 16}, "Description:", Color::light_grey()},
|
||||
};
|
||||
|
||||
OptionsField field_type{{13 * 8, 3 * 16}, 8, {
|
||||
{"Single", 0},
|
||||
{"Range", 1},
|
||||
{"HamRadio", 2},
|
||||
{"Raw", 3},
|
||||
}};
|
||||
|
||||
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
|
||||
|
||||
FrequencyField field_freq_b{{13 * 8, 5 * 16}};
|
||||
|
||||
OptionsField field_modulation{{13 * 8, 6 * 16}, 5, {}};
|
||||
|
||||
OptionsField field_bandwidth{{13 * 8, 7 * 16}, 7, {}};
|
||||
|
||||
OptionsField field_step{{13 * 8, 8 * 16}, 12, {}};
|
||||
|
||||
OptionsField field_tone{{13 * 8, 9 * 16}, 13, {}};
|
||||
|
||||
TextField field_description{
|
||||
{13 * 8, 10 * 16, 17 * 8, 1 * 16},
|
||||
{}};
|
||||
|
||||
Text text_validation{
|
||||
{12 * 8, 12 * 16, 5 * 8, 1 * 16},
|
||||
{}};
|
||||
|
||||
Button button_save{
|
||||
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
"Cancel"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
|
||||
void freqman_set_modulation_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option_short(ui::OptionsField& option);
|
||||
void freqman_set_tone_option(ui::OptionsField& option);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "ui_level.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -24,21 +24,21 @@
|
||||
#ifndef _UI_LEVEL
|
||||
#define _UI_LEVEL
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "portapack_hal.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "tonesets.hpp"
|
||||
@@ -355,13 +356,10 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
options_tone_key.hidden(0); // we are in FM mode, we should have active the Key-tones & CTCSS option.
|
||||
|
||||
rxbw.emplace_back(" NFM1:8k5 ", 0); // restore the original dynamic field_rxbw value.
|
||||
rxbw.emplace_back(" NFM2:11k ", 1);
|
||||
rxbw.emplace_back(" FM :16k ", 2);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
|
||||
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
|
||||
freqman_set_bandwidth_option(NFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with NFM options from freqman_db.cpp
|
||||
field_rxbw.set_by_value(2); // // bw 16k (2) default
|
||||
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
|
||||
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
|
||||
break;
|
||||
case 1: //{ "WFM", 1 }
|
||||
enable_am = false;
|
||||
@@ -375,13 +373,10 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
options_tone_key.hidden(0); // we are in WFM mode, we should have active the Key-tones & CTCSS option.
|
||||
|
||||
rxbw.emplace_back(" 200k-WFM ", 0); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K, (2) WFM-40K.
|
||||
rxbw.emplace_back(" 180k-WFM ", 1);
|
||||
rxbw.emplace_back(" 40k-WFM ", 2);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
|
||||
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
|
||||
freqman_set_bandwidth_option(WFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with WFM options from freqman_db.cpp
|
||||
field_rxbw.set_by_value(0); // bw 200k (0) default
|
||||
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
|
||||
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
|
||||
break;
|
||||
case 2: //{ "AM", 2 }
|
||||
enable_am = true;
|
||||
@@ -390,8 +385,8 @@ MicTXView::MicTXView(
|
||||
set_dirty(); // Refresh display
|
||||
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
|
||||
|
||||
rxbw.emplace_back(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k.
|
||||
rxbw.emplace_back(" DSB2-6k ", 1);
|
||||
rxbw.emplace_back("DSB 9k", 0); // we offer in AM DSB two audio BW 9k / 6k.
|
||||
rxbw.emplace_back("DSB 6k", 1);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
|
||||
@@ -404,8 +399,8 @@ MicTXView::MicTXView(
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
|
||||
check_rogerbeep.hidden(1); // hide that roger beep selection.
|
||||
|
||||
rxbw.emplace_back(" USB+3k ", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
rxbw.emplace_back("USB+3k", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
set_dirty(); // Refresh display
|
||||
break;
|
||||
@@ -415,8 +410,8 @@ MicTXView::MicTXView(
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
|
||||
check_rogerbeep.hidden(1); // hide that roger beep selection.
|
||||
|
||||
rxbw.emplace_back(" LSB-3k ", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
rxbw.emplace_back("LSB-3k", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
set_dirty(); // Refresh display
|
||||
break;
|
||||
@@ -425,8 +420,8 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
|
||||
|
||||
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
|
||||
rxbw.emplace_back("SSB2:LSB-3k", 1);
|
||||
rxbw.emplace_back("USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-SC case to the field_rxbw value.
|
||||
rxbw.emplace_back("LSB-3k", 1);
|
||||
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField options_ak4951_alc_mode{
|
||||
{20 * 8, 1 * 8},
|
||||
11,
|
||||
10, // Label has 10 chars
|
||||
{
|
||||
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL, keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
|
||||
{"+12dB-6kHz", 1}, // ALC-> on, (+12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
@@ -198,7 +198,7 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField options_wm8731_boost_mode{
|
||||
{22 * 8, 1 * 8},
|
||||
5,
|
||||
8, // Label has 8 chars
|
||||
{
|
||||
{"ON +12dB", 0}, // WM8731 Mic Boost ON, original+12dBs condition, easy to saturate ADC sat in high voice, relative G = +12 dB's respect ref level
|
||||
{"ON +06dB", 1}, // WM8731 Mic Boost ON, original+6 dBs condition, easy to saturate ADC sat in high voice, relative G = +06 dB's respect ref level
|
||||
@@ -298,11 +298,11 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField field_rxbw{
|
||||
{19 * 8, (23 * 8) + 2},
|
||||
3,
|
||||
7,
|
||||
{
|
||||
{" NFM1:8k5 ", 0},
|
||||
{" NFM2:11k ", 1},
|
||||
{" FM :16k ", 2},
|
||||
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
|
||||
{" 11k ", 1},
|
||||
{" 16k ", 2},
|
||||
}};
|
||||
|
||||
NumberField field_squelch{
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "utility.hpp"
|
||||
@@ -48,7 +50,6 @@ namespace fs = std::filesystem;
|
||||
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) {
|
||||
@@ -258,7 +259,7 @@ void PlaylistView::send_current_track() {
|
||||
chThdSleepMilliseconds(current()->ms_delay);
|
||||
|
||||
// Open the sample file to send.
|
||||
auto reader = std::make_unique<FileReader>();
|
||||
auto reader = std::make_unique<FileConvertReader>();
|
||||
auto error = reader->open(current()->path);
|
||||
if (error) {
|
||||
show_file_error(current()->path, "Can't open file to send.");
|
||||
@@ -323,9 +324,10 @@ void PlaylistView::update_ui() {
|
||||
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");
|
||||
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, (current()->metadata.sample_rate > 1000000) ? 2 : 0) + "Hz");
|
||||
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
|
||||
uint8_t sample_size = capture_file_sample_size(current()->path);
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, sample_size);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
field_frequency.set_value(current()->metadata.center_frequency);
|
||||
|
||||
@@ -336,7 +338,7 @@ void PlaylistView::update_ui() {
|
||||
|
||||
progressbar_track.set_max(playlist_db_.size() - 1);
|
||||
progressbar_track.set_value(current_index_);
|
||||
progressbar_transmit.set_max(current()->file_size);
|
||||
progressbar_transmit.set_max(current()->file_size * sizeof(complex16_t) / sample_size);
|
||||
}
|
||||
|
||||
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
|
||||
@@ -406,7 +408,7 @@ PlaylistView::PlaylistView(
|
||||
button_add.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
auto open_view = nav_.push<FileLoadView>(".C16");
|
||||
auto open_view = nav_.push<FileLoadView>(".C*");
|
||||
open_view->push_dir(u"CAPTURES");
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
add_entry(std::move(path));
|
||||
@@ -459,7 +461,7 @@ PlaylistView::PlaylistView(
|
||||
auto ext = path.extension();
|
||||
if (path_iequal(ext, ppl_ext))
|
||||
on_file_changed(path);
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
else if (is_cxx_capture_file(path))
|
||||
add_entry(fs::path{path});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,17 @@
|
||||
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "tone_key.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
using portapack::memory::map::backup_ram;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -124,152 +128,81 @@ void ReconView::colorize_waits() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ReconView::recon_save_freq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) {
|
||||
File recon_file;
|
||||
|
||||
bool ReconView::recon_save_freq(const fs::path& path, size_t freq_index, bool warn_if_exists) {
|
||||
if (frequency_list.size() == 0 || !current_is_valid())
|
||||
return false;
|
||||
|
||||
FreqmanDB freq_db;
|
||||
if (!freq_db.open(path, /*create*/ true))
|
||||
return false;
|
||||
|
||||
freqman_entry entry = *frequency_list[freq_index]; // Makes a copy.
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.type = freqman_type::Single;
|
||||
|
||||
// TODO: Use FreqmanDB
|
||||
auto frequency_to_add = to_freqman_string(entry);
|
||||
|
||||
auto result = recon_file.open(freq_file_path); // First recon if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
char one_char[1]{}; // Read it char by char
|
||||
std::string line{}; // and put read line in here
|
||||
bool found{false};
|
||||
for (size_t pointer = 0; pointer < recon_file.size(); pointer++) {
|
||||
recon_file.seek(pointer);
|
||||
recon_file.read(one_char, 1);
|
||||
if ((int)one_char[0] > 31) { // ascii space upwards
|
||||
line += one_char[0]; // add it to the textline
|
||||
} else if (one_char[0] == '\n') { // New Line
|
||||
if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
line.clear(); // Ready for next textline
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
result = recon_file.append(freq_file_path); // Second: append if it is not there
|
||||
if (!result.is_valid()) {
|
||||
recon_file.write_line(frequency_to_add);
|
||||
}
|
||||
}
|
||||
if (found && warn_if_exists) {
|
||||
nav_.display_modal("Error", "Frequency already exists");
|
||||
}
|
||||
} else {
|
||||
result = recon_file.create(freq_file_path); // First freq if no output file
|
||||
if (!result.is_valid()) {
|
||||
recon_file.write_line(frequency_to_add);
|
||||
}
|
||||
// For ranges, save the current frequency instead.
|
||||
if (entry.type == freqman_type::Range) {
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.type = freqman_type::Single;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.step = freqman_invalid_index;
|
||||
}
|
||||
|
||||
auto it = freq_db.find_entry(entry);
|
||||
auto found = (it != freq_db.end());
|
||||
|
||||
if (found && warn_if_exists)
|
||||
nav_.display_modal("Error", "Frequency already exists");
|
||||
|
||||
if (!found)
|
||||
freq_db.append_entry(entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReconView::recon_load_config_from_sd() {
|
||||
File settings_file{};
|
||||
size_t length{0};
|
||||
size_t file_position{0};
|
||||
char* pos{NULL};
|
||||
char* line_start{NULL};
|
||||
char* line_end{NULL};
|
||||
char file_data[257]{};
|
||||
uint32_t it{0};
|
||||
uint32_t nb_params{RECON_SETTINGS_NB_PARAMS};
|
||||
std::string params[RECON_SETTINGS_NB_PARAMS]{};
|
||||
|
||||
make_new_directory(u"SETTINGS");
|
||||
|
||||
auto result = settings_file.open(RECON_CFG_FILE);
|
||||
if (!result.is_valid()) {
|
||||
while (it < nb_params) {
|
||||
// Read a 256 bytes block from file
|
||||
settings_file.seek(file_position);
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = settings_file.read(file_data, 256);
|
||||
if (read_size.is_error())
|
||||
break;
|
||||
file_position += 256;
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
pos = line_start;
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
length = line_end - line_start - 1;
|
||||
params[it] = std::string(pos, length);
|
||||
it++;
|
||||
line_start = line_end + 1;
|
||||
pos = line_start;
|
||||
if (line_start - file_data >= 256)
|
||||
break;
|
||||
if (it >= nb_params)
|
||||
break;
|
||||
}
|
||||
if (read_size.value() != 256)
|
||||
break; // End of file
|
||||
|
||||
// Restart at beginning of last incomplete line
|
||||
file_position -= (file_data + 256 - line_start);
|
||||
}
|
||||
}
|
||||
|
||||
if (it < nb_params) {
|
||||
/* bad number of params, signal defaults */
|
||||
input_file = "RECON";
|
||||
output_file = "RECON_RESULTS";
|
||||
recon_lock_duration = RECON_MIN_LOCK_DURATION;
|
||||
recon_lock_nb_match = RECON_DEF_NB_MATCH;
|
||||
squelch = -14;
|
||||
recon_match_mode = RECON_MATCH_CONTINUOUS;
|
||||
wait = RECON_DEF_WAIT_DURATION;
|
||||
File settings_file;
|
||||
auto error = settings_file.open(RECON_CFG_FILE);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
auto complete = false;
|
||||
auto line_nb = 0;
|
||||
auto reader = FileLineReader(settings_file);
|
||||
for (const auto& line : reader) {
|
||||
switch (line_nb) {
|
||||
case 0:
|
||||
input_file = trim(line);
|
||||
break;
|
||||
case 1:
|
||||
output_file = trim(line);
|
||||
break;
|
||||
case 2:
|
||||
parse_int(line, recon_lock_duration);
|
||||
break;
|
||||
case 3:
|
||||
parse_int(line, recon_lock_nb_match);
|
||||
break;
|
||||
case 4:
|
||||
parse_int(line, squelch);
|
||||
break;
|
||||
case 5:
|
||||
parse_int(line, recon_match_mode);
|
||||
break;
|
||||
case 6:
|
||||
parse_int(line, wait);
|
||||
complete = true; // NB: Last entry.
|
||||
break;
|
||||
}
|
||||
|
||||
if (complete) break;
|
||||
|
||||
line_nb++;
|
||||
}
|
||||
|
||||
if (it > 0)
|
||||
input_file = params[0];
|
||||
else
|
||||
input_file = "RECON";
|
||||
|
||||
if (it > 1)
|
||||
output_file = params[1];
|
||||
else
|
||||
output_file = "RECON_RESULTS";
|
||||
|
||||
if (it > 2)
|
||||
recon_lock_duration = strtoll(params[2].c_str(), nullptr, 10);
|
||||
else
|
||||
recon_lock_duration = RECON_MIN_LOCK_DURATION;
|
||||
|
||||
if (it > 3)
|
||||
recon_lock_nb_match = strtoll(params[3].c_str(), nullptr, 10);
|
||||
else
|
||||
recon_lock_nb_match = RECON_DEF_NB_MATCH;
|
||||
|
||||
if (it > 4)
|
||||
squelch = strtoll(params[4].c_str(), nullptr, 10);
|
||||
else
|
||||
squelch = -14;
|
||||
|
||||
if (it > 5)
|
||||
recon_match_mode = strtoll(params[5].c_str(), nullptr, 10);
|
||||
else
|
||||
recon_match_mode = RECON_MATCH_CONTINUOUS;
|
||||
|
||||
if (it > 6)
|
||||
wait = strtoll(params[6].c_str(), nullptr, 10);
|
||||
else
|
||||
wait = RECON_DEF_WAIT_DURATION;
|
||||
|
||||
return true;
|
||||
return complete;
|
||||
}
|
||||
|
||||
bool ReconView::recon_save_config_to_sd() {
|
||||
@@ -486,6 +419,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
if (frequency_list.size() > 0) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(current_index);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
// NB: This is using the freq keypad to select an index.
|
||||
f = f / OneMHz;
|
||||
if (f >= 1 && f <= frequency_list.size()) {
|
||||
index_stepper = f - 1 - current_index;
|
||||
@@ -496,17 +430,18 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_manual_start.on_change = [this]() {
|
||||
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * freqman_entry_get_step_value(def_step);
|
||||
auto step_val = freqman_entry_get_step_value(def_step);
|
||||
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * step_val;
|
||||
if (frequency_range.min < 0) {
|
||||
frequency_range.min = 0;
|
||||
}
|
||||
if (frequency_range.min > (MAX_UFREQ - freqman_entry_get_step_value(def_step))) {
|
||||
frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.min > (MAX_UFREQ - step_val)) {
|
||||
frequency_range.min = MAX_UFREQ - step_val;
|
||||
}
|
||||
if (frequency_range.min > (frequency_range.max - freqman_entry_get_step_value(def_step))) {
|
||||
frequency_range.max = frequency_range.min + freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.min > (frequency_range.max - step_val)) {
|
||||
frequency_range.max = frequency_range.min + step_val;
|
||||
if (frequency_range.max > MAX_UFREQ) {
|
||||
frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step);
|
||||
frequency_range.min = MAX_UFREQ - step_val;
|
||||
frequency_range.max = MAX_UFREQ;
|
||||
}
|
||||
}
|
||||
@@ -516,18 +451,19 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_manual_end.on_change = [this]() {
|
||||
frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) {
|
||||
frequency_range.max = (freqman_entry_get_step_value(def_step) + 1);
|
||||
auto step_val = freqman_entry_get_step_value(def_step);
|
||||
frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * step_val;
|
||||
if (frequency_range.max < (step_val + 1)) {
|
||||
frequency_range.max = (step_val + 1);
|
||||
}
|
||||
if (frequency_range.max > MAX_UFREQ) {
|
||||
frequency_range.max = MAX_UFREQ;
|
||||
}
|
||||
if (frequency_range.max < (frequency_range.min + freqman_entry_get_step_value(def_step))) {
|
||||
frequency_range.min = frequency_range.max - freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) {
|
||||
if (frequency_range.max < (frequency_range.min + step_val)) {
|
||||
frequency_range.min = frequency_range.max - step_val;
|
||||
if (frequency_range.max < (step_val + 1)) {
|
||||
frequency_range.min = 1;
|
||||
frequency_range.max = (freqman_entry_get_step_value(def_step) + 1);
|
||||
frequency_range.max = (step_val + 1);
|
||||
}
|
||||
}
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
@@ -607,87 +543,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_remove.on_select = [this](ButtonWithEncoder&) {
|
||||
// TODO: Use FreqmanDB
|
||||
if (frequency_list.size() > 0) {
|
||||
if (!manual_mode) {
|
||||
// scanner or recon (!scanner) mode
|
||||
// in both we delete index from live view, but only remove in output file in scanner_mode
|
||||
if (current_index >= (int32_t)frequency_list.size()) {
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
frequency_list.erase(frequency_list.begin() + current_index);
|
||||
if (current_index >= (int32_t)frequency_list.size()) {
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
if (frequency_list.size() > 0) {
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
}
|
||||
// also remove from output file if in scanner mode
|
||||
if (scanner_mode) {
|
||||
File freqman_file{};
|
||||
delete_file(freq_file_path);
|
||||
auto result = freqman_file.create(freq_file_path);
|
||||
if (!result.is_valid()) {
|
||||
for (size_t n = 0; n < frequency_list.size(); n++) {
|
||||
auto line = to_freqman_string(*frequency_list[n]);
|
||||
freqman_file.write_line(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (manual_mode) // only remove from output
|
||||
{
|
||||
File recon_file{};
|
||||
File tmp_recon_file{};
|
||||
std::string tmp_freq_file_path{freq_file_path + ".TMP"};
|
||||
handle_remove_current_item();
|
||||
|
||||
freqman_entry entry = current_entry();
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.type = freqman_type::Single;
|
||||
|
||||
auto frequency_to_add = to_freqman_string(entry);
|
||||
|
||||
delete_file(tmp_freq_file_path);
|
||||
auto result = tmp_recon_file.create(tmp_freq_file_path); // First recon if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
bool found = false;
|
||||
result = recon_file.open(freq_file_path); // First recon if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
char one_char[1]{}; // Read it char by char
|
||||
std::string line{}; // and put read line in here
|
||||
for (size_t pointer = 0; pointer < recon_file.size(); pointer++) {
|
||||
recon_file.seek(pointer);
|
||||
recon_file.read(one_char, 1);
|
||||
if ((int)one_char[0] > 31) { // ascii space upwards
|
||||
line += one_char[0]; // add it to the textline
|
||||
} else if (one_char[0] == '\n') { // new Line
|
||||
if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) {
|
||||
found = true;
|
||||
} else {
|
||||
tmp_recon_file.write_line(frequency_to_add);
|
||||
}
|
||||
line.clear(); // ready for next textline
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
delete_file(freq_file_path);
|
||||
rename_file(tmp_freq_file_path, freq_file_path);
|
||||
} else {
|
||||
delete_file(tmp_freq_file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
receiver_model.set_target_frequency(current_entry().frequency_a); // retune
|
||||
}
|
||||
if (frequency_list.size() == 0) {
|
||||
text_cycle.set_text(" ");
|
||||
desc_cycle.set("no entries in list"); // Show new description
|
||||
delete_file(freq_file_path);
|
||||
}
|
||||
// TODO: refresh UI.
|
||||
timer = 0;
|
||||
freq_lock = 0;
|
||||
};
|
||||
@@ -766,9 +624,8 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_restart.on_select = [this](Button&) {
|
||||
frequency_file_load(true);
|
||||
if (frequency_list.size() > 0) {
|
||||
def_step = step_mode.selected_index(); // Use def_step from manual selector
|
||||
frequency_file_load(true);
|
||||
if (fwd) {
|
||||
button_dir.set_text("FW>");
|
||||
} else {
|
||||
@@ -921,8 +778,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
recon_redraw();
|
||||
}
|
||||
|
||||
void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
(void)(stop_all_before);
|
||||
void ReconView::frequency_file_load(bool) {
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
@@ -940,6 +796,7 @@ void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
desc_cycle.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
|
||||
freqman_load_options options{
|
||||
.load_freqs = load_freqs,
|
||||
.load_ranges = load_ranges,
|
||||
@@ -1033,7 +890,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (recon) {
|
||||
if (!timer) {
|
||||
status = 0;
|
||||
continuous_lock = false;
|
||||
freq_lock = 0;
|
||||
timer = local_recon_lock_duration;
|
||||
big_display.set_style(&Styles::white);
|
||||
@@ -1050,22 +906,19 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
}
|
||||
if (db > squelch) // MATCHING LEVEL
|
||||
{
|
||||
continuous_lock = true;
|
||||
freq_lock++;
|
||||
timer += time_interval; // give some more time for next lock
|
||||
} else {
|
||||
// continuous, direct cut it if not consecutive match after 1 first match
|
||||
if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
|
||||
if (freq_lock > 0) {
|
||||
timer = 0;
|
||||
continuous_lock = false;
|
||||
}
|
||||
freq_lock = 0;
|
||||
timer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (freq_lock >= recon_lock_nb_match) // LOCKED
|
||||
{
|
||||
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) {
|
||||
@@ -1106,10 +959,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
last_timer = timer;
|
||||
text_timer.set("TIMER: " + to_string_dec_int(timer));
|
||||
}
|
||||
if (timer) {
|
||||
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE) {
|
||||
timer -= time_interval;
|
||||
}
|
||||
|
||||
if (timer != 0) {
|
||||
timer -= time_interval;
|
||||
if (timer < 0) {
|
||||
timer = 0;
|
||||
}
|
||||
@@ -1269,7 +1121,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
void ReconView::recon_pause() {
|
||||
timer = 0;
|
||||
freq_lock = 0;
|
||||
continuous_lock = false;
|
||||
recon = false;
|
||||
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
@@ -1282,7 +1133,6 @@ void ReconView::recon_pause() {
|
||||
void ReconView::recon_resume() {
|
||||
timer = 0;
|
||||
freq_lock = 0;
|
||||
continuous_lock = false;
|
||||
recon = true;
|
||||
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
@@ -1430,4 +1280,35 @@ void ReconView::handle_coded_squelch(const uint32_t value) {
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
void ReconView::handle_remove_current_item() {
|
||||
if (frequency_list.empty() || !current_is_valid())
|
||||
return;
|
||||
|
||||
auto entry = current_entry(); // Copy the current entry.
|
||||
|
||||
// In Scanner or Recon modes, remove from the in-memory list.
|
||||
if (mode() != recon_mode::Manual) {
|
||||
if (current_is_valid()) {
|
||||
frequency_list.erase(frequency_list.begin() + current_index);
|
||||
}
|
||||
|
||||
if (frequency_list.size() > 0) {
|
||||
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
} else {
|
||||
current_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// In Scanner or Manual mode, remove the entry from the output file.
|
||||
if (mode() != recon_mode::Recon) {
|
||||
FreqmanDB freq_db;
|
||||
if (!freq_db.open(freq_file_path))
|
||||
return;
|
||||
|
||||
freq_db.delete_entry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -46,6 +46,12 @@ namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
Scanner,
|
||||
Manual
|
||||
};
|
||||
|
||||
class ReconView : public View {
|
||||
public:
|
||||
ReconView(NavigationView& nav);
|
||||
@@ -82,9 +88,10 @@ class ReconView : public View {
|
||||
void recon_redraw();
|
||||
void handle_retune();
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
void handle_remove_current_item();
|
||||
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);
|
||||
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
|
||||
// placeholder for possible void recon_start_recording();
|
||||
void recon_stop_recording();
|
||||
|
||||
@@ -92,8 +99,15 @@ class ReconView : public View {
|
||||
bool current_is_valid();
|
||||
freqman_entry& current_entry();
|
||||
|
||||
// TODO: consolidate mode bools and use recon_mode.
|
||||
recon_mode mode() const {
|
||||
if (manual_mode) return recon_mode::Manual;
|
||||
if (scanner_mode) return recon_mode::Scanner;
|
||||
return recon_mode::Recon;
|
||||
}
|
||||
|
||||
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
|
||||
int32_t squelch{0};
|
||||
int32_t squelch{RECON_DEF_SQUELCH};
|
||||
int32_t db{0};
|
||||
int32_t timer{0};
|
||||
int32_t wait{RECON_DEF_WAIT_DURATION}; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
@@ -117,7 +131,7 @@ class ReconView : public View {
|
||||
bool user_pause{false};
|
||||
bool auto_record_locked{false};
|
||||
bool is_recording{false};
|
||||
uint32_t recon_lock_nb_match{3};
|
||||
uint32_t recon_lock_nb_match{RECON_DEF_NB_MATCH};
|
||||
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
|
||||
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
|
||||
bool scanner_mode{false};
|
||||
|
||||
@@ -70,11 +70,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
||||
button_save_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
if (new_file_path.native().find(freqman_dir.native()) == 0) {
|
||||
_output_file = new_file_path.stem().string();
|
||||
button_output_file.set_text(_output_file);
|
||||
text_input_file.set(_output_file);
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
@@ -84,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
||||
button_output_file.on_select = [this, &nav](Button&) {
|
||||
text_prompt(nav, _output_file, 28,
|
||||
[this](std::string& buffer) {
|
||||
_output_file = buffer;
|
||||
_output_file = std::move(buffer);
|
||||
button_output_file.set_text(_output_file);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
// maximum lock duration
|
||||
#define RECON_MAX_LOCK_DURATION 9900
|
||||
|
||||
#define RECON_DEF_SQUELCH -14
|
||||
|
||||
// default number of match to have a lock
|
||||
#define RECON_DEF_NB_MATCH 3
|
||||
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "ui_scanner.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
|
||||
@@ -480,10 +480,12 @@ class SetEncoderDialView : public View {
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 3 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_MEDIUM},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
7,
|
||||
{{"LOWEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOWEST},
|
||||
{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH},
|
||||
{"HIGHEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGHEST}}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
*/
|
||||
|
||||
#include "ui_spectrum_painter.hpp"
|
||||
|
||||
#include "bmp.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "file.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
|
||||
@@ -301,6 +301,38 @@ void TextEditorMenu::hide_children(bool hidden) {
|
||||
|
||||
/* TextEditorView ***************************************************/
|
||||
|
||||
static fs::path get_temp_path(const fs::path& path) {
|
||||
if (!path.empty())
|
||||
return path + "~";
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static void delete_temp_file(const fs::path& path) {
|
||||
auto temp_path = get_temp_path(path);
|
||||
if (!temp_path.empty()) {
|
||||
delete_file(temp_path);
|
||||
}
|
||||
}
|
||||
|
||||
static void save_temp_file(const fs::path& path) {
|
||||
delete_file(path);
|
||||
copy_file(get_temp_path(path), path);
|
||||
}
|
||||
|
||||
static void show_save_prompt(
|
||||
NavigationView& nav,
|
||||
std::function<void()> on_save,
|
||||
std::function<void()> continuation) {
|
||||
nav.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[on_save](bool choice) {
|
||||
if (choice && on_save)
|
||||
on_save();
|
||||
});
|
||||
nav.set_on_pop(continuation);
|
||||
}
|
||||
|
||||
TextEditorView::TextEditorView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children(
|
||||
@@ -381,9 +413,7 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
};
|
||||
|
||||
menu.on_exit() = [this]() {
|
||||
show_save_prompt([this]() {
|
||||
nav_.pop();
|
||||
});
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_menu.on_select = [this]() {
|
||||
@@ -402,7 +432,15 @@ TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
|
||||
}
|
||||
|
||||
TextEditorView::~TextEditorView() {
|
||||
delete_temp_file();
|
||||
// NB: Be careful here. The UI will render after this instance
|
||||
// has been destroyed. Everything needed to render the UI
|
||||
// and perform the save actions must be value captured.
|
||||
if (file_dirty_) {
|
||||
ui::show_save_prompt(
|
||||
nav_,
|
||||
[p = path_]() { ui::save_temp_file(p); },
|
||||
[p = std::move(path_)]() { delete_temp_file(p); });
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditorView::on_show() {
|
||||
@@ -415,7 +453,7 @@ void TextEditorView::on_show() {
|
||||
void TextEditorView::open_file(const fs::path& path) {
|
||||
file_.reset();
|
||||
viewer.clear_file();
|
||||
delete_temp_file();
|
||||
delete_temp_file(path_);
|
||||
|
||||
path_ = {};
|
||||
file_dirty_ = false;
|
||||
@@ -516,13 +554,10 @@ void TextEditorView::show_save_prompt(std::function<void()> continuation) {
|
||||
return;
|
||||
}
|
||||
|
||||
nav_.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
save_temp_file();
|
||||
});
|
||||
nav_.set_on_pop(continuation);
|
||||
ui::show_save_prompt(
|
||||
nav_,
|
||||
[this]() { save_temp_file(); },
|
||||
continuation);
|
||||
}
|
||||
|
||||
void TextEditorView::prepare_for_write() {
|
||||
@@ -533,31 +568,16 @@ void TextEditorView::prepare_for_write() {
|
||||
|
||||
// Copy to temp file on write.
|
||||
has_temp_file_ = true;
|
||||
delete_temp_file();
|
||||
copy_file(path_, get_temp_path());
|
||||
file_->assume_file(get_temp_path());
|
||||
}
|
||||
|
||||
void TextEditorView::delete_temp_file() const {
|
||||
auto temp_path = get_temp_path();
|
||||
if (!temp_path.empty()) {
|
||||
delete_file(temp_path);
|
||||
}
|
||||
delete_temp_file(path_);
|
||||
copy_file(path_, get_temp_path(path_));
|
||||
file_->assume_file(get_temp_path(path_));
|
||||
}
|
||||
|
||||
void TextEditorView::save_temp_file() {
|
||||
if (file_dirty_) {
|
||||
delete_file(path_);
|
||||
copy_file(get_temp_path(), path_);
|
||||
ui::save_temp_file(path_);
|
||||
file_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
fs::path TextEditorView::get_temp_path() const {
|
||||
if (!path_.empty())
|
||||
return path_ + "~";
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -229,10 +229,7 @@ class TextEditorView : public View {
|
||||
void show_save_prompt(std::function<void()> continuation);
|
||||
|
||||
void prepare_for_write();
|
||||
void create_temp_file() const;
|
||||
void delete_temp_file() const;
|
||||
void save_temp_file();
|
||||
std::filesystem::path get_temp_path() const;
|
||||
|
||||
NavigationView& nav_;
|
||||
std::unique_ptr<FileWrapper> file_{};
|
||||
|
||||
@@ -31,6 +31,16 @@
|
||||
|
||||
#include "core_control.hpp"
|
||||
|
||||
/* Set true to enable additional checks to ensure
|
||||
* M4 and M0 are synchronized before passing messages. */
|
||||
static constexpr bool enforce_core_sync = true;
|
||||
|
||||
/* Set true to enable check for baseband messages getting stuck.
|
||||
* This implies the baseband thread is not dequeuing and has probably stalled.
|
||||
* NB: This check adds a small amout of overhead to the message sending code
|
||||
* and may impact application perf if it is sending a lot of messages. */
|
||||
static constexpr bool check_for_message_hang = true;
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace baseband {
|
||||
@@ -40,8 +50,18 @@ static void send_message(const Message* const message) {
|
||||
// another message is present before setting new message.
|
||||
shared_memory.baseband_message = message;
|
||||
creg::m0apptxevent::assert_event();
|
||||
while (shared_memory.baseband_message)
|
||||
;
|
||||
|
||||
if constexpr (check_for_message_hang) {
|
||||
auto count = UINT32_MAX;
|
||||
while (shared_memory.baseband_message && --count)
|
||||
/* spin */;
|
||||
|
||||
if (count == 0)
|
||||
chDbgPanic("Baseband Send Fail");
|
||||
} else {
|
||||
while (shared_memory.baseband_message)
|
||||
/* spin */;
|
||||
}
|
||||
}
|
||||
|
||||
void AMConfig::apply() const {
|
||||
@@ -276,17 +296,28 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
|
||||
|
||||
static bool baseband_image_running = false;
|
||||
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag) {
|
||||
void run_image(const spi_flash::image_tag_t image_tag) {
|
||||
if (baseband_image_running) {
|
||||
chDbgPanic("BBRunning");
|
||||
}
|
||||
|
||||
creg::m4txevent::clear();
|
||||
shared_memory.clear_baseband_ready();
|
||||
|
||||
m4_init(image_tag, portapack::memory::map::m4_code, false);
|
||||
m4_init(image_tag, memory::map::m4_code, false);
|
||||
baseband_image_running = true;
|
||||
|
||||
creg::m4txevent::enable();
|
||||
|
||||
if constexpr (enforce_core_sync) {
|
||||
// Wait up to 3 seconds for baseband to start handling events.
|
||||
auto count = 3'000u;
|
||||
while (!shared_memory.baseband_ready && --count)
|
||||
chThdSleepMilliseconds(1);
|
||||
|
||||
if (count == 0)
|
||||
chDbgPanic("Baseband Sync Fail");
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
|
||||
@@ -20,21 +20,20 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "core_control.hpp"
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "message.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "core_control.hpp"
|
||||
#include "hal.h"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "lz4.h"
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to, const bool full_reset) {
|
||||
const portapack::spi_flash::chunk_t* chunk = reinterpret_cast<const portapack::spi_flash::chunk_t*>(portapack::spi_flash::images.base());
|
||||
using namespace lpc43xx;
|
||||
using namespace portapack;
|
||||
|
||||
void m4_init(const spi_flash::image_tag_t image_tag, const memory::region_t to, const bool full_reset) {
|
||||
const spi_flash::chunk_t* chunk = reinterpret_cast<const spi_flash::chunk_t*>(spi_flash::images.base());
|
||||
while (chunk->tag) {
|
||||
if (chunk->tag == image_tag) {
|
||||
const void* src = &chunk->data[0];
|
||||
@@ -49,9 +48,8 @@ void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack:
|
||||
LPC_CREG->M4MEMMAP = to.base();
|
||||
|
||||
/* Reset M4 core and optionally all peripherals */
|
||||
LPC_RGU->RESET_CTRL[0] = (full_reset) ? (1 << 1) // PERIPH_RST
|
||||
: (1 << 13) // M4_RST
|
||||
;
|
||||
LPC_RGU->RESET_CTRL[0] = (full_reset) ? (1 << 1) // PERIPH_RST
|
||||
: (1 << 13); // M4_RST
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __FIELD_BINDER_H__
|
||||
#define __FIELD_BINDER_H__
|
||||
|
||||
/* There's a common pattern that appears in applications that use
|
||||
* field widgets. The field value is set, on_change and on_select
|
||||
* handlers are registered. on_select will read the value and
|
||||
* set the new value once changed. on_change will be called which
|
||||
* will write the new value to the backing variable. */
|
||||
|
||||
struct binding_traits {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr auto get_traits() {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename TField, typename TTraits = get_traits<TField>()>
|
||||
class FieldBinding {
|
||||
};
|
||||
|
||||
#endif /*__FIELD_BINDER_H__*/
|
||||
@@ -21,12 +21,18 @@
|
||||
*/
|
||||
|
||||
#include "file.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
#include <locale>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path c32_ext{u".C32"};
|
||||
|
||||
Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BYTE mode) {
|
||||
auto result = f_open(&f, reinterpret_cast<const TCHAR*>(filename.c_str()), mode);
|
||||
if (result == FR_OK) {
|
||||
@@ -45,8 +51,11 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
|
||||
}
|
||||
}
|
||||
|
||||
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only) {
|
||||
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
|
||||
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
|
||||
if (create)
|
||||
mode |= FA_OPEN_ALWAYS;
|
||||
|
||||
return open_fatfs(filename, mode);
|
||||
}
|
||||
|
||||
@@ -504,6 +513,21 @@ bool path_iequal(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_cxx_capture_file(const path& filename) {
|
||||
auto ext = filename.extension();
|
||||
return path_iequal(c8_ext, ext) || path_iequal(c16_ext, ext) || path_iequal(c32_ext, ext);
|
||||
}
|
||||
|
||||
uint8_t capture_file_sample_size(const path& filename) {
|
||||
if (path_iequal(filename.extension(), c8_ext))
|
||||
return sizeof(complex8_t);
|
||||
if (path_iequal(filename.extension(), c16_ext))
|
||||
return sizeof(complex16_t);
|
||||
if (path_iequal(filename.extension(), c32_ext))
|
||||
return sizeof(complex32_t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
const std::filesystem::path& path,
|
||||
const std::filesystem::path& wild)
|
||||
|
||||
@@ -168,6 +168,8 @@ 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);
|
||||
bool is_cxx_capture_file(const path& filename);
|
||||
uint8_t capture_file_sample_size(const path& filename);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
@@ -319,7 +321,7 @@ class File {
|
||||
File& operator=(const File&) = delete;
|
||||
|
||||
// TODO: Return Result<>.
|
||||
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true);
|
||||
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true, bool create = false);
|
||||
Optional<Error> append(const std::filesystem::path& filename);
|
||||
Optional<Error> create(const std::filesystem::path& filename);
|
||||
|
||||
|
||||
@@ -463,9 +463,9 @@ class FileWrapper : public BufferWrapper<File, 64> {
|
||||
template <typename T>
|
||||
using Result = File::Result<T>;
|
||||
using Error = File::Error;
|
||||
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path) {
|
||||
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path, bool create = false) {
|
||||
auto fw = std::unique_ptr<FileWrapper>(new FileWrapper());
|
||||
auto error = fw->file_.open(path, /*read_only*/ false);
|
||||
auto error = fw->file_.open(path, /*read_only*/ false, create);
|
||||
|
||||
if (error)
|
||||
return *error;
|
||||
|
||||
@@ -22,92 +22,16 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
using option_t = ui::OptionsField::option_t;
|
||||
using options_t = ui::OptionsField::options_t;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace tonekey;
|
||||
using namespace ui;
|
||||
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
|
||||
extern options_t freqman_modulations;
|
||||
extern options_t freqman_bandwidths[4];
|
||||
extern options_t freqman_steps;
|
||||
extern options_t freqman_steps_short;
|
||||
|
||||
extern const option_t* find_by_index(const options_t& options, freqman_index_t index);
|
||||
|
||||
// TODO: remove in favor of FreqmanDB
|
||||
/* Freqman file handling. */
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
|
||||
return parse_freqman_file(get_freqman_path(file_stem), db, options);
|
||||
}
|
||||
|
||||
bool delete_freqman_file(const std::string& file_stem) {
|
||||
delete_file(get_freqman_path(file_stem));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
|
||||
auto path = get_freqman_path(file_stem);
|
||||
delete_file(path);
|
||||
|
||||
File freqman_file;
|
||||
auto error = freqman_file.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
for (size_t n = 0; n < db.size(); n++)
|
||||
freqman_file.write_line(to_freqman_string(*db[n]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create_freqman_file(const std::string& file_stem) {
|
||||
auto fs_error = make_new_file(get_freqman_path(file_stem));
|
||||
return fs_error.ok();
|
||||
}
|
||||
|
||||
/* Set options. */
|
||||
void freqman_set_modulation_option(OptionsField& option) {
|
||||
option.set_options(freqman_modulations);
|
||||
}
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||
if (is_valid(modulation))
|
||||
option.set_options(freqman_bandwidths[modulation]);
|
||||
}
|
||||
|
||||
void freqman_set_step_option(OptionsField& option) {
|
||||
option.set_options(freqman_steps);
|
||||
}
|
||||
|
||||
void freqman_set_step_option_short(OptionsField& option) {
|
||||
option.set_options(freqman_steps_short);
|
||||
}
|
||||
|
||||
/* Option value lookup. */
|
||||
// TODO: use Optional instead of magic values.
|
||||
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation) {
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->second;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth) {
|
||||
if (modulation < freqman_modulations.size()) {
|
||||
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
|
||||
return opt->second;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step) {
|
||||
if (auto opt = find_by_index(freqman_steps, step))
|
||||
return opt->second;
|
||||
|
||||
@@ -25,13 +25,7 @@
|
||||
#ifndef __FREQMAN_H__
|
||||
#define __FREQMAN_H__
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "file.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
// Defined for back-compat.
|
||||
#define FREQMAN_MAX_PER_FILE freqman_default_max_entries
|
||||
@@ -50,21 +44,7 @@ enum freqman_entry_modulation : uint8_t {
|
||||
SPEC_MODULATION
|
||||
};
|
||||
|
||||
// TODO: Replace with FreqmanDB.
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
|
||||
bool delete_freqman_file(const std::string& file_stem);
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db);
|
||||
bool create_freqman_file(const std::string& file_stem);
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
|
||||
void freqman_set_modulation_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option_short(ui::OptionsField& option);
|
||||
void freqman_set_tone_option(ui::OptionsField& option);
|
||||
|
||||
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
|
||||
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation);
|
||||
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth);
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step);
|
||||
|
||||
#endif /*__FREQMAN_H__*/
|
||||
|
||||
@@ -40,10 +40,6 @@ namespace fs = std::filesystem;
|
||||
const std::filesystem::path freqman_dir{u"/FREQMAN"};
|
||||
const std::filesystem::path freqman_extension{u".TXT"};
|
||||
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem) {
|
||||
return freqman_dir / stem + freqman_extension;
|
||||
}
|
||||
|
||||
// NB: Don't include UI headers to keep this code unit testable.
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
@@ -97,6 +93,7 @@ options_t freqman_bandwidths[4] = {
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps = {
|
||||
{"0.1kHz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
@@ -116,6 +113,7 @@ options_t freqman_steps = {
|
||||
{"1MHz ", 1000000},
|
||||
};
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps_short = {
|
||||
{"0.1kHz", 100},
|
||||
{"1kHz", 1000},
|
||||
@@ -162,6 +160,27 @@ const option_t* find_by_index(const options_t& options, freqman_index_t index) {
|
||||
*}
|
||||
*/
|
||||
|
||||
bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
|
||||
auto equal = lhs.type == rhs.type &&
|
||||
lhs.frequency_a == rhs.frequency_a &&
|
||||
lhs.description == rhs.description &&
|
||||
lhs.modulation == rhs.modulation &&
|
||||
lhs.bandwidth == rhs.bandwidth;
|
||||
|
||||
if (!equal)
|
||||
return false;
|
||||
|
||||
if (lhs.type == freqman_type::Range) {
|
||||
equal = lhs.frequency_b == rhs.frequency_b &&
|
||||
lhs.step == rhs.step;
|
||||
} else if (lhs.type == freqman_type::HamRadio) {
|
||||
equal = lhs.frequency_b == rhs.frequency_b &&
|
||||
lhs.tone == rhs.tone;
|
||||
}
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->first;
|
||||
@@ -188,6 +207,23 @@ std::string freqman_entry_get_step_string_short(freqman_index_t step) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem) {
|
||||
return freqman_dir / stem + freqman_extension;
|
||||
}
|
||||
|
||||
bool create_freqman_file(const std::string& file_stem) {
|
||||
auto fs_error = make_new_file(get_freqman_path(file_stem));
|
||||
return fs_error.ok();
|
||||
}
|
||||
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
|
||||
return parse_freqman_file(get_freqman_path(file_stem), db, options);
|
||||
}
|
||||
|
||||
void delete_freqman_file(const std::string& file_stem) {
|
||||
delete_file(get_freqman_path(file_stem));
|
||||
}
|
||||
|
||||
std::string pretty_string(const freqman_entry& entry, size_t max_length) {
|
||||
std::string str;
|
||||
|
||||
@@ -196,12 +232,15 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) {
|
||||
str = to_string_short_freq(entry.frequency_a) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::Range:
|
||||
str = to_string_dec_uint(entry.frequency_a / 1'000'000) + "M-" +
|
||||
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
|
||||
str = to_string_rounded_freq(entry.frequency_a, 1) + "M-" +
|
||||
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::HamRadio:
|
||||
str = "" + to_string_dec_uint(entry.frequency_a / 1'000'000) + "M," +
|
||||
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
|
||||
str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
|
||||
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::Raw:
|
||||
str = entry.description;
|
||||
break;
|
||||
default:
|
||||
str = "UNK:" + entry.description;
|
||||
@@ -244,8 +283,10 @@ std::string to_freqman_string(const freqman_entry& entry) {
|
||||
if (is_valid(entry.tone))
|
||||
append_field("c", tonekey::tone_key_value_string(entry.tone));
|
||||
break;
|
||||
case freqman_type::Raw:
|
||||
return entry.description;
|
||||
default:
|
||||
return {}; // TODO: Comment type with description?
|
||||
return {};
|
||||
};
|
||||
|
||||
if (is_valid(entry.modulation) && entry.modulation < freqman_modulations.size()) {
|
||||
@@ -332,55 +373,23 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
|
||||
}
|
||||
}
|
||||
|
||||
// No valid frequency combination was set.
|
||||
if (entry.type == freqman_type::Unknown)
|
||||
return false;
|
||||
|
||||
// Frequency A must be set for all types
|
||||
if (entry.frequency_a == 0)
|
||||
return false;
|
||||
|
||||
// Frequency B must be set for type Range or Ham Radio
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
|
||||
if (entry.frequency_b == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ranges should have frequencies A <= B.
|
||||
if (entry.type == freqman_type::Range) {
|
||||
if (entry.frequency_a > entry.frequency_b)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Consider additional validation:
|
||||
// - Tone only on HamRadio.
|
||||
// - Fail on failed parse_int.
|
||||
// - Fail if bandwidth set before modulation.
|
||||
|
||||
return true;
|
||||
return is_valid(entry);
|
||||
}
|
||||
|
||||
// TODO: Use FreqmanDB iterator.
|
||||
bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_options options) {
|
||||
File f;
|
||||
auto error = f.open(path);
|
||||
if (error)
|
||||
FreqmanDB freqman_db;
|
||||
freqman_db.set_read_raw(false); // Don't return malformed lines.
|
||||
if (!freqman_db.open(path))
|
||||
return false;
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
auto line_count = count_lines(reader);
|
||||
|
||||
// Attempt to avoid a re-alloc if possible.
|
||||
db.clear();
|
||||
db.reserve(line_count);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
freqman_entry entry{};
|
||||
if (!parse_freqman_entry(line, entry))
|
||||
continue;
|
||||
db.reserve(freqman_db.entry_count());
|
||||
|
||||
for (auto entry : freqman_db) {
|
||||
// Filter by entry type.
|
||||
if ((entry.type == freqman_type::Single && !options.load_freqs) ||
|
||||
if (entry.type == freqman_type::Unknown ||
|
||||
(entry.type == freqman_type::Single && !options.load_freqs) ||
|
||||
(entry.type == freqman_type::Range && !options.load_ranges) ||
|
||||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) {
|
||||
continue;
|
||||
@@ -406,10 +415,40 @@ bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_optio
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_valid(const freqman_entry& entry) {
|
||||
// No valid frequency combination was set.
|
||||
if (entry.type == freqman_type::Unknown)
|
||||
return false;
|
||||
|
||||
// Frequency A must be set for all types
|
||||
if (entry.frequency_a == 0)
|
||||
return false;
|
||||
|
||||
// Frequency B must be set for type Range or Ham Radio
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
|
||||
if (entry.frequency_b == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ranges should have frequencies A <= B.
|
||||
if (entry.type == freqman_type::Range) {
|
||||
if (entry.frequency_a > entry.frequency_b)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Consider additional validation:
|
||||
// - Tone only on HamRadio.
|
||||
// - Step only on Range
|
||||
// - Fail on failed parse_int.
|
||||
// - Fail if bandwidth set before modulation.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* FreqmanDB ***********************************/
|
||||
|
||||
bool FreqmanDB::open(const std::filesystem::path& path) {
|
||||
auto result = FileWrapper::open(path);
|
||||
bool FreqmanDB::open(const std::filesystem::path& path, bool create) {
|
||||
auto result = FileWrapper::open(path, create);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
@@ -421,38 +460,61 @@ void FreqmanDB::close() {
|
||||
wrapper_.reset();
|
||||
}
|
||||
|
||||
freqman_entry FreqmanDB::operator[](FileWrapper::Line line) const {
|
||||
auto length = wrapper_->line_length(line);
|
||||
auto line_text = wrapper_->get_text(line, 0, length);
|
||||
freqman_entry FreqmanDB::operator[](Index index) const {
|
||||
auto length = wrapper_->line_length(index);
|
||||
auto line_text = wrapper_->get_text(index, 0, length);
|
||||
|
||||
if (line_text) {
|
||||
freqman_entry entry;
|
||||
if (parse_freqman_entry(*line_text, entry))
|
||||
return entry;
|
||||
else if (read_raw_) {
|
||||
entry.type = freqman_type::Raw;
|
||||
entry.description = trim(*line_text);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void FreqmanDB::insert_entry(const freqman_entry& entry, FileWrapper::Line line) {
|
||||
// TODO: Can be more efficient.
|
||||
line = clip<uint32_t>(line, 0u, entry_count());
|
||||
wrapper_->insert_line(line);
|
||||
replace_entry(line, entry);
|
||||
void FreqmanDB::insert_entry(Index index, const freqman_entry& entry) {
|
||||
index = clip<uint32_t>(index, 0u, entry_count());
|
||||
wrapper_->insert_line(index);
|
||||
replace_entry(index, entry);
|
||||
}
|
||||
|
||||
void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry) {
|
||||
auto range = wrapper_->line_range(line);
|
||||
void FreqmanDB::append_entry(const freqman_entry& entry) {
|
||||
insert_entry(entry_count(), entry);
|
||||
}
|
||||
|
||||
void FreqmanDB::replace_entry(Index index, const freqman_entry& entry) {
|
||||
auto range = wrapper_->line_range(index);
|
||||
if (!range)
|
||||
return; // TODO: Message?
|
||||
return;
|
||||
|
||||
// Don't overwrite the '\n'.
|
||||
range->end--;
|
||||
wrapper_->replace_range(*range, to_freqman_string(entry));
|
||||
}
|
||||
|
||||
void FreqmanDB::delete_entry(FileWrapper::Line line) {
|
||||
wrapper_->delete_line(line);
|
||||
void FreqmanDB::delete_entry(Index index) {
|
||||
wrapper_->delete_line(index);
|
||||
}
|
||||
|
||||
bool FreqmanDB::delete_entry(const freqman_entry& entry) {
|
||||
auto it = find_entry(entry);
|
||||
if (it == end())
|
||||
return false;
|
||||
|
||||
delete_entry(it.index());
|
||||
return true;
|
||||
}
|
||||
|
||||
FreqmanDB::iterator FreqmanDB::find_entry(const freqman_entry& entry) {
|
||||
return find_entry([&entry](const auto& other) {
|
||||
return entry == other;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t FreqmanDB::entry_count() const {
|
||||
|
||||
@@ -52,13 +52,11 @@ constexpr bool is_invalid(freqman_index_t index) {
|
||||
return index == freqman_invalid_index;
|
||||
}
|
||||
|
||||
/* Gets the full path for a given file stem (no extension). */
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem);
|
||||
|
||||
enum class freqman_type : uint8_t {
|
||||
Single, // f=
|
||||
Range, // a=,b=
|
||||
HamRadio, // r=,t=
|
||||
Raw, // line content in description
|
||||
Unknown,
|
||||
};
|
||||
|
||||
@@ -140,9 +138,9 @@ enum class freqman_type : uint8_t {
|
||||
|
||||
/* Freqman Entry *******************************/
|
||||
struct freqman_entry {
|
||||
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{0}; // 'd=desc'
|
||||
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{}; // 'd=desc'
|
||||
freqman_type type{freqman_type::Unknown};
|
||||
freqman_index_t modulation{freqman_invalid_index};
|
||||
freqman_index_t bandwidth{freqman_invalid_index};
|
||||
@@ -150,6 +148,8 @@ struct freqman_entry {
|
||||
freqman_index_t tone{freqman_invalid_index};
|
||||
};
|
||||
|
||||
bool operator==(const freqman_entry& lhs, const freqman_entry& rhs);
|
||||
|
||||
// TODO: These shouldn't be exported.
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation);
|
||||
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth);
|
||||
@@ -172,6 +172,12 @@ struct freqman_load_options {
|
||||
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
|
||||
using freqman_db = std::vector<freqman_entry_ptr>;
|
||||
|
||||
/* Gets the full path for a given file stem (no extension). */
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem);
|
||||
bool create_freqman_file(const std::string& file_stem);
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
|
||||
void delete_freqman_file(const std::string& file_stem);
|
||||
|
||||
/* Gets a pretty string representation for an entry. */
|
||||
std::string pretty_string(const freqman_entry& item, size_t max_length = 30);
|
||||
|
||||
@@ -181,54 +187,94 @@ std::string to_freqman_string(const freqman_entry& entry);
|
||||
bool parse_freqman_entry(std::string_view str, freqman_entry& entry);
|
||||
bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqman_load_options options);
|
||||
|
||||
/* The tricky part of using the file directly is that there can be comments
|
||||
* and empty lines in the file. This messes up the 'count' calculation.
|
||||
* Either have to live with 'count' being an upper bound have the callers
|
||||
* know to expect that entries may be empty. */
|
||||
// NB: This won't apply implicit mod/bandwidth.
|
||||
// TODO: Reuse for parse_freqman_file
|
||||
/* Returns true if the entry is well-formed. */
|
||||
bool is_valid(const freqman_entry& entry);
|
||||
|
||||
/* API wrapper over a Freqman file. Provides CRUD operations
|
||||
* for freqman_entry instances that are read/written directly
|
||||
* to the underlying file. */
|
||||
class FreqmanDB {
|
||||
public:
|
||||
using Index = FileWrapper::Line;
|
||||
|
||||
/* NB: This iterator is very basic: forward only, read-only. */
|
||||
class iterator {
|
||||
public:
|
||||
iterator(FreqmanDB& db, FileWrapper::Offset line)
|
||||
: db_{db}, line_{line} {}
|
||||
iterator(FreqmanDB& db, Index index)
|
||||
: db_{db}, index_{index} {}
|
||||
iterator& operator++() {
|
||||
line_++;
|
||||
index_++;
|
||||
|
||||
if (index_ >= db_.entry_count())
|
||||
index_ = end_index;
|
||||
|
||||
return *this;
|
||||
}
|
||||
freqman_entry operator*() const {
|
||||
return db_[line_];
|
||||
return db_[index_];
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) {
|
||||
return &db_ == &other.db_ && index_ == other.index_;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) {
|
||||
return &db_ != &other.db_ || line_ != other.line_;
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Index index() const {
|
||||
return index_;
|
||||
}
|
||||
|
||||
/* Value indicating the 'end' iterator. */
|
||||
static constexpr Index end_index = (Index)-1;
|
||||
|
||||
private:
|
||||
FreqmanDB& db_;
|
||||
FileWrapper::Line line_;
|
||||
Index index_;
|
||||
};
|
||||
|
||||
bool open(const std::filesystem::path& path);
|
||||
bool open(const std::filesystem::path& path, bool create = false);
|
||||
void close();
|
||||
freqman_entry operator[](FileWrapper::Line line) const;
|
||||
void insert_entry(const freqman_entry& entry, FileWrapper::Line line);
|
||||
void replace_entry(FileWrapper::Line line, const freqman_entry& entry);
|
||||
void delete_entry(FileWrapper::Line line);
|
||||
|
||||
freqman_entry operator[](Index index) const;
|
||||
void insert_entry(Index index, const freqman_entry& entry);
|
||||
void append_entry(const freqman_entry& entry);
|
||||
void replace_entry(Index index, const freqman_entry& entry);
|
||||
void delete_entry(Index index);
|
||||
bool delete_entry(const freqman_entry& entry);
|
||||
|
||||
template <typename Fn>
|
||||
iterator find_entry(const Fn& predicate) {
|
||||
// TODO: use std::find, but need to make the iterator compliant.
|
||||
auto it = begin();
|
||||
const auto it_end = end();
|
||||
|
||||
while (it != it_end) {
|
||||
if (predicate(*it))
|
||||
return it;
|
||||
++it;
|
||||
}
|
||||
|
||||
return it_end;
|
||||
}
|
||||
iterator find_entry(const freqman_entry& entry);
|
||||
|
||||
uint32_t entry_count() const;
|
||||
bool empty() const;
|
||||
|
||||
/* When true, Raw entries are returned instead of Unknown. */
|
||||
void set_read_raw(bool v) { read_raw_ = v; }
|
||||
iterator begin() {
|
||||
return {*this, 0};
|
||||
}
|
||||
iterator end() {
|
||||
return {*this, entry_count()};
|
||||
return {*this, iterator::end_index};
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileWrapper> wrapper_{};
|
||||
bool read_raw_{true};
|
||||
};
|
||||
|
||||
#endif /* __FREQMAN_DB_H__ */
|
||||
@@ -32,66 +32,24 @@
|
||||
// 12 degrees of rotation.
|
||||
//
|
||||
// For each encoder "pulse" there are 4 state transitions, and we can choose
|
||||
// between looking at all of them (high sensitivity), half of them (medium/default),
|
||||
// or one quarter of them (low sensitivity).
|
||||
static const int8_t transition_map[][16] = {
|
||||
// Normal (Medium) Sensitivity -- default
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Low Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
0, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
0, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// High Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// how many transitions are needed before movement is registered
|
||||
static const int8_t transition_map[16] = {
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
};
|
||||
|
||||
int_fast8_t Encoder::update(
|
||||
@@ -102,6 +60,13 @@ int_fast8_t Encoder::update(
|
||||
state <<= 1;
|
||||
state |= phase_1;
|
||||
|
||||
// dial sensitivity setting is stored in pmem
|
||||
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
|
||||
int_fast8_t retval = transition_map[state & 0xf];
|
||||
|
||||
transition_count += retval;
|
||||
if (abs(transition_count) > portapack::persistent_memory::config_encoder_dial_sensitivity())
|
||||
transition_count = 0;
|
||||
else
|
||||
retval = 0;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class Encoder {
|
||||
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
int_fast8_t transition_count{0};
|
||||
};
|
||||
|
||||
#endif /*__ENCODER_H__*/
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "io_convert.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext = u".C8";
|
||||
static const fs::path c32_ext = u".C32";
|
||||
|
||||
namespace file_convert {
|
||||
|
||||
// Convert buffer contents from c16 to c8.
|
||||
// Same buffer used for input & output; input size is bytes; output size is bytes/2.
|
||||
void c16_to_c8(const void* buffer, File::Size bytes) {
|
||||
complex16_t* src = (complex16_t*)buffer;
|
||||
complex8_t* dest = (complex8_t*)buffer;
|
||||
|
||||
// Shift isn't used here because it would amplify noise at center freq since it's a signed number
|
||||
// i.e. ((-1 >> 8) << 8) = -256, whereas (-1 / 256) * 256 = 0
|
||||
for (File::Size i = 0; i < bytes / sizeof(complex16_t); i++) {
|
||||
auto re_out = src[i].real() / 256;
|
||||
auto im_out = src[i].imag() / 256;
|
||||
dest[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert c8 buffer to c16 buffer.
|
||||
// Same buffer used for input & output; input size is bytes; output size is 2*bytes.
|
||||
void c8_to_c16(const void* buffer, File::Size bytes) {
|
||||
complex8_t* src = (complex8_t*)buffer;
|
||||
complex16_t* dest = (complex16_t*)buffer;
|
||||
uint32_t i = bytes / sizeof(complex8_t);
|
||||
|
||||
if (i != 0) {
|
||||
do {
|
||||
i--;
|
||||
auto re_out = (int16_t)src[i].real() * 256; // C8 to C16 conversion;
|
||||
auto im_out = (int16_t)src[i].imag() * 256; // Can't shift signed numbers left, so using multiply
|
||||
dest[i] = {(int16_t)re_out, (int16_t)im_out};
|
||||
} while (i != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert c32 buffer to c16 buffer.
|
||||
// Same buffer used for input & output; input size is bytes; output size is bytes/2.
|
||||
void c32_to_c16(const void* buffer, File::Size bytes) {
|
||||
complex32_t* src = (complex32_t*)buffer;
|
||||
complex16_t* dest = (complex16_t*)buffer;
|
||||
|
||||
// Shift isn't used here because it would amplify noise at center freq since it's a signed number
|
||||
// i.e. ((-1 >> 16) << 16) = -65536, whereas (-1 / 65536) * 65536 = 0
|
||||
for (File::Size i = 0; i < bytes / sizeof(complex32_t); i++) {
|
||||
auto re_out = src[i].real() / 65536;
|
||||
auto im_out = src[i].imag() / 65536;
|
||||
dest[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace file_convert */
|
||||
|
||||
// Automatically enables C8/C16 or C32/C16 conversion based on file extension
|
||||
Optional<File::Error> FileConvertReader::open(const std::filesystem::path& filename) {
|
||||
convert_c8_to_c16 = path_iequal(filename.extension(), c8_ext);
|
||||
convert_c32_to_c16 = path_iequal(filename.extension(), c32_ext);
|
||||
return file_.open(filename);
|
||||
}
|
||||
|
||||
// If C8 conversion enabled, half the number of bytes are read from the file & expanded to fill the whole buffer.
|
||||
// If C32 conversion enabled, the full byte count is read from the file, and compressed to half the buffer size.
|
||||
File::Result<File::Size> FileConvertReader::read(void* const buffer, const File::Size bytes) {
|
||||
auto read_result = file_.read(buffer, convert_c8_to_c16 ? bytes / 2 : bytes);
|
||||
if (read_result.is_ok()) {
|
||||
if (convert_c8_to_c16) {
|
||||
file_convert::c8_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() * 2;
|
||||
} else if (convert_c32_to_c16) {
|
||||
file_convert::c32_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() / 2;
|
||||
}
|
||||
bytes_read_ += read_result.value();
|
||||
}
|
||||
return read_result;
|
||||
}
|
||||
|
||||
// Automatically enables C8/C16 conversion based on file extension
|
||||
Optional<File::Error> FileConvertWriter::create(const std::filesystem::path& filename) {
|
||||
convert_c16_to_c8 = path_iequal(filename.extension(), c8_ext);
|
||||
return file_.create(filename);
|
||||
}
|
||||
|
||||
// If C8 conversion is enabled, half the number of bytes are written to the file.
|
||||
File::Result<File::Size> FileConvertWriter::write(const void* const buffer, const File::Size bytes) {
|
||||
if (convert_c16_to_c8) {
|
||||
file_convert::c16_to_c8(buffer, bytes);
|
||||
}
|
||||
auto write_result = file_.write(buffer, convert_c16_to_c8 ? bytes / 2 : bytes);
|
||||
if (write_result.is_ok()) {
|
||||
if (convert_c16_to_c8) {
|
||||
write_result = write_result.value() * 2;
|
||||
}
|
||||
bytes_written_ += write_result.value();
|
||||
}
|
||||
return write_result;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "io_file.hpp"
|
||||
|
||||
#include "io.hpp"
|
||||
#include "file.hpp"
|
||||
#include "optional.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace file_convert {
|
||||
|
||||
void c8_to_c16(const void* buffer, File::Size bytes);
|
||||
void c32_to_c16(const void* buffer, File::Size bytes);
|
||||
void c16_to_c8(const void* buffer, File::Size bytes);
|
||||
|
||||
} /* namespace file_convert */
|
||||
|
||||
class FileConvertReader : public stream::Reader {
|
||||
public:
|
||||
FileConvertReader() = default;
|
||||
|
||||
FileConvertReader(const FileConvertReader&) = delete;
|
||||
FileConvertReader& operator=(const FileConvertReader&) = delete;
|
||||
FileConvertReader(FileConvertReader&& file) = delete;
|
||||
FileConvertReader& operator=(FileConvertReader&&) = delete;
|
||||
|
||||
Optional<File::Error> open(const std::filesystem::path& filename);
|
||||
|
||||
File::Result<File::Size> read(void* const buffer, const File::Size bytes) override;
|
||||
const File& file() const& { return file_; }
|
||||
|
||||
bool convert_c8_to_c16{};
|
||||
bool convert_c32_to_c16{};
|
||||
|
||||
protected:
|
||||
File file_{};
|
||||
uint64_t bytes_read_{0};
|
||||
};
|
||||
|
||||
class FileConvertWriter : public stream::Writer {
|
||||
public:
|
||||
FileConvertWriter() = default;
|
||||
|
||||
FileConvertWriter(const FileConvertWriter&) = delete;
|
||||
FileConvertWriter& operator=(const FileConvertWriter&) = delete;
|
||||
FileConvertWriter(FileConvertWriter&& file) = delete;
|
||||
FileConvertWriter& operator=(FileConvertWriter&&) = delete;
|
||||
|
||||
Optional<File::Error> create(const std::filesystem::path& filename);
|
||||
|
||||
File::Result<File::Size> write(const void* const buffer, const File::Size bytes) override;
|
||||
const File& file() const& { return file_; }
|
||||
|
||||
bool convert_c16_to_c8{};
|
||||
|
||||
protected:
|
||||
File file_{};
|
||||
uint64_t bytes_written_{0};
|
||||
};
|
||||
@@ -238,6 +238,7 @@ void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum) {
|
||||
|
||||
void set_baseband_rate(const uint32_t rate) {
|
||||
portapack::clock_manager.set_sampling_frequency(rate);
|
||||
// TODO: actually set baseband too?
|
||||
}
|
||||
|
||||
void set_antenna_bias(const bool on) {
|
||||
|
||||
@@ -162,13 +162,47 @@ std::string to_string_decimal(float decimal, int8_t precision) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// right-justified frequency in Hz, always 10 characters
|
||||
std::string to_string_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
|
||||
std::string final_str{""};
|
||||
|
||||
if (f < 1000000)
|
||||
final_str = to_string_dec_int(f, 10, ' ');
|
||||
else
|
||||
final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
|
||||
|
||||
return final_str;
|
||||
}
|
||||
|
||||
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
|
||||
std::string to_string_short_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int((f / 100) % 10000, 4, '0');
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
|
||||
return final_str;
|
||||
}
|
||||
|
||||
// non-justified non-padded frequency in MHz, rounded to specified number of decimal places
|
||||
std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
|
||||
std::string final_str{""};
|
||||
static constexpr uint32_t pow10[7] = {
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
};
|
||||
|
||||
if (precision < 1) {
|
||||
final_str = to_string_dec_uint64(f / 1000000);
|
||||
} else {
|
||||
if (precision > 6)
|
||||
precision = 6;
|
||||
|
||||
uint32_t divisor = pow10[6 - precision];
|
||||
|
||||
final_str = to_string_dec_uint64(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
}
|
||||
return final_str;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ std::string to_string_decimal(float decimal, int8_t precision);
|
||||
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
||||
std::string to_string_hex_array(uint8_t* const array, const int32_t l = 0);
|
||||
|
||||
// NB: These pad-left and don't work correctly for values less than 1M.
|
||||
std::string to_string_freq(const uint64_t f);
|
||||
std::string to_string_short_freq(const uint64_t f);
|
||||
std::string to_string_rounded_freq(const uint64_t f, int8_t precision);
|
||||
std::string to_string_time_ms(const uint32_t ms);
|
||||
|
||||
std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format = YMDHMS);
|
||||
|
||||
@@ -56,16 +56,19 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
auto text = std::string{};
|
||||
auto index = start_index_ + offset;
|
||||
auto line_position = rect.location() + Point{4, 1 + (int)offset * char_height};
|
||||
|
||||
// Highlight the selected item.
|
||||
auto style = (offset == selected_index_) ? &Styles::bg_white : base_style;
|
||||
auto is_selected = offset == selected_index_;
|
||||
auto style = base_style;
|
||||
|
||||
if (index < db_->entry_count()) {
|
||||
auto entry = (*db_)[index];
|
||||
// db_ is directly backed by a file, so invalid lines cannot be
|
||||
// pre-filtered. Just show an empty 'slot' in this case.
|
||||
// pre-filtered. Show an empty entry if 'Unknown'.
|
||||
if (entry.type != freqman_type::Unknown)
|
||||
text = pretty_string(entry, line_max_length);
|
||||
|
||||
// Otherwise, if 'Raw' indicate an invalid entry by color.
|
||||
if (entry.type == freqman_type::Raw)
|
||||
style = &Styles::light_grey;
|
||||
}
|
||||
|
||||
// Pad right with ' ' so trailing chars are cleaned up.
|
||||
@@ -73,7 +76,8 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
if (text.length() < line_max_length)
|
||||
text.resize(line_max_length, ' ');
|
||||
|
||||
painter.draw_string(line_position, *style, text);
|
||||
painter.draw_string(
|
||||
line_position, (is_selected ? style->invert() : *style), text);
|
||||
}
|
||||
|
||||
// Draw a bounding rectangle when focused.
|
||||
|
||||
@@ -308,13 +308,25 @@ WaterfallView::WaterfallView(const bool cursor) {
|
||||
}
|
||||
|
||||
void WaterfallView::on_show() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_start();
|
||||
start();
|
||||
}
|
||||
|
||||
void WaterfallView::on_hide() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_stop();
|
||||
stop();
|
||||
}
|
||||
|
||||
void WaterfallView::start() {
|
||||
if (!running_) {
|
||||
baseband::spectrum_streaming_start();
|
||||
running_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallView::stop() {
|
||||
if (running_) {
|
||||
baseband::spectrum_streaming_stop();
|
||||
running_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallView::show_audio_spectrum_view(const bool show) {
|
||||
|
||||
@@ -131,11 +131,14 @@ class WaterfallView : public View {
|
||||
WaterfallView& operator=(const WaterfallView&) = delete;
|
||||
WaterfallView& operator=(WaterfallView&&) = delete;
|
||||
|
||||
// TODO: remove these, use start/stop directly instead.
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void show_audio_spectrum_view(const bool show);
|
||||
|
||||
private:
|
||||
@@ -147,6 +150,7 @@ class WaterfallView : public View {
|
||||
|
||||
WaterfallWidget waterfall_widget{};
|
||||
FrequencyScale frequency_scale{};
|
||||
bool running_{false};
|
||||
|
||||
ChannelSpectrumFIFO* channel_fifo{nullptr};
|
||||
AudioSpectrum* audio_spectrum_data{nullptr};
|
||||
|
||||
@@ -48,7 +48,7 @@ class TextEntryView : public View {
|
||||
void char_add(const char c);
|
||||
void char_delete();
|
||||
|
||||
TextField text_input;
|
||||
TextEdit text_input;
|
||||
Button button_ok{
|
||||
{22 * 8, 33 * 8, 7 * 8, 32},
|
||||
"OK"};
|
||||
|
||||
@@ -497,13 +497,8 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{
|
||||
"ADS-B",
|
||||
Color::green(),
|
||||
&bitmap_icon_adsb,
|
||||
[&nav]() { nav.push<ADSBRxView>(); },
|
||||
},
|
||||
//{ "ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }, },
|
||||
{"ADS-B", Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBRxView>(); }},
|
||||
//{ "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>(); }},
|
||||
|
||||
@@ -26,6 +26,7 @@ using namespace portapack;
|
||||
|
||||
#include "io_file.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "io_convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
@@ -194,6 +195,7 @@ void RecordView::start() {
|
||||
}
|
||||
} break;
|
||||
|
||||
case FileType::RawS8:
|
||||
case FileType::RawS16: {
|
||||
const auto metadata_file_error =
|
||||
write_metadata_file(get_metadata_path(base_path),
|
||||
@@ -204,8 +206,8 @@ void RecordView::start() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = std::make_unique<RawFileWriter>();
|
||||
auto create_error = p->create(base_path.replace_extension(u".C16"));
|
||||
auto p = std::make_unique<FileConvertWriter>();
|
||||
auto create_error = p->create(base_path.replace_extension((file_type == FileType::RawS8) ? u".C8" : u".C16"));
|
||||
if (create_error.is_valid()) {
|
||||
handle_error(create_error.value());
|
||||
} else {
|
||||
|
||||
@@ -40,8 +40,10 @@ class RecordView : public View {
|
||||
std::function<void(std::string)> on_error{};
|
||||
|
||||
enum FileType {
|
||||
RawS8 = 1,
|
||||
RawS16 = 2,
|
||||
WAV = 3,
|
||||
RawS32 = 3,
|
||||
WAV = 4,
|
||||
};
|
||||
|
||||
RecordView(
|
||||
@@ -57,6 +59,8 @@ class RecordView : public View {
|
||||
|
||||
void set_sampling_rate(const size_t new_sampling_rate);
|
||||
|
||||
void set_file_type(const FileType v) { file_type = v; }
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void on_hide() override;
|
||||
@@ -83,7 +87,7 @@ class RecordView : public View {
|
||||
|
||||
const std::filesystem::path filename_stem_pattern;
|
||||
const std::filesystem::path folder;
|
||||
const FileType file_type;
|
||||
FileType file_type;
|
||||
const size_t write_size;
|
||||
const size_t buffer_count;
|
||||
size_t sampling_rate{0};
|
||||
|
||||
@@ -19,21 +19,18 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "event_m4.hpp"
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "message_queue.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include "debug.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "message_queue.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
using namespace lpc43xx;
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(MAPP_IRQHandler) {
|
||||
@@ -61,6 +58,10 @@ void EventDispatcher::run() {
|
||||
|
||||
lpc43xx::creg::m0apptxevent::enable();
|
||||
|
||||
// Indicate to the M0 thread that
|
||||
// M4 is ready to receive message events.
|
||||
shared_memory.set_baseband_ready();
|
||||
|
||||
while (is_running) {
|
||||
const auto events = wait();
|
||||
dispatch(events);
|
||||
|
||||
@@ -23,9 +23,7 @@
|
||||
#include "proc_capture.hpp"
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
CaptureProcessor::CaptureProcessor() {
|
||||
@@ -33,9 +31,13 @@ CaptureProcessor::CaptureProcessor() {
|
||||
decim_1.configure(taps_200k_decim_1.taps, 131072);
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
ready = true;
|
||||
}
|
||||
|
||||
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!ready)
|
||||
return;
|
||||
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||
|
||||
@@ -46,9 +46,13 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
|
||||
private:
|
||||
// TODO: Repeated value needs to be transmitted from application side.
|
||||
size_t baseband_fs = 0;
|
||||
size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
// HACK: BasebandThread starts immediately and starts sending data to members
|
||||
// before they are initialized. This is a workaround to prevent uninit data problems.
|
||||
bool ready = false;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{NORMALPRIO + 10};
|
||||
|
||||
|
||||
@@ -42,34 +42,33 @@ ReplayProcessor::ReplayProcessor() {
|
||||
}
|
||||
|
||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 4MHz, 2048 samples */
|
||||
/* 2.6MHz, 2048 samples */
|
||||
|
||||
if (!configured) return;
|
||||
if (!configured || !stream) return;
|
||||
|
||||
// File data is in C16 format, we need C8
|
||||
// File samplerate is 500kHz, we're at 4MHz
|
||||
// iq_buffer can only be 512 C16 samples (RAM limitation)
|
||||
// To fill up the 2048-sample C8 buffer, we need:
|
||||
// 2048 samples * 2 bytes per sample = 4096 bytes
|
||||
// Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each
|
||||
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
|
||||
if (stream) { // sizeof(*buffer.p) = sizeof(C8) = 2*int8 = 2 bytes //buffer.count = 2048
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
bytes_read += stream->read(iq_buffer.p, bytes_to_read);
|
||||
}
|
||||
// File data is in C8 format, which is what we need
|
||||
// File samplerate is 2.6MHz, which is what we need
|
||||
// To fill up the 2048-sample C8 buffer @ 2 bytes per sample = 4096 bytes
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count);
|
||||
size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read);
|
||||
size_t samples_read_this_iteration = bytes_read_this_iteration / sizeof(*buffer.p);
|
||||
|
||||
// Fill and "stretch"
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
auto re_out = iq_buffer.p[i].real();
|
||||
auto im_out = iq_buffer.p[i].imag();
|
||||
buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
}
|
||||
bytes_read += bytes_read_this_iteration;
|
||||
|
||||
spectrum_samples += buffer.count;
|
||||
// NB: Couldn't we have just read the data into buffer.p to start with, or some DMA/cache coherency concern?
|
||||
//
|
||||
// for (size_t i = 0; i < buffer.count; i++) {
|
||||
// auto re_out = iq_buffer.p[i].real();
|
||||
// auto im_out = iq_buffer.p[i].imag();
|
||||
// buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
// }
|
||||
memcpy(buffer.p, iq_buffer.p, bytes_read_this_iteration); // memcpy should be more efficient than 1 byte at a time
|
||||
|
||||
spectrum_samples += samples_read_this_iteration;
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
|
||||
txprogress_message.progress = bytes_read / 1024; // Inform UI about progress
|
||||
txprogress_message.progress = bytes_read; // Inform UI about progress
|
||||
|
||||
txprogress_message.done = false;
|
||||
shared_memory.application_queue.push(txprogress_message);
|
||||
|
||||
@@ -43,7 +43,7 @@ class ReplayProcessor : public BasebandProcessor {
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
size_t baseband_fs = 0;
|
||||
size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
@@ -43,7 +43,7 @@ ReplayProcessor::ReplayProcessor() {
|
||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 4MHz, 2048 samples */
|
||||
|
||||
if (!configured) return;
|
||||
if (!configured || !stream) return;
|
||||
|
||||
// File data is in C16 format, we need C8
|
||||
// File samplerate is 500kHz, we're at 4MHz
|
||||
@@ -52,14 +52,15 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// 2048 samples * 2 bytes per sample = 4096 bytes
|
||||
// Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each
|
||||
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
|
||||
if (stream) {
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
bytes_read += stream->read(iq_buffer.p, bytes_to_read);
|
||||
}
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read);
|
||||
size_t oversamples_this_iteration = bytes_read_this_iteration * 8 / (sizeof(*buffer.p) * 2);
|
||||
|
||||
bytes_read += bytes_read_this_iteration;
|
||||
|
||||
// Fill and "stretch"
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
if (i & 3) {
|
||||
for (size_t i = 0; i < oversamples_this_iteration; i++) {
|
||||
if (i & 7) {
|
||||
buffer.p[i] = buffer.p[i - 1];
|
||||
} else {
|
||||
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
|
||||
@@ -68,7 +69,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
spectrum_samples += buffer.count;
|
||||
spectrum_samples += oversamples_this_iteration;
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.feed(iq_buffer, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
|
||||
|
||||
@@ -42,7 +42,7 @@ class ReplayProcessor : public BasebandProcessor {
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
size_t baseband_fs = 0;
|
||||
size_t baseband_fs = 3072000;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
BasebandThread baseband_thread{baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit};
|
||||
|
||||
+57
-12
@@ -22,26 +22,71 @@
|
||||
#ifndef __CONVERT_H__
|
||||
#define __CONVERT_H__
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
/* Zero-allocation conversion helper. */
|
||||
/* Notes:
|
||||
* - T must be an integer type.
|
||||
* - For base 16, input _must not_ contain a '0x' or '0X' prefix.
|
||||
* - For base 8 a leading 0 on the literal is allowed.
|
||||
* - Leading whitespce will cause the parse to fail.
|
||||
*/
|
||||
// TODO: from_chars seems to cause code bloat.
|
||||
// Look into using strtol and friends instead.
|
||||
constexpr size_t max_parse_int_length = 64;
|
||||
|
||||
/* This undefined type is used to see the size of the
|
||||
* unhandled type and to create a compilation error. */
|
||||
template <size_t N>
|
||||
struct UnhandledSize;
|
||||
|
||||
/* Returns true when errno is ERANGE. */
|
||||
inline bool range_error() {
|
||||
return errno == ERANGE;
|
||||
}
|
||||
|
||||
/* Assigns 'value' to 'out_val' and returns true if 'value' is
|
||||
* in bounds for type TOut. */
|
||||
template <typename TVal, typename TOut>
|
||||
bool checked_assign(TVal value, TOut& out_val) {
|
||||
// No chance for overflow, just return.
|
||||
if constexpr (sizeof(TVal) <= sizeof(TOut)) {
|
||||
out_val = value;
|
||||
return true;
|
||||
}
|
||||
out_val = static_cast<TOut>(value);
|
||||
return value >= static_cast<TVal>(std::numeric_limits<TOut>::min()) &&
|
||||
value <= static_cast<TVal>(std::numeric_limits<TOut>::max());
|
||||
}
|
||||
|
||||
/* Zero-allocation conversion helper. 'str' must be smaller than 'max_parse_int_length'. */
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_integral_v<T>, bool> parse_int(std::string_view str, T& out_val, int base = 10) {
|
||||
auto result = std::from_chars(str.data(), str.data() + str.length(), out_val, base);
|
||||
return static_cast<int>(result.ec) == 0;
|
||||
// Always initialize the output.
|
||||
out_val = {};
|
||||
|
||||
if (str.size() > max_parse_int_length)
|
||||
return false;
|
||||
|
||||
// Copy onto the stack and null terminate.
|
||||
char zstr[max_parse_int_length + 1];
|
||||
std::memcpy(zstr, str.data(), str.size());
|
||||
zstr[str.size()] = '\0';
|
||||
|
||||
// A little C++ type magic to select the correct conversion function.
|
||||
if constexpr (sizeof(T) == sizeof(long long)) {
|
||||
if constexpr (std::is_unsigned_v<T>)
|
||||
return checked_assign(strtoull(zstr, nullptr, base), out_val) && !range_error();
|
||||
else
|
||||
return checked_assign(strtoll(zstr, nullptr, base), out_val) && !range_error();
|
||||
|
||||
} else if constexpr (sizeof(T) <= sizeof(long)) {
|
||||
if constexpr (std::is_unsigned_v<T>)
|
||||
return checked_assign(strtoul(zstr, nullptr, base), out_val) && !range_error();
|
||||
else
|
||||
return checked_assign(strtol(zstr, nullptr, base), out_val) && !range_error();
|
||||
} else {
|
||||
UnhandledSize<sizeof(T) * 8> unhandled_case;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /*__CONVERT_H__*/
|
||||
@@ -269,7 +269,7 @@ struct data_t {
|
||||
frequency_rx_correction(0),
|
||||
frequency_tx_correction(0),
|
||||
|
||||
encoder_dial_sensitivity(0),
|
||||
encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL),
|
||||
UNUSED_8(0),
|
||||
headphone_volume_cb(-600),
|
||||
misc_config(),
|
||||
@@ -363,6 +363,7 @@ void defaults() {
|
||||
|
||||
set_config_backlight_timer(backlight_config_t{});
|
||||
set_config_splash(true);
|
||||
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
|
||||
|
||||
// Default values for recon app.
|
||||
set_recon_autosave_freqs(false);
|
||||
|
||||
@@ -107,9 +107,11 @@ struct backlight_config_t {
|
||||
};
|
||||
|
||||
enum encoder_dial_sensitivity {
|
||||
DIAL_SENSITIVITY_MEDIUM = 0,
|
||||
DIAL_SENSITIVITY_LOW = 1,
|
||||
DIAL_SENSITIVITY_HIGH = 2,
|
||||
DIAL_SENSITIVITY_HIGHEST = 0,
|
||||
DIAL_SENSITIVITY_HIGH = 1,
|
||||
DIAL_SENSITIVITY_NORMAL = 2,
|
||||
DIAL_SENSITIVITY_LOW = 3,
|
||||
DIAL_SENSITIVITY_LOWEST = 4,
|
||||
NUM_DIAL_SENSITIVITY
|
||||
};
|
||||
|
||||
|
||||
@@ -65,6 +65,11 @@ struct SharedMemory {
|
||||
uint8_t data[512];
|
||||
} bb_data{{{{0, 0}}, 0, {0}}};
|
||||
|
||||
// Set by the M4 to indicate that the baseband app is ready.
|
||||
bool volatile baseband_ready{false};
|
||||
void clear_baseband_ready() { baseband_ready = false; }
|
||||
void set_baseband_ready() { baseband_ready = true; }
|
||||
|
||||
uint8_t volatile request_m4_performance_counter{0};
|
||||
uint8_t volatile m4_cpu_usage{0};
|
||||
uint16_t volatile m4_stack_usage{0};
|
||||
|
||||
@@ -349,7 +349,7 @@ Text::Text(
|
||||
Rect parent_rect,
|
||||
std::string text)
|
||||
: Widget{parent_rect},
|
||||
text{text} {
|
||||
text{std::move(text)} {
|
||||
}
|
||||
|
||||
Text::Text(
|
||||
@@ -357,14 +357,14 @@ Text::Text(
|
||||
: Text{parent_rect, {}} {
|
||||
}
|
||||
|
||||
void Text::set(const std::string value) {
|
||||
text = value;
|
||||
void Text::set(std::string_view value) {
|
||||
text = std::string{value};
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
const auto s = style();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
|
||||
@@ -573,7 +573,7 @@ void ProgressBar::paint(Painter& painter) {
|
||||
const auto sr = screen_rect();
|
||||
const auto s = style();
|
||||
|
||||
v_scaled = (sr.size().width() * _value) / _max;
|
||||
v_scaled = (sr.size().width() * (uint64_t)_value) / _max;
|
||||
|
||||
painter.fill_rectangle({sr.location(), {v_scaled, sr.size().height()}}, style().foreground);
|
||||
painter.fill_rectangle({{sr.location().x() + v_scaled, sr.location().y()}, {sr.size().width() - v_scaled, sr.size().height()}}, s.background);
|
||||
@@ -1608,9 +1608,9 @@ bool OptionsField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextField ***********************************************************/
|
||||
/* TextEdit ***********************************************************/
|
||||
|
||||
TextField::TextField(
|
||||
TextEdit::TextEdit(
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
Point position,
|
||||
@@ -1624,24 +1624,24 @@ TextField::TextField(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::value() const {
|
||||
const std::string& TextEdit::value() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void TextField::set_cursor(uint32_t pos) {
|
||||
void TextEdit::set_cursor(uint32_t pos) {
|
||||
cursor_pos_ = std::min<size_t>(pos, text_.length());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::set_insert_mode() {
|
||||
void TextEdit::set_insert_mode() {
|
||||
insert_mode_ = true;
|
||||
}
|
||||
|
||||
void TextField::set_overwrite_mode() {
|
||||
void TextEdit::set_overwrite_mode() {
|
||||
insert_mode_ = false;
|
||||
}
|
||||
|
||||
void TextField::char_add(char c) {
|
||||
void TextEdit::char_add(char c) {
|
||||
// Don't add if inserting and at max_length and
|
||||
// don't overwrite if past the end of the text.
|
||||
if ((text_.length() >= max_length_ && insert_mode_) ||
|
||||
@@ -1657,7 +1657,7 @@ void TextField::char_add(char c) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::char_delete() {
|
||||
void TextEdit::char_delete() {
|
||||
if (cursor_pos_ == 0)
|
||||
return;
|
||||
|
||||
@@ -1666,7 +1666,7 @@ void TextField::char_delete() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::paint(Painter& painter) {
|
||||
void TextEdit::paint(Painter& painter) {
|
||||
constexpr int char_width = 8;
|
||||
|
||||
auto rect = screen_rect();
|
||||
@@ -1702,7 +1702,7 @@ void TextField::paint(Painter& painter) {
|
||||
painter.draw_rectangle(cursor_box, cursor_style.background);
|
||||
}
|
||||
|
||||
bool TextField::on_key(const KeyEvent key) {
|
||||
bool TextEdit::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Left && cursor_pos_ > 0)
|
||||
cursor_pos_--;
|
||||
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
|
||||
@@ -1716,7 +1716,7 @@ bool TextField::on_key(const KeyEvent key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
bool TextEdit::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_pos = cursor_pos_ + delta;
|
||||
|
||||
// Let the encoder wrap around the ends of the text.
|
||||
@@ -1729,7 +1729,7 @@ bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_touch(const TouchEvent event) {
|
||||
bool TextEdit::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
|
||||
@@ -1737,6 +1737,32 @@ bool TextField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextField *************************************************************/
|
||||
|
||||
TextField::TextField(Rect parent_rect, std::string text)
|
||||
: Text(parent_rect, std::move(text)) {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::get_text() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
void TextField::set_text(std::string_view value) {
|
||||
set(value);
|
||||
if (on_change)
|
||||
on_change(*this);
|
||||
}
|
||||
|
||||
bool TextField::on_key(KeyEvent key) {
|
||||
if (key == KeyEvent::Select && on_select) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* NumberField ***********************************************************/
|
||||
|
||||
NumberField::NumberField(
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
#include "portapack.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -202,11 +203,11 @@ class Text : public Widget {
|
||||
Text(Rect parent_rect, std::string text);
|
||||
Text(Rect parent_rect);
|
||||
|
||||
void set(const std::string value);
|
||||
void set(std::string_view value);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::string text;
|
||||
};
|
||||
|
||||
@@ -619,6 +620,7 @@ class OptionsField : public Widget {
|
||||
|
||||
OptionsField(Point parent_pos, size_t length, options_t options);
|
||||
|
||||
options_t& options() { return options_; }
|
||||
const options_t& options() const { return options_; }
|
||||
void set_options(options_t new_options);
|
||||
|
||||
@@ -642,14 +644,14 @@ class OptionsField : public Widget {
|
||||
size_t selected_index_{0};
|
||||
};
|
||||
|
||||
// A TextField is bound to a string reference and allows the string
|
||||
// A TextEdit is bound to a string reference and allows the string
|
||||
// to be manipulated. The field itself does not provide the UI for
|
||||
// setting the value. It provides the UI of rendering the text,
|
||||
// a cursor, and an API to edit the string content.
|
||||
class TextField : public Widget {
|
||||
class TextEdit : public Widget {
|
||||
public:
|
||||
TextField(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextField{str, 64, position, length} {}
|
||||
TextEdit(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextEdit{str, 64, position, length} {}
|
||||
|
||||
// Str: the string containing the content to edit.
|
||||
// Max_length: max length the string is allowed to use.
|
||||
@@ -658,12 +660,12 @@ class TextField : public Widget {
|
||||
// - Characters are 8 pixels wide.
|
||||
// - The screen can show 30 characters max.
|
||||
// - The control is 16 pixels tall.
|
||||
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
TextEdit(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
|
||||
TextField(const TextField&) = delete;
|
||||
TextField(TextField&&) = delete;
|
||||
TextField& operator=(const TextField&) = delete;
|
||||
TextField& operator=(TextField&&) = delete;
|
||||
TextEdit(const TextEdit&) = delete;
|
||||
TextEdit(TextEdit&&) = delete;
|
||||
TextEdit& operator=(const TextEdit&) = delete;
|
||||
TextEdit& operator=(TextEdit&&) = delete;
|
||||
|
||||
const std::string& value() const;
|
||||
|
||||
@@ -688,6 +690,22 @@ class TextField : public Widget {
|
||||
bool insert_mode_;
|
||||
};
|
||||
|
||||
class TextField : public Text {
|
||||
public:
|
||||
std::function<void(TextField&)> on_select{};
|
||||
std::function<void(TextField&)> on_change{};
|
||||
|
||||
TextField(Rect parent_rect, std::string text);
|
||||
|
||||
const std::string& get_text() const;
|
||||
void set_text(std::string_view value);
|
||||
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
private:
|
||||
using Text::set;
|
||||
};
|
||||
|
||||
class NumberField : public Widget {
|
||||
public:
|
||||
std::function<void(NumberField&)> on_select{};
|
||||
|
||||
@@ -71,6 +71,7 @@ target_include_directories(application_test PRIVATE
|
||||
)
|
||||
|
||||
target_compile_options(application_test PRIVATE
|
||||
-std=c++17
|
||||
-DLPC43XX
|
||||
-DLPC43XX_M0
|
||||
-D__NEWLIB__
|
||||
|
||||
@@ -46,10 +46,11 @@ TEST_CASE("It should convert string_views.") {
|
||||
CHECK_EQ(val, 12345);
|
||||
}
|
||||
|
||||
TEST_CASE("It should return false for invalid input") {
|
||||
// 'from_chars' supported this, but 'strtol' just returns 0.
|
||||
/*TEST_CASE("It should return false for invalid input") {
|
||||
int val = 0;
|
||||
REQUIRE_FALSE(parse_int("QWERTY", val));
|
||||
}
|
||||
}*/
|
||||
|
||||
TEST_CASE("It should return false for overflow input") {
|
||||
uint8_t val = 0;
|
||||
@@ -58,7 +59,7 @@ TEST_CASE("It should return false for overflow input") {
|
||||
|
||||
TEST_CASE("It should convert base 16.") {
|
||||
int val = 0;
|
||||
REQUIRE(parse_int("30", val, 16)); // NB: No '0x'
|
||||
REQUIRE(parse_int("0x30", val, 16));
|
||||
CHECK_EQ(val, 0x30);
|
||||
}
|
||||
|
||||
@@ -74,4 +75,28 @@ TEST_CASE("It should convert as much of the string as it can.") {
|
||||
CHECK_EQ(val, 12345);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 64-bit.") {
|
||||
int64_t val = 0;
|
||||
REQUIRE(parse_int("123456789", val));
|
||||
CHECK_EQ(val, 123456789);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 32-bit.") {
|
||||
int32_t val = 0;
|
||||
REQUIRE(parse_int("123456", val));
|
||||
CHECK_EQ(val, 123456);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 16-bit.") {
|
||||
int16_t val = 0;
|
||||
REQUIRE(parse_int("12345", val));
|
||||
CHECK_EQ(val, 12345);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 8-bit.") {
|
||||
int8_t val = 0;
|
||||
REQUIRE(parse_int("123", val));
|
||||
CHECK_EQ(val, 123);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
@@ -32,13 +32,41 @@ TEST_CASE("to_string_dec_uint64 returns correct value.") {
|
||||
CHECK_EQ(to_string_dec_uint64(1'234'567'891), "1234567891");
|
||||
}
|
||||
|
||||
/*TEST_CASE("to_string_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_freq(0), "0");
|
||||
CHECK_EQ(to_string_freq(1), "1");
|
||||
CHECK_EQ(to_string_freq(1'000'000), "1000000");
|
||||
TEST_CASE("to_string_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_freq(0), " 0");
|
||||
CHECK_EQ(to_string_freq(1), " 1");
|
||||
CHECK_EQ(to_string_freq(1'000'000), " 1000000");
|
||||
CHECK_EQ(to_string_freq(1'234'567'890), "1234567890");
|
||||
CHECK_EQ(to_string_freq(1'234'567'891), "1234567891");
|
||||
}*/
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_short_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_short_freq(0), " 0.0000");
|
||||
CHECK_EQ(to_string_short_freq(49), " 0.0000");
|
||||
CHECK_EQ(to_string_short_freq(50), " 0.0001");
|
||||
CHECK_EQ(to_string_short_freq(1'000'049), " 1.0000");
|
||||
CHECK_EQ(to_string_short_freq(1'000'050), " 1.0001");
|
||||
CHECK_EQ(to_string_short_freq(1'234'567'850), "1234.5679");
|
||||
CHECK_EQ(to_string_short_freq(1'234'567'849), "1234.5678");
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_rounded_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_rounded_freq(0, 0), "0");
|
||||
CHECK_EQ(to_string_rounded_freq(0, 2), "0.00");
|
||||
CHECK_EQ(to_string_rounded_freq(49, 4), "0.0000");
|
||||
CHECK_EQ(to_string_rounded_freq(50, 4), "0.0001");
|
||||
CHECK_EQ(to_string_rounded_freq(23'456, 4), "0.0235");
|
||||
CHECK_EQ(to_string_rounded_freq(1'000'000, 4), "1.0000");
|
||||
CHECK_EQ(to_string_rounded_freq(1'000'567'849, 4), "1000.5678");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 0), "1234");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 1), "1234.6");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 2), "1234.57");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 3), "1234.568");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 4), "1234.5679");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 5), "1234.56789");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 6), "1234.567891");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 9), "1234.567891");
|
||||
}
|
||||
|
||||
TEST_CASE("trim removes whitespace.") {
|
||||
CHECK(trim(" foo\n") == "foo");
|
||||
@@ -47,3 +75,7 @@ TEST_CASE("trim removes whitespace.") {
|
||||
TEST_CASE("trim returns empty for only whitespace.") {
|
||||
CHECK(trim(" \n").empty());
|
||||
}
|
||||
|
||||
TEST_CASE("trim empty returns empty.") {
|
||||
CHECK(trim("").empty());
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
These fonts are for free and can be use for commercial and non-commercial purposes. These aren't professionally made--only beta test. Let me know if there are problems with spaces and kernings.
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2017 The Sedgwick Ave Project Authors (https://github.com/googlefonts/sedgwickave)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
,dannybernecker,dannys-mbp.speedport_w_724v_typ_a_05011603_00_007,16.12.2015 04:43,file:///Users/dannybernecker/Library/Application%20Support/LibreOffice/4;
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,94 @@
|
||||
Copyright (c) <2006>, <Punkto Design> (<http://charaktertypen.com | info@charaktertypen.com>),
|
||||
with Reserved Font Name <„Beatstreet“>.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright (c) <2006>, <Punkto Design, D. Bernecker> (<http://charaktertypen.com | info@charaktertypen.com>), with Reserved Font Name <„Tagster“>.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,143 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=shift_jis">
|
||||
<TITLE>LARABIE FONTS “README</TITLE>
|
||||
<META NAME="GENERATOR" CONTENT="StarOffice 8 (Win32)">
|
||||
<META NAME="AUTHOR" CONTENT="Rina Larabie">
|
||||
<META NAME="CREATED" CONTENT="20061130;11580000">
|
||||
<META NAME="CHANGEDBY" CONTENT="Rina Larabie">
|
||||
<META NAME="CHANGED" CONTENT="20061207;15120000">
|
||||
<STYLE>
|
||||
<!--
|
||||
@page { size: 8.5in 11in; margin-right: 1.25in; margin-top: 1in; margin-bottom: 1in }
|
||||
P { margin-bottom: 0.08in; direction: ltr; color: #000000; widows: 2; orphans: 2 }
|
||||
P.western { font-family: "Times New Roman", serif; font-size: 12pt; so-language: en-US }
|
||||
P.cjk { font-family: "Times New Roman", serif; font-size: 12pt }
|
||||
P.ctl { font-family: "Times New Roman", serif; font-size: 12pt; so-language: ar-SA }
|
||||
H3 { margin-top: 0.19in; margin-bottom: 0.19in; direction: ltr; color: #000000; widows: 2; orphans: 2; page-break-after: auto }
|
||||
H3.western { font-size: 13pt; so-language: en-US }
|
||||
H3.cjk { font-family: "Times New Roman", serif; font-size: 13pt }
|
||||
H3.ctl { font-family: "Times New Roman", serif; font-size: 13pt; so-language: ar-SA }
|
||||
A:link { color: #0000ff }
|
||||
-->
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY LANG="en-US" TEXT="#000000" LINK="#0000ff" DIR="LTR">
|
||||
<H3 CLASS="western" STYLE="margin-top: 0in"><FONT FACE="Tahoma, sans-serif">LARABIE
|
||||
FONTS FREEWARE FONTS EULA (End User License Agreement) and SOFTWARE
|
||||
INCLUSION AGREEMENT</FONT></H3>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><B><FONT FACE="Tahoma, sans-serif">LARABIE
|
||||
FONTS FREEWARE FONTS EULA</FONT></B></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Larabie
|
||||
Fonts Freeware Fonts in TrueType format are free to use for personal
|
||||
and commercial purposes. No payment is necessary to use Larabie Fonts
|
||||
Freeware Fonts for personal or commercial use. If you wish to include
|
||||
Larabie Fonts Freeware Fonts in software see SOFTWARE INCLUSION
|
||||
AGREEMENT below. </FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>USAGE</B></FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">You
|
||||
can offer individual Larabie Fonts Freeware Fonts for download on a
|
||||
website but do not combine fonts into a single archive or alter them
|
||||
in any way. We appreciate inclusion of the font name and trademark or
|
||||
site URL in the credits or documentation but it is not mandatory. </FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Some
|
||||
Larabie Fonts Freeware Fonts may have enhanced and/or expanded
|
||||
families available for sale at </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.typodermic.com/"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.typodermic.com</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>PAYMENT</B></FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Payment
|
||||
is not required for the use of Larabie Fonts Freeware Fonts unless
|
||||
they are intended to be <I>included</I> with software. More details
|
||||
follow.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Donations
|
||||
to Larabie Fonts are welcome via check or electronic payment via
|
||||
Paypal. We can accept any amount in US dollars, Canadian dollars, GBP
|
||||
or euros. If you require an invoice for your donation, please refer
|
||||
to </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/donation.html"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com/donation.html</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">
|
||||
for instructions. </FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Donations
|
||||
can include CDs, magazines, t-shirts, a sample of your merchandise or
|
||||
anything featuring Larabie Fonts. Mailing information is available at
|
||||
the link above.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>SUPPORT</B></FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Font
|
||||
installation help is available at </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.myfonts.com/support"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.myfonts.com/support</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.
|
||||
If you experience problems with any Larabie Font, please visit
|
||||
</FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">
|
||||
to verify you have the latest version. If you download Larabie Fonts
|
||||
Freeware Fonts from other websites you may get older versions that
|
||||
have spacing issues, incomplete character sets or technical problems.
|
||||
</FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><B><FONT FACE="Tahoma, sans-serif">SOFTWARE
|
||||
INCLUSION AGREEMENT </FONT></B>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">The
|
||||
Larabie Fonts SOFTWARE PRODUCT is protected by copyright laws and
|
||||
International copyright treaties, as well as other intellectual
|
||||
property laws and treaties. The SOFTWARE PRODUCT is licensed, not
|
||||
sold.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">1.
|
||||
GRANT OF LICENSE. This document grants you the following rights:</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>-
|
||||
Installation and Use. You may install and use an unlimited number of
|
||||
copies of the SOFTWARE PRODUCT. You may copy and distribute unlimited
|
||||
copies of the SOFTWARE PRODUCT as you receive them, in any medium,
|
||||
provided that you publish on each copy an appropriate copyright
|
||||
notice. Keep intact all the notices that refer to this License and
|
||||
give any other recipients of the fonts a copy of this License along
|
||||
with the fonts.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">2.
|
||||
LIMITED WARRANTY NO WARRANTIES. Larabie Fonts expressly disclaims any
|
||||
warranty for the SOFTWARE PRODUCT. The SOFTWARE PRODUCT and any
|
||||
related documentation is provided "as is" without warranty
|
||||
of any kind, either express or implied, including, without
|
||||
limitation, the implied warranties or merchantability, fitness for a
|
||||
particular purpose, or non-infringement. The entire risk arising out
|
||||
of use or performance of the SOFTWARE PRODUCT remains with you.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">NO
|
||||
LIABILITY FOR CONSEQUENTIAL DAMAGES. In no event shall Ray Larabie or
|
||||
Larabie Fonts be liable for any damages whatsoever (including,
|
||||
without limitation, damages for loss of business profits, business
|
||||
interruption, loss of business information, or any other pecuniary
|
||||
loss) arising out of the use of or inability to use this product,
|
||||
even if Larabie Fonts has been advised of the possibility of such
|
||||
damages.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>3.
|
||||
MISCELLANEOUS</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Should
|
||||
you have any questions concerning this document or you desire to
|
||||
contact Larabie Fonts for any reason, please email
|
||||
</FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/email.html"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com/email.html</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">4.
|
||||
GOVERNING LAW</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>This
|
||||
agreement is governed by the laws of Canada and the province of
|
||||
British Columbia.</FONT></FONT></P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 723 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 721 KiB |
@@ -0,0 +1,456 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="2320px" height="2320px" viewBox="0 0 2320 2320" enable-background="new 0 0 2320 2320" xml:space="preserve">
|
||||
<rect x="0" y="0" width="2320" height="2320" fill="rgb(255,255,255)" /><g transform="translate(160,160)"><g transform="translate(640,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,320) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,320) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,320) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,560) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,560) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(240,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(320,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(240,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,0) scale(5.6, 5.6)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<g>
|
||||
<rect x="15" y="15" style="fill:none;" width="70" height="70"/>
|
||||
<path d="M85,0H15H0v15v70v15h15h70h15V85V15V0H85z M85,85H15V15h70V85z"/>
|
||||
</g>
|
||||
</g></g><g transform="translate(1440,0) scale(5.6, 5.6)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<g>
|
||||
<rect x="15" y="15" style="fill:none;" width="70" height="70"/>
|
||||
<path d="M85,0H15H0v15v70v15h15h70h15V85V15V0H85z M85,85H15V15h70V85z"/>
|
||||
</g>
|
||||
</g></g><g transform="translate(0,1440) scale(5.6, 5.6)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<g>
|
||||
<rect x="15" y="15" style="fill:none;" width="70" height="70"/>
|
||||
<path d="M85,0H15H0v15v70v15h15h70h15V85V15V0H85z M85,85H15V15h70V85z"/>
|
||||
</g>
|
||||
</g></g><g transform="translate(160,160) scale(2.4, 2.4)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,160) scale(2.4, 2.4)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1600) scale(2.4, 2.4)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 30 KiB |
Reference in New Issue
Block a user