mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-06 14:59:04 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ba5715db0 | |||
| c7e8506ad0 | |||
| 8cb0a91bb5 | |||
| 1bcbefeb96 | |||
| 31031edbd1 | |||
| 7116f92d07 | |||
| df8e79f9e6 | |||
| e80e4e3bfd | |||
| da6c6bb03c | |||
| 75718c79b9 | |||
| 9b263def37 | |||
| c7d88da1a6 | |||
| 7b6f8b271c | |||
| 1b18b3ac45 | |||
| 68fc2a143f | |||
| 1deebaff09 | |||
| d6118c9fc4 | |||
| 44a62aef21 | |||
| 0742fc169d | |||
| 62859f901f | |||
| c6316f5aa6 | |||
| 5f2043d229 | |||
| 074658c4bb | |||
| d641ae5b47 | |||
| bec70b4ee5 | |||
| c4373d1560 | |||
| 1dae2c0d25 | |||
| 658d0c9b3a | |||
| 2f343adf21 | |||
| f7a5f2c437 | |||
| 5a336d5e71 | |||
| 91675e26cb | |||
| 078da8ca16 | |||
| 77260bc68a | |||
| ca143f9788 | |||
| 3fc23354ce | |||
| d48e25167f | |||
| cfe5a6bfe4 | |||
| d77102426a | |||
| 11f4edc892 | |||
| 804fa0d3c4 | |||
| d5f20c45b9 | |||
| 2cba96ff36 | |||
| 7139abb947 | |||
| bf4ed416bd | |||
| a74d0e5167 |
@@ -1 +1 @@
|
||||
v1.5.4
|
||||
v1.6.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.6.0
|
||||
v1.7.0
|
||||
|
||||
@@ -206,7 +206,7 @@ void SoundBoardView::refresh_list() {
|
||||
file_list[n].string().substr(0, 30),
|
||||
ui::Color::white(),
|
||||
nullptr,
|
||||
[this](){
|
||||
[this](KeyEvent){
|
||||
on_select_entry();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,72 +20,173 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/* TODO:
|
||||
* - Paging menu items
|
||||
* - Copy/Move
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "ui_fileman.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
using namespace ui;
|
||||
|
||||
bool is_hidden_file(const fs::path& path) {
|
||||
return !path.empty() && path.native()[0] == u'.';
|
||||
}
|
||||
|
||||
// Gets a truncated name from a path for display.
|
||||
std::string truncate(const fs::path& path, size_t max_length) {
|
||||
auto name = path.string();
|
||||
return name.length() <= max_length ? name : name.substr(0, max_length);
|
||||
}
|
||||
|
||||
// Gets a human readable file size string.
|
||||
std::string get_pretty_size(uint32_t file_size) {
|
||||
static const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
||||
size_t suffix_index = 0;
|
||||
|
||||
while (file_size >= 1024) {
|
||||
file_size /= 1024;
|
||||
suffix_index++;
|
||||
}
|
||||
|
||||
if (suffix_index > 4)
|
||||
suffix_index = 4;
|
||||
|
||||
return to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
}
|
||||
|
||||
// Case insensitive path equality on underlying "native" string.
|
||||
bool iequal(
|
||||
const fs::path& lhs,
|
||||
const fs::path& rhs
|
||||
) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry,
|
||||
[](const fileman_entry& lhs, const fileman_entry& rhs) {
|
||||
if (lhs.is_directory && !rhs.is_directory)
|
||||
return true;
|
||||
else if (!lhs.is_directory && rhs.is_directory)
|
||||
return false;
|
||||
else
|
||||
return lhs.path < rhs.path;
|
||||
});
|
||||
|
||||
entries.insert(it, std::move(entry));
|
||||
}
|
||||
|
||||
// Returns the partner file path or an empty path if no partner is found.
|
||||
fs::path get_partner_file(fs::path path) {
|
||||
if (fs::is_directory(path))
|
||||
return { };
|
||||
|
||||
const fs::path txt_path{ u".TXT" };
|
||||
const fs::path c16_path{ u".C16" };
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(ext, txt_path))
|
||||
ext = c16_path;
|
||||
else if (iequal(ext, c16_path))
|
||||
ext = txt_path;
|
||||
else
|
||||
return { };
|
||||
|
||||
path.replace_extension(ext);
|
||||
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{ };
|
||||
}
|
||||
|
||||
// Modal prompt to update the partner file if it exists.
|
||||
// Runs continuation on_partner_action to update the partner file.
|
||||
// Returns true is a partner is found, otherwise false.
|
||||
// Path must be the full path to the file.
|
||||
bool partner_file_prompt(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
std::string action_name,
|
||||
std::function<void(const fs::path&, bool)> on_partner_action
|
||||
) {
|
||||
auto partner = get_partner_file(path);
|
||||
|
||||
if (partner.empty())
|
||||
return false;
|
||||
|
||||
nav.push_under_current<ModalMessageView>(
|
||||
"Partner File",
|
||||
partner.filename().string() + "\n" + action_name + " this file too?",
|
||||
YESNO,
|
||||
[&nav, partner, on_partner_action](bool choice) {
|
||||
if (on_partner_action)
|
||||
on_partner_action(partner, choice);
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) {
|
||||
/* FileManBaseView ***********************************************************/
|
||||
|
||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
current_path = dir_path;
|
||||
|
||||
text_current.set(dir_path.string().length()? dir_path.string().substr(0, 30 - 6):"(sd root)");
|
||||
|
||||
entry_list.clear();
|
||||
|
||||
auto filtering = (bool)extension_filter.size();
|
||||
|
||||
// List directories and files, put directories up top
|
||||
if (dir_path.string().length())
|
||||
entry_list.push_back({ u"..", 0, true });
|
||||
auto filtering = !extension_filter.empty();
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir_path, u"*")) {
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (is_hidden_file(entry.path()))
|
||||
continue;
|
||||
|
||||
// do not display dir / files starting with '.' (hidden / tmp)
|
||||
if (entry.path().string().length() && entry.path().filename().string()[0] != '.') {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
bool matched = true;
|
||||
if (filtering) {
|
||||
auto entry_extension = entry.path().extension().string();
|
||||
|
||||
for (auto &c: entry_extension)
|
||||
c = toupper(c);
|
||||
|
||||
if (entry_extension != extension_filter)
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if (matched)
|
||||
entry_list.push_back({ entry.path(), (uint32_t)entry.size(), false });
|
||||
} else if (std::filesystem::is_directory(entry.status())) {
|
||||
entry_list.insert(entry_list.begin(), { entry.path(), 0, true });
|
||||
}
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || iequal(entry.path().extension(), extension_filter))
|
||||
insert_sorted(entry_list, { entry.path(), (uint32_t)entry.size(), false });
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, { entry.path(), 0, true });
|
||||
}
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty())
|
||||
entry_list.insert(entry_list.begin(), { parent_dir_path, 0, true });
|
||||
}
|
||||
|
||||
std::filesystem::path FileManBaseView::get_selected_path() {
|
||||
auto selected_path_str = current_path.string();
|
||||
auto entry_path = entry_list[menu_view.highlighted_index()].entry_path.string();
|
||||
|
||||
if (entry_path == "..") {
|
||||
selected_path_str = get_parent_dir().string();
|
||||
} else {
|
||||
if (selected_path_str.back() != '/')
|
||||
selected_path_str += '/';
|
||||
|
||||
selected_path_str += entry_path;
|
||||
}
|
||||
|
||||
return selected_path_str;
|
||||
fs::path FileManBaseView::get_selected_full_path() const {
|
||||
if (get_selected_entry().path == parent_dir_path)
|
||||
return current_path.parent_path();
|
||||
|
||||
return current_path / get_selected_entry().path;
|
||||
}
|
||||
|
||||
std::filesystem::path FileManBaseView::get_parent_dir() {
|
||||
auto current_path_str = current_path.string();
|
||||
return current_path.string().substr(0, current_path_str.find_last_of('/'));
|
||||
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||
// TODO: return reference to an "empty" entry on OOB?
|
||||
return entry_list[menu_view.highlighted_index()];
|
||||
}
|
||||
|
||||
FileManBaseView::FileManBaseView(
|
||||
@@ -105,20 +206,20 @@ FileManBaseView::FileManBaseView(
|
||||
};
|
||||
|
||||
if (!sdcIsCardInserted(&SDCD1)) {
|
||||
empty_root=true;
|
||||
empty_root = true;
|
||||
text_current.set("NO SD CARD!");
|
||||
return;
|
||||
}
|
||||
|
||||
load_directory_contents(current_path);
|
||||
|
||||
if (!entry_list.size()) {
|
||||
empty_root = true;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
load_directory_contents(current_path);
|
||||
if (!entry_list.size())
|
||||
{
|
||||
empty_root = true;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
menu_view.on_left = [&nav, this]() {
|
||||
load_directory_contents(get_parent_dir());
|
||||
refresh_list();
|
||||
};
|
||||
}
|
||||
menu_view.on_left = [this]() {
|
||||
pop_dir();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,67 +231,83 @@ void FileManBaseView::focus() {
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current();
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
return;
|
||||
|
||||
current_path = current_path.parent_path();
|
||||
reload_current();
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
}
|
||||
|
||||
void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
auto prev_highlight = menu_view.highlighted_index();
|
||||
menu_view.clear();
|
||||
|
||||
for (size_t n = 0; n < entry_list.size(); n++) {
|
||||
auto entry = &entry_list[n];
|
||||
auto entry_name = entry->entry_path.filename().string().substr(0, 20);
|
||||
|
||||
if (entry->is_directory) {
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
auto entry_name = truncate(entry.path, 20);
|
||||
|
||||
if (entry.is_directory) {
|
||||
menu_view.add_item({
|
||||
entry_name,
|
||||
ui::Color::yellow(),
|
||||
&bitmap_icon_dir,
|
||||
[this](){
|
||||
[this](KeyEvent key) {
|
||||
if (on_select_entry)
|
||||
on_select_entry();
|
||||
on_select_entry(key);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
auto file_size = entry->size;
|
||||
size_t suffix_index = 0;
|
||||
|
||||
while (file_size >= 1024) {
|
||||
file_size /= 1024;
|
||||
suffix_index++;
|
||||
}
|
||||
if (suffix_index > 4)
|
||||
suffix_index = 4;
|
||||
|
||||
std::string size_str = to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
|
||||
auto entry_extension = entry->entry_path.extension().string();
|
||||
for (auto &c: entry_extension)
|
||||
c = toupper(c);
|
||||
|
||||
// Associate extension to icon and color
|
||||
size_t c;
|
||||
for (c = 0; c < file_types.size() - 1; c++) {
|
||||
if (entry_extension == file_types[c].extension)
|
||||
break;
|
||||
}
|
||||
const auto& assoc = get_assoc(entry.path.extension());
|
||||
auto size_str = get_pretty_size(entry.size);
|
||||
|
||||
menu_view.add_item({
|
||||
entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
file_types[c].color,
|
||||
file_types[c].icon,
|
||||
[this](){
|
||||
assoc.color,
|
||||
assoc.icon,
|
||||
[this](KeyEvent key) {
|
||||
if (on_select_entry)
|
||||
on_select_entry();
|
||||
on_select_entry(key);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(0); // Refresh
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current() {
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
const fs::path& ext) const
|
||||
{
|
||||
size_t index = 0;
|
||||
|
||||
for (; index < file_types.size() - 1; ++index)
|
||||
if (iequal(ext, file_types[index].extension))
|
||||
return file_types[index];
|
||||
|
||||
// Default to last entry in the list.
|
||||
return file_types[index];
|
||||
}
|
||||
|
||||
/*void FileSaveView::on_save_name() {
|
||||
@@ -215,8 +332,9 @@ FileSaveView::FileSaveView(
|
||||
};
|
||||
}*/
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool v) {
|
||||
(void)v; //avoid unused warning
|
||||
/* FileLoadView **************************************************************/
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -238,84 +356,92 @@ FileLoadView::FileLoadView(
|
||||
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [&nav, this]() {
|
||||
if (entry_list[menu_view.highlighted_index()].is_directory) {
|
||||
load_directory_contents(get_selected_path());
|
||||
refresh_list();
|
||||
on_select_entry = [this](KeyEvent) {
|
||||
if (get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
nav_.pop();
|
||||
if (on_changed)
|
||||
on_changed(current_path.string() + '/' + entry_list[menu_view.highlighted_index()].entry_path.string());
|
||||
on_changed(get_selected_full_path());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void FileManagerView::on_rename(NavigationView& nav) {
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
std::string destination_path = current_path.string();
|
||||
if (destination_path.back() != '/')
|
||||
destination_path += '/';
|
||||
destination_path = destination_path + buffer;
|
||||
rename_file(get_selected_path(), destination_path);
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
});
|
||||
}
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::on_refactor(NavigationView& nav) {
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
void FileManagerView::on_rename() {
|
||||
auto& entry = get_selected_entry();
|
||||
name_buffer = entry.path.filename().string();
|
||||
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
||||
|
||||
std::string destination_path = current_path.string();
|
||||
if (destination_path.back() != '/')//if the path is not ended with '/', add '/'
|
||||
destination_path += '/';
|
||||
if (auto pos = name_buffer.find_last_of(".");
|
||||
pos != name_buffer.npos && !entry.is_directory)
|
||||
cursor_pos = pos;
|
||||
|
||||
auto selected_path = get_selected_path();
|
||||
auto extension = selected_path.extension().string();
|
||||
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
|
||||
[this](std::string& renamed) {
|
||||
auto renamed_path = fs::path{ renamed };
|
||||
rename_file(get_selected_full_path(), current_path / renamed_path);
|
||||
|
||||
if(extension.empty()){// Is Dir
|
||||
destination_path = destination_path + buffer;
|
||||
extension_buffer = "";
|
||||
}else{//is File
|
||||
destination_path = destination_path + buffer + extension_buffer;
|
||||
}
|
||||
|
||||
rename_file(get_selected_path(), destination_path); //rename the selected file
|
||||
|
||||
if (!extension.empty() && selected_path.string().back() != '/' && extension.substr(1) == "C16") { //substr(1) is for ignore the dot
|
||||
// Rename its partner ( C16 <-> TXT ) file.
|
||||
auto partner_file_path = selected_path.string().substr(0, selected_path.string().size() - 4) + ".TXT";
|
||||
destination_path = destination_path.substr(0, destination_path.size() - 4) + ".TXT";
|
||||
rename_file(partner_file_path, destination_path);
|
||||
} else if (!extension.empty() && selected_path.string().back() != '/' && extension.substr(1) == "TXT") {
|
||||
// If the file user choose is a TXT file.
|
||||
auto partner_file_path = selected_path.string().substr(0, selected_path.string().size() - 4) + ".C16";
|
||||
destination_path = destination_path.substr(0, destination_path.size() - 4) + ".C16";
|
||||
rename_file(partner_file_path, destination_path);
|
||||
}
|
||||
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
|
||||
});
|
||||
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename",
|
||||
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
|
||||
if (should_rename) {
|
||||
auto new_name = renamed_path.replace_extension(partner.extension());
|
||||
rename_file(current_path / partner, current_path / new_name);
|
||||
}
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
void FileManagerView::on_delete() {
|
||||
delete_file(get_selected_path());
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
auto name = get_selected_entry().path.filename().string();
|
||||
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
delete_file(get_selected_full_path());
|
||||
|
||||
auto has_partner = partner_file_prompt(
|
||||
nav_, get_selected_full_path(), "Delete",
|
||||
[this](const fs::path& partner, bool should_delete) {
|
||||
if (should_delete)
|
||||
delete_file(current_path / partner);
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FileManagerView::on_new_dir() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||
make_new_directory(current_path / dir_name);
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
bool FileManagerView::selected_is_valid() const {
|
||||
return !entry_list.empty() &&
|
||||
get_selected_entry().path != parent_dir_path;
|
||||
}
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
button_rename.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
button_refactor.hidden(v);
|
||||
button_delete.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
FileManagerView::~FileManagerView() {
|
||||
// Flush ?
|
||||
}
|
||||
|
||||
FileManagerView::FileManagerView(
|
||||
@@ -332,60 +458,40 @@ FileManagerView::FileManagerView(
|
||||
&labels,
|
||||
&text_date,
|
||||
&button_rename,
|
||||
&button_refactor,
|
||||
&button_delete,
|
||||
&button_new_dir,
|
||||
&button_delete
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_path())));
|
||||
// TODO: enable/disable buttons.
|
||||
if (selected_is_valid())
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [this]() {
|
||||
if (entry_list[menu_view.highlighted_index()].is_directory) {
|
||||
load_directory_contents(get_selected_path());
|
||||
refresh_list();
|
||||
} else
|
||||
|
||||
on_select_entry = [this](KeyEvent key) {
|
||||
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
button_rename.focus();
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this, &nav](Button&) {
|
||||
name_buffer.clear();
|
||||
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
make_new_directory(current_path.string() + '/' + buffer);
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
});
|
||||
};
|
||||
|
||||
button_rename.on_select = [this, &nav](Button&) {
|
||||
name_buffer = entry_list[menu_view.highlighted_index()].entry_path.filename().string().substr(0, max_filename_length);
|
||||
on_rename(nav);
|
||||
};
|
||||
|
||||
button_refactor.on_select = [this, &nav](Button&) {
|
||||
name_buffer = entry_list[menu_view.highlighted_index()].entry_path.filename().string().substr(0, max_filename_length);
|
||||
size_t pos = name_buffer.find_last_of(".");
|
||||
|
||||
if (pos != std::string::npos) {
|
||||
extension_buffer = name_buffer.substr(pos);
|
||||
name_buffer = name_buffer.substr(0, pos);
|
||||
}
|
||||
|
||||
on_refactor(nav);
|
||||
};
|
||||
|
||||
button_rename.on_select = [this](Button&) {
|
||||
if (selected_is_valid())
|
||||
on_rename();
|
||||
};
|
||||
|
||||
button_delete.on_select = [this, &nav](Button&) {
|
||||
// Use display_modal ?
|
||||
nav.push<ModalMessageView>("Delete", "Delete " + entry_list[menu_view.highlighted_index()].entry_path.filename().string() + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
on_delete();
|
||||
}
|
||||
);
|
||||
button_delete.on_select = [this](Button&) {
|
||||
if (selected_is_valid())
|
||||
on_delete();
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this](Button&) {
|
||||
on_new_dir();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
namespace ui {
|
||||
|
||||
struct fileman_entry {
|
||||
std::filesystem::path entry_path { };
|
||||
std::filesystem::path path { };
|
||||
uint32_t size { };
|
||||
bool is_directory { };
|
||||
};
|
||||
@@ -43,50 +43,56 @@ public:
|
||||
std::string filter
|
||||
);
|
||||
|
||||
void focus() override;
|
||||
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
std::filesystem::path get_selected_path();
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Fileman"; };
|
||||
|
||||
protected:
|
||||
NavigationView& nav_;
|
||||
|
||||
static constexpr size_t max_filename_length = 30 - 2;
|
||||
|
||||
const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
||||
|
||||
static constexpr size_t max_filename_length = 50;
|
||||
|
||||
struct file_assoc_t {
|
||||
std::string extension;
|
||||
std::filesystem::path extension;
|
||||
const Bitmap* icon;
|
||||
ui::Color color;
|
||||
};
|
||||
|
||||
const std::vector<file_assoc_t> file_types = {
|
||||
{ ".TXT", &bitmap_icon_file_text, ui::Color::white() },
|
||||
{ ".PNG", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ ".BMP", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ ".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ ".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ ".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
|
||||
{ "", &bitmap_icon_file, ui::Color::light_grey() }
|
||||
{ u".TXT", &bitmap_icon_file_text, ui::Color::white() },
|
||||
{ u".PNG", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ 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".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
|
||||
{ u"", &bitmap_icon_file, ui::Color::light_grey() } // NB: Must be last.
|
||||
};
|
||||
|
||||
bool empty_root { false };
|
||||
std::function<void(void)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
std::vector<fileman_entry> entry_list { };
|
||||
std::filesystem::path current_path { u"" };
|
||||
std::string extension_filter { "" };
|
||||
|
||||
void change_category(int32_t category_id);
|
||||
std::filesystem::path get_parent_dir();
|
||||
|
||||
|
||||
std::filesystem::path get_selected_full_path() const;
|
||||
const fileman_entry& get_selected_entry() const;
|
||||
|
||||
void push_dir(const std::filesystem::path& path);
|
||||
void pop_dir();
|
||||
void refresh_list();
|
||||
void reload_current();
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
bool empty_root { false };
|
||||
std::function<void(KeyEvent)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
|
||||
const std::filesystem::path parent_dir_path { u".." };
|
||||
std::filesystem::path current_path { u"" };
|
||||
std::filesystem::path extension_filter { u"" };
|
||||
|
||||
std::vector<fileman_entry> entry_list { };
|
||||
std::vector<uint32_t> saved_index_stack { };
|
||||
|
||||
Labels labels {
|
||||
{ { 0, 0 }, "Path:", Color::light_grey() }
|
||||
};
|
||||
|
||||
Text text_current {
|
||||
{ 6 * 8, 0 * 8, 24 * 8, 16 },
|
||||
"",
|
||||
@@ -142,13 +148,16 @@ public:
|
||||
~FileManagerView();
|
||||
|
||||
private:
|
||||
// Passed by ref to other views needing lifetime extension.
|
||||
std::string name_buffer { };
|
||||
std::string extension_buffer { };
|
||||
|
||||
void refresh_widgets(const bool v);
|
||||
void on_rename(NavigationView& nav);
|
||||
void on_refactor(NavigationView& nav);
|
||||
void on_rename();
|
||||
void on_delete();
|
||||
void on_new_dir();
|
||||
|
||||
// True if the selected entry is a real file item.
|
||||
bool selected_is_valid() const;
|
||||
|
||||
Labels labels {
|
||||
{ { 0, 26 * 8 }, "Created ", Color::light_grey() }
|
||||
@@ -160,25 +169,19 @@ private:
|
||||
};
|
||||
|
||||
Button button_rename {
|
||||
{ 0 * 8, 29 * 8, 9 * 8, 32 },
|
||||
{ 0 * 8, 29 * 8, 14 * 8, 32 },
|
||||
"Rename"
|
||||
};
|
||||
|
||||
Button button_refactor{
|
||||
{ 10 * 8, 29 * 8, 10 * 8, 32 },
|
||||
"Refactor"
|
||||
};
|
||||
|
||||
Button button_delete {
|
||||
{ 21 * 8, 29 * 8, 9 * 8, 32 },
|
||||
{ 16 * 8, 29 * 8, 14 * 8, 32 },
|
||||
"Delete"
|
||||
};
|
||||
|
||||
|
||||
Button button_new_dir {
|
||||
{ 0 * 8, 34 * 8, 14 * 8, 32 },
|
||||
"New dir"
|
||||
"New Dir"
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -44,7 +44,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav) : nav_ (nav) {
|
||||
filename.string().substr(0, max_filename_length),
|
||||
ui::Color::red(),
|
||||
&bitmap_icon_temperature,
|
||||
[this, path]() {
|
||||
[this, path](KeyEvent) {
|
||||
this->firmware_selected(path);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ void FreqManBaseView::refresh_list() {
|
||||
freqman_item_string(database[n], 30),
|
||||
ui::Color::white(),
|
||||
nullptr,
|
||||
[this](){
|
||||
[this](KeyEvent){
|
||||
if (on_select_frequency)
|
||||
on_select_frequency();
|
||||
}
|
||||
|
||||
@@ -30,10 +30,9 @@
|
||||
#define MSG_RECON_SET_MODULATION 10000 // for handle_retune to know that recon thread triggered a modulation change. f is the index of the modulation
|
||||
#define MSG_RECON_SET_BANDWIDTH 20000 // for handle_retune to know that recon thread triggered a bandwidth change. f is the new bandwidth value index for current modulation
|
||||
#define MSG_RECON_SET_STEP 30000 // for handle_retune to know that recon thread triggered a bandwidth change. f is the new bandwidth value index for current modulation
|
||||
#define MSG_RECON_SET_RECEIVER_BANDWIDTH 40000 // for handle_retune to know that recon thread triggered a receiver bandwidth change. f is the new bandwidth in hz
|
||||
#define MSG_RECON_SET_RECEIVER_BANDWIDTH 40000 // for handle_retune to know that recon thread triggered a receiver bandwidth change. f is the new bandwidth in hz
|
||||
#define MSG_RECON_SET_RECEIVER_SAMPLERATE 50000 // for handle_retune to know that recon thread triggered a receiver samplerate change. f is the new samplerate in hz/s
|
||||
|
||||
|
||||
using namespace portapack;
|
||||
using portapack::memory::map::backup_ram;
|
||||
|
||||
@@ -104,7 +103,7 @@ namespace ui {
|
||||
|
||||
void ReconThread::change_recon_direction() {
|
||||
_fwd = !_fwd;
|
||||
// chThdSleepMilliseconds(300); //Give some pause after reversing recon direction
|
||||
//chThdSleepMilliseconds(300); //Give some pause after reversing recon direction
|
||||
}
|
||||
|
||||
bool ReconThread::get_recon_direction() {
|
||||
@@ -409,15 +408,16 @@ namespace ui {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
restart_recon = true ;
|
||||
}
|
||||
// send a pause message with the right freq
|
||||
if( has_looped && !_continuous )
|
||||
{
|
||||
// signal pause to handle_retune
|
||||
receiver_model.set_tuning_frequency( freq ); // Retune to actual freq
|
||||
message.freq = freq ;
|
||||
message.range = MSG_RECON_PAUSE ;
|
||||
EventDispatcher::send_message(message);
|
||||
receiver_model.set_tuning_frequency( freq ); // Retune to actual freq
|
||||
}
|
||||
if( _stepper < 0 ) _stepper ++ ;
|
||||
if( _stepper > 0 ) _stepper -- ;
|
||||
@@ -475,8 +475,8 @@ namespace ui {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
change_mode( freq );
|
||||
if ( !recon_thread->is_recon() ) //for some motive, audio output gets stopped.
|
||||
audio::output::start(); //So if recon was stopped we resume audio
|
||||
if ( !recon_thread->is_recon() ) // for some motive, audio output gets stopped.
|
||||
audio::output::start(); // so if recon was stopped we resume audio
|
||||
receiver_model.enable();
|
||||
field_mode.set_selected_index( freq );
|
||||
return ;
|
||||
@@ -526,7 +526,7 @@ namespace ui {
|
||||
}
|
||||
}
|
||||
}
|
||||
text_cycle.set_text( to_string_dec_uint( index + 1 , 3 ) );
|
||||
text_cycle.set_text( to_string_dec_uint( index + 1 , 3 ) );
|
||||
if(frequency_list[index].description.size() > 0)
|
||||
{
|
||||
switch( frequency_list[current_index].type )
|
||||
@@ -543,6 +543,10 @@ namespace ui {
|
||||
break ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
desc_cycle.set( "...no description..." ); //Show new description
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t freq_lock = recon_thread->is_freq_lock();
|
||||
@@ -641,7 +645,7 @@ namespace ui {
|
||||
static int32_t last_db = 999999 ;
|
||||
static int32_t last_nb_match = 999999 ;
|
||||
static int32_t last_timer = -1 ;
|
||||
if( recon_thread && frequency_list.size() > 0 )
|
||||
if( recon_thread )
|
||||
{
|
||||
int32_t nb_match = recon_thread->is_freq_lock();
|
||||
if( last_db != db )
|
||||
@@ -666,15 +670,6 @@ namespace ui {
|
||||
text_timer.set( "TIMER: " + to_string_dec_int( timer ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( refresh_display )
|
||||
{
|
||||
text_max.set( to_string_dec_int( db ) + " db" );
|
||||
freq_stats.set( "RSSI: " +to_string_dec_int( rssi.get_min() )+"/"+to_string_dec_int( rssi.get_avg() )+"/"+to_string_dec_int( rssi.get_max() )+" db" );
|
||||
text_timer.set( "TIMER: " + to_string_dec_int( timer ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReconView::ReconView( NavigationView& nav) : nav_ { nav } {
|
||||
@@ -716,15 +711,11 @@ namespace ui {
|
||||
} );
|
||||
|
||||
// Recon directory
|
||||
if( check_sd_card() ) { // Check to see if SD Card is mounted
|
||||
if( check_sd_card() ) { // Check to see if SD Card is mounted
|
||||
make_new_directory( u"/RECON" );
|
||||
sd_card_mounted = true ;
|
||||
}
|
||||
|
||||
def_step = 0 ;
|
||||
change_mode(AM_MODULATION); //Start on AM
|
||||
field_mode.set_by_value(AM_MODULATION); //Reflect the mode into the manual selector
|
||||
|
||||
//HELPER: Pre-setting a manual range, based on stored frequency
|
||||
rf::Frequency stored_freq = persistent_memory::tuned_frequency();
|
||||
receiver_model.set_tuning_frequency( stored_freq );
|
||||
@@ -742,14 +733,12 @@ namespace ui {
|
||||
load_hamradios = persistent_memory::recon_load_hamradios();
|
||||
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
||||
|
||||
//Loading input and output file from settings
|
||||
ReconSetupLoadStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , volume );
|
||||
|
||||
field_volume.set_value( volume );
|
||||
|
||||
// load auto common app settings
|
||||
if( sd_card_mounted )
|
||||
{
|
||||
//Loading input and output file from settings
|
||||
ReconSetupLoadStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , volume );
|
||||
// load auto common app settings
|
||||
auto rc = settings.load("recon", &app_settings);
|
||||
if(rc == SETTINGS_OK) {
|
||||
field_lna.set_value(app_settings.lna);
|
||||
@@ -758,12 +747,7 @@ namespace ui {
|
||||
receiver_model.set_rf_amp(app_settings.rx_amp);
|
||||
}
|
||||
}
|
||||
button_scanner_mode.set_style( &style_blue );
|
||||
button_scanner_mode.set_text( "RECON" );
|
||||
file_name.set( "=>" );
|
||||
|
||||
field_squelch.set_value( squelch );
|
||||
|
||||
|
||||
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(frequency_range.min);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
@@ -995,21 +979,21 @@ namespace ui {
|
||||
switch( frequency_list[current_index].type )
|
||||
{
|
||||
case RANGE:
|
||||
desc_cycle.set( "R: " + frequency_list[current_index].description ); //Show new description
|
||||
desc_cycle.set( "R: " + frequency_list[current_index].description );
|
||||
break ;
|
||||
case HAMRADIO:
|
||||
desc_cycle.set( "H: " + frequency_list[current_index].description ); //Show new description
|
||||
desc_cycle.set( "H: " + frequency_list[current_index].description );
|
||||
break ;
|
||||
default:
|
||||
case SINGLE:
|
||||
desc_cycle.set( "S: " + frequency_list[current_index].description ); //Show new description
|
||||
desc_cycle.set( "S: " + frequency_list[current_index].description );
|
||||
break ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
desc_cycle.set( "no description" ); //Show new description
|
||||
show_max( true ); //UPDATE new list size on screen
|
||||
desc_cycle.set( "...no description..." );
|
||||
show_max( true );
|
||||
}
|
||||
text_cycle.set_text( to_string_dec_uint( current_index + 1 , 3 ) );
|
||||
|
||||
@@ -1163,7 +1147,6 @@ namespace ui {
|
||||
|
||||
def_step = step_mode.selected_index(); // max range val
|
||||
|
||||
|
||||
manual_freq_entry . type = RANGE ;
|
||||
manual_freq_entry . description =
|
||||
"R " + to_string_short_freq(frequency_range.min) + ">"
|
||||
@@ -1184,11 +1167,14 @@ namespace ui {
|
||||
freq_stats.set( "0/0/0" );
|
||||
|
||||
show_max(); /* display step information */
|
||||
text_cycle.set_text( "MANUAL SEARCH" );
|
||||
text_cycle.set_text( "1" );
|
||||
text_max.set( "/1" );
|
||||
button_scanner_mode.set_style( &style_white );
|
||||
button_scanner_mode.set_text( "MSEARCH" );
|
||||
file_name.set_style( &style_white );
|
||||
file_name.set( "=> MANUAL RANGE" );
|
||||
file_name.set( "MANUAL RANGE RECON" );
|
||||
desc_cycle.set_style( &style_white );
|
||||
desc_cycle.set( "MANUAL RANGE RECON" );
|
||||
|
||||
start_recon_thread();
|
||||
user_resume();
|
||||
@@ -1226,7 +1212,7 @@ namespace ui {
|
||||
button_restart.on_select = [this](Button&) {
|
||||
if( frequency_list.size() > 0 )
|
||||
{
|
||||
def_step = step_mode.selected_index(); //Use def_step from manual selector
|
||||
def_step = step_mode.selected_index(); //Use def_step from manual selector
|
||||
frequency_file_load( true );
|
||||
if( recon_thread )
|
||||
{
|
||||
@@ -1245,9 +1231,20 @@ namespace ui {
|
||||
show_max();
|
||||
user_resume();
|
||||
}
|
||||
if( scanner_mode )
|
||||
{
|
||||
file_name.set_style( &style_red );
|
||||
button_scanner_mode.set_style( &style_red );
|
||||
button_scanner_mode.set_text( "SCANNER" );
|
||||
}
|
||||
else
|
||||
{
|
||||
file_name.set_style( &style_blue );
|
||||
button_scanner_mode.set_style( &style_blue );
|
||||
button_scanner_mode.set_text( "RECON" );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
button_add.on_select = [this](ButtonWithEncoder&) { //frequency_list[current_index]
|
||||
if( !scanner_mode)
|
||||
{
|
||||
@@ -1371,7 +1368,8 @@ namespace ui {
|
||||
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() );
|
||||
|
||||
user_pause();
|
||||
if( frequency_list.size() != 0 )
|
||||
user_pause();
|
||||
|
||||
auto open_view = nav.push<ReconSetupView>(input_file,output_file,recon_lock_duration,recon_lock_nb_match,recon_match_mode);
|
||||
open_view -> on_changed = [this](std::vector<std::string> result) {
|
||||
@@ -1379,7 +1377,7 @@ namespace ui {
|
||||
output_file = result[1];
|
||||
recon_lock_duration = strtol( result[2].c_str() , nullptr , 10 );
|
||||
recon_lock_nb_match = strtol( result[3].c_str() , nullptr , 10 );
|
||||
recon_match_mode = strtol( result[4].c_str() , nullptr , 10 );
|
||||
recon_match_mode = strtol( result[4].c_str() , nullptr , 10 );
|
||||
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() );
|
||||
|
||||
@@ -1449,7 +1447,6 @@ namespace ui {
|
||||
}
|
||||
};
|
||||
|
||||
//PRE-CONFIGURATION:
|
||||
field_wait.on_change = [this](int32_t v)
|
||||
{
|
||||
wait = v ;
|
||||
@@ -1470,6 +1467,7 @@ namespace ui {
|
||||
field_wait.set_style( &style_green );
|
||||
}
|
||||
};
|
||||
|
||||
field_lock_wait.on_change = [this](int32_t v)
|
||||
{
|
||||
lock_wait = v ;
|
||||
@@ -1494,6 +1492,22 @@ namespace ui {
|
||||
field_lock_wait.set_style(&style_red);
|
||||
}
|
||||
};
|
||||
|
||||
field_squelch.on_change = [this](int32_t v) {
|
||||
squelch = v ;
|
||||
};
|
||||
|
||||
field_volume.on_change = [this](int32_t v) {
|
||||
this->on_headphone_volume_changed(v);
|
||||
};
|
||||
|
||||
//PRE-CONFIGURATION:
|
||||
change_mode(AM_MODULATION); //Start on AM
|
||||
field_mode.set_by_value(AM_MODULATION); //Reflect the mode into the manual selector
|
||||
button_scanner_mode.set_style( &style_blue );
|
||||
button_scanner_mode.set_text( "RECON" );
|
||||
file_name.set( "=>" );
|
||||
field_squelch.set_value( squelch );
|
||||
|
||||
field_wait.set_value(wait);
|
||||
lock_wait = ( 4 * ( recon_lock_duration * recon_lock_nb_match ) );
|
||||
@@ -1501,16 +1515,13 @@ namespace ui {
|
||||
if( lock_wait < 400 )
|
||||
lock_wait = 400 ;
|
||||
field_lock_wait.set_value(lock_wait);
|
||||
|
||||
field_squelch.on_change = [this](int32_t v) {
|
||||
squelch = v ;
|
||||
};
|
||||
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
|
||||
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
|
||||
|
||||
//FILL STEP OPTIONS
|
||||
freqman_set_modulation_option( field_mode );
|
||||
freqman_set_step_option( step_mode );
|
||||
|
||||
receiver_model.set_tuning_frequency( portapack::persistent_memory::tuned_frequency() ); // Retune
|
||||
|
||||
if( filedelete )
|
||||
{
|
||||
@@ -1518,11 +1529,11 @@ namespace ui {
|
||||
}
|
||||
|
||||
frequency_file_load( false ); /* do not stop all at start */
|
||||
if( recon_thread && frequency_list.size() > 0 )
|
||||
if( recon_thread )
|
||||
{
|
||||
recon_thread->set_lock_duration( recon_lock_duration );
|
||||
recon_thread->set_lock_nb_match( recon_lock_nb_match );
|
||||
if( update_ranges )
|
||||
if( update_ranges && frequency_list.size() > 0 )
|
||||
{
|
||||
button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) );
|
||||
frequency_range.min = frequency_list[ current_index ] . frequency_a ;
|
||||
@@ -1537,7 +1548,6 @@ namespace ui {
|
||||
frequency_range.max = frequency_list[ current_index ] . frequency_a ;
|
||||
}
|
||||
}
|
||||
|
||||
if( autostart )
|
||||
{
|
||||
timer = 0 ; //Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
@@ -1560,42 +1570,77 @@ namespace ui {
|
||||
}
|
||||
audio::output::stop();
|
||||
|
||||
if( !scanner_mode)
|
||||
def_step = step_mode.selected_index(); // use def_step from manual selector
|
||||
frequency_list.clear(); // clear the existing frequency list (expected behavior)
|
||||
std::string file_input = input_file ; // default recon mode
|
||||
if( scanner_mode )
|
||||
{
|
||||
def_step = step_mode.selected_index(); //Use def_step from manual selector
|
||||
frequency_list.clear(); // clear the existing frequency list (expected behavior)
|
||||
if( !load_freqman_file_ex( input_file , frequency_list, load_freqs, load_ranges, load_hamradios ) )
|
||||
{
|
||||
desc_cycle.set(" NO " + input_file + ".TXT FILE ..." );
|
||||
file_name.set_style( &style_white );
|
||||
file_name.set( "=> NO DATA" );
|
||||
}
|
||||
else
|
||||
{
|
||||
file_name.set_style( &style_blue );
|
||||
file_name.set( "=> "+input_file );
|
||||
}
|
||||
step_mode.set_selected_index(def_step); //Impose the default step into the manual step selector
|
||||
file_input = output_file ;
|
||||
file_name.set_style( &style_red );
|
||||
button_scanner_mode.set_style( &style_red );
|
||||
button_scanner_mode.set_text( "SCANNER" );
|
||||
}
|
||||
else
|
||||
{
|
||||
def_step = step_mode.selected_index(); //Use def_step from manual selector
|
||||
frequency_list.clear(); // clear the existing frequency list (expected behavior)
|
||||
if( !load_freqman_file_ex( output_file , frequency_list, load_freqs, load_ranges, load_hamradios ) )
|
||||
file_name.set_style( &style_blue );
|
||||
button_scanner_mode.set_style( &style_blue );
|
||||
button_scanner_mode.set_text( "RECON" );
|
||||
}
|
||||
file_name.set_style( &style_white );
|
||||
desc_cycle.set_style( &style_white );
|
||||
if( !load_freqman_file_ex( file_input , frequency_list, load_freqs, load_ranges, load_hamradios ) )
|
||||
{
|
||||
file_name.set_style( &style_red );
|
||||
desc_cycle.set_style( &style_red );
|
||||
desc_cycle.set(" NO " + file_input + ".TXT FILE ..." );
|
||||
file_name.set( "=> NO DATA" );
|
||||
}
|
||||
else
|
||||
{
|
||||
file_name.set( "=> "+file_input );
|
||||
if( frequency_list.size() == 0 )
|
||||
{
|
||||
desc_cycle.set(" NO " + output_file + ".TXT FILE ..." );
|
||||
file_name.set_style( &style_white );
|
||||
file_name.set( "=> EMPTY" );
|
||||
file_name.set_style( &style_red );
|
||||
desc_cycle.set_style( &style_red );
|
||||
desc_cycle.set("/0 no entries in list" );
|
||||
file_name.set( "BadOrEmpty "+file_input );
|
||||
}
|
||||
else
|
||||
{
|
||||
file_name.set( "=> "+output_file );
|
||||
file_name.set_style( &style_red );
|
||||
if( frequency_list.size() > FREQMAN_MAX_PER_FILE )
|
||||
{
|
||||
file_name.set_style( &style_yellow );
|
||||
desc_cycle.set_style( &style_yellow );
|
||||
}
|
||||
}
|
||||
step_mode.set_selected_index(def_step); //Impose the default step into the manual step selector
|
||||
}
|
||||
|
||||
step_mode.set_selected_index(def_step); //Impose the default step into the manual step selector
|
||||
start_recon_thread();
|
||||
std::string description = "...no description..." ;
|
||||
if( frequency_list.size() != 0 )
|
||||
{
|
||||
current_index = 0 ;
|
||||
recon_thread-> set_freq_index( 0 );
|
||||
switch( frequency_list[current_index].type )
|
||||
{
|
||||
case RANGE:
|
||||
description = "R: " + frequency_list[current_index].description ;
|
||||
break ;
|
||||
case HAMRADIO:
|
||||
description = "H: " + frequency_list[current_index].description ;
|
||||
break ;
|
||||
default:
|
||||
case SINGLE:
|
||||
description = "S: " + frequency_list[current_index].description ;
|
||||
break ;
|
||||
}
|
||||
text_cycle.set_text( to_string_dec_uint( current_index + 1 , 3 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
text_cycle.set_text( " " );
|
||||
}
|
||||
desc_cycle.set( description );
|
||||
}
|
||||
|
||||
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
@@ -1745,12 +1790,12 @@ namespace ui {
|
||||
void ReconView::recon_resume() {
|
||||
audio::output::stop();
|
||||
if( !recon_thread->is_recon() )
|
||||
recon_thread->set_recon(true); // RESUME!
|
||||
recon_thread->set_recon(true); // RESUME!
|
||||
big_display.set_style(&style_white); //Back to grey color
|
||||
}
|
||||
|
||||
void ReconView::user_pause() {
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
//button_pause.set_text("<RESUME>"); //PAUSED, show resume
|
||||
userpause=true;
|
||||
continuous_lock=false;
|
||||
@@ -1758,9 +1803,9 @@ namespace ui {
|
||||
}
|
||||
|
||||
void ReconView::user_resume() {
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
//button_pause.set_text("<PAUSE>"); //Show button for pause
|
||||
userpause=false; // Resume recon
|
||||
userpause=false; // Resume recon
|
||||
continuous_lock=false;
|
||||
recon_resume();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include "file.hpp"
|
||||
#include "app_settings.hpp"
|
||||
|
||||
// maximum usable freq
|
||||
#define MAX_UFREQ 7200000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -176,7 +178,7 @@ namespace ui {
|
||||
bool check_sd_card();
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
|
||||
jammer::jammer_range_t frequency_range { false, 0, 0 }; //perfect for manual recon task too...
|
||||
jammer::jammer_range_t frequency_range { false, 0, MAX_UFREQ }; //perfect for manual recon task too...
|
||||
int32_t squelch { 0 };
|
||||
int32_t db { 0 };
|
||||
int32_t timer { 0 };
|
||||
@@ -198,8 +200,6 @@ namespace ui {
|
||||
bool load_hamradios = { true };
|
||||
bool update_ranges = { true };
|
||||
bool fwd = { true };
|
||||
// maximum usable freq
|
||||
long long int MAX_UFREQ = { 7200000000 };
|
||||
uint32_t recon_lock_nb_match = { 10 };
|
||||
uint32_t recon_lock_duration = { 50 };
|
||||
uint32_t recon_match_mode = { 0 };
|
||||
|
||||
@@ -41,7 +41,6 @@ using namespace portapack;
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav
|
||||
) {
|
||||
@@ -306,7 +305,6 @@ namespace ui {
|
||||
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
|
||||
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
@@ -405,6 +403,7 @@ namespace ui {
|
||||
&check_load_mem_at_startup,
|
||||
&button_save_mem_to_file,
|
||||
&button_load_mem_from_file,
|
||||
&button_load_mem_defaults,
|
||||
&button_return
|
||||
});
|
||||
|
||||
@@ -483,6 +482,17 @@ namespace ui {
|
||||
}
|
||||
};
|
||||
|
||||
button_load_mem_defaults.on_select = [&nav, this](Button&) {
|
||||
nav.push<ModalMessageView>(
|
||||
"Warning!",
|
||||
"This will reset the p.mem\nand set the default settings",
|
||||
YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
button_return.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
|
||||
@@ -314,7 +314,7 @@ private:
|
||||
25,
|
||||
"Save app settings"
|
||||
};
|
||||
|
||||
|
||||
Button button_save {
|
||||
{ 2 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Save"
|
||||
@@ -464,15 +464,20 @@ private:
|
||||
};
|
||||
|
||||
Button button_save_mem_to_file {
|
||||
{ 0, 9 * 16, 240, 32 },
|
||||
{ 0, 8 * 16, 240, 32 },
|
||||
"save p.mem to sdcard"
|
||||
};
|
||||
|
||||
Button button_load_mem_from_file {
|
||||
{ 0, 12 * 16, 240, 32 },
|
||||
{ 0, 10 * 16 + 4 , 240, 32 },
|
||||
"load p.mem from sdcard"
|
||||
};
|
||||
|
||||
|
||||
Button button_load_mem_defaults {
|
||||
{ 0, 12 * 16 + 8 , 240, 32 },
|
||||
"! reset p.mem, load defaults !"
|
||||
};
|
||||
|
||||
Button button_return {
|
||||
{ 16 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Return",
|
||||
|
||||
@@ -48,29 +48,27 @@ private:
|
||||
void update_tone();
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
const std::string shape_strings[9] = {
|
||||
"CW ",
|
||||
"Sine ",
|
||||
"Triangle ",
|
||||
"Saw up ",
|
||||
"Saw down ",
|
||||
"Square ",
|
||||
"Noise n20Khz",
|
||||
"Noise n10khz",
|
||||
"Noise n5khz "
|
||||
const std::string shape_strings[7] = {
|
||||
"CW-just carrier",
|
||||
"Sine signal ",
|
||||
"Triangle signal",
|
||||
"Saw up signal ",
|
||||
"Saw down signal",
|
||||
"Square signal ",
|
||||
"Noise signal " // using 16 bits LFSR register, 16 order polynomial feedback.
|
||||
};
|
||||
|
||||
bool auto_update { false };
|
||||
|
||||
Labels labels {
|
||||
{ { 6 * 8, 4 + 10 }, "Shape:", Color::light_grey() },
|
||||
{ { 7 * 8, 7 * 8 }, "Tone: Hz", Color::light_grey() },
|
||||
{ { 3 * 8, 4 + 10 }, "Shape:", Color::light_grey() },
|
||||
{ { 6 * 8, 7 * 8 }, "Tone: Hz", Color::light_grey() },
|
||||
{ { 22 * 8, 15 * 8 + 4 }, "s.", Color::light_grey() },
|
||||
{ { 8 * 8, 20 * 8 }, "Modulation: FM", Color::light_grey() }
|
||||
};
|
||||
|
||||
ImageOptionsField options_shape {
|
||||
{ 13 * 8, 4, 32, 32 },
|
||||
{ 10 * 8, 4, 32, 32 },
|
||||
Color::white(),
|
||||
Color::black(),
|
||||
{
|
||||
@@ -80,14 +78,12 @@ private:
|
||||
{ &bitmap_sig_saw_up, 3 },
|
||||
{ &bitmap_sig_saw_down, 4 },
|
||||
{ &bitmap_sig_square, 5 },
|
||||
{ &bitmap_sig_noise, 6 },
|
||||
{ &bitmap_sig_noise, 7 },
|
||||
{ &bitmap_sig_noise, 8 }
|
||||
{ &bitmap_sig_noise, 6 }
|
||||
}
|
||||
};
|
||||
|
||||
Text text_shape {
|
||||
{ 18 * 8, 4 + 10, 8 * 8, 16 },
|
||||
{ 15 * 8, 4 + 10, 8 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
@@ -98,12 +94,12 @@ private:
|
||||
};
|
||||
|
||||
Button button_update {
|
||||
{ 6 * 8, 10 * 8, 8 * 8, 3 * 8 },
|
||||
{ 5 * 8, 10 * 8, 8 * 8, 3 * 8 },
|
||||
"Update"
|
||||
};
|
||||
|
||||
Checkbox checkbox_auto {
|
||||
{ 16 * 8, 10 * 8 },
|
||||
{ 15 * 8, 10 * 8 },
|
||||
4,
|
||||
"Auto"
|
||||
};
|
||||
|
||||
@@ -250,6 +250,15 @@ std::string filesystem_error::what() const {
|
||||
}
|
||||
}
|
||||
|
||||
path path::parent_path() const {
|
||||
const auto index = _s.find_last_of(preferred_separator);
|
||||
if( index == _s.npos ) {
|
||||
return { }; // NB: Deviation from STL.
|
||||
} else {
|
||||
return _s.substr(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
path path::extension() const {
|
||||
const auto t = filename().native();
|
||||
const auto index = t.find_last_of(u'.');
|
||||
@@ -296,6 +305,14 @@ path& path::replace_extension(const path& replacement) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const path& lhs, const path& rhs) {
|
||||
return lhs.native() == rhs.native();
|
||||
}
|
||||
|
||||
bool operator!=(const path& lhs, const path& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator<(const path& lhs, const path& rhs) {
|
||||
return lhs.native() < rhs.native();
|
||||
}
|
||||
@@ -304,6 +321,18 @@ bool operator>(const path& lhs, const path& rhs) {
|
||||
return lhs.native() > rhs.native();
|
||||
}
|
||||
|
||||
path operator+(const path& lhs, const path& rhs) {
|
||||
path result = lhs;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
path operator/(const path& lhs, const path& rhs) {
|
||||
path result = lhs;
|
||||
result /= rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
std::filesystem::path path,
|
||||
std::filesystem::path wild
|
||||
@@ -311,7 +340,7 @@ directory_iterator::directory_iterator(
|
||||
{
|
||||
impl = std::make_shared<Impl>();
|
||||
const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast<const TCHAR*>(path.c_str()), reinterpret_cast<const TCHAR*>(pattern.c_str()));
|
||||
if( result != FR_OK ) {
|
||||
if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
|
||||
impl.reset();
|
||||
// TODO: Throw exception if/when I enable exceptions...
|
||||
}
|
||||
@@ -333,6 +362,20 @@ bool is_regular_file(const file_status s) {
|
||||
return !(s & AM_DIR);
|
||||
}
|
||||
|
||||
bool file_exists(const path& file_path) {
|
||||
FILINFO filinfo;
|
||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
|
||||
return fr == FR_OK;
|
||||
}
|
||||
|
||||
bool is_directory(const path& file_path) {
|
||||
FILINFO filinfo;
|
||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
|
||||
return fr == FR_OK && is_directory(static_cast<file_status>(filinfo.fattrib));
|
||||
}
|
||||
|
||||
space_info space(const path& p) {
|
||||
DWORD free_clusters { 0 };
|
||||
FATFS* fs;
|
||||
|
||||
@@ -123,6 +123,7 @@ struct path {
|
||||
return *this;
|
||||
}
|
||||
|
||||
path parent_path() const;
|
||||
path extension() const;
|
||||
path filename() const;
|
||||
path stem() const;
|
||||
@@ -151,14 +152,25 @@ struct path {
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& operator/=(const path& p) {
|
||||
if (_s.back() != preferred_separator)
|
||||
_s += preferred_separator;
|
||||
_s += p._s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& replace_extension(const path& replacement = path());
|
||||
|
||||
private:
|
||||
string_type _s;
|
||||
};
|
||||
|
||||
bool operator==(const path& lhs, const path& rhs);
|
||||
bool operator!=(const path& lhs, const path& rhs);
|
||||
bool operator<(const path& lhs, const path& rhs);
|
||||
bool operator>(const path& lhs, const path& rhs);
|
||||
path operator+(const path& lhs, const path& rhs);
|
||||
path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
@@ -227,6 +239,8 @@ inline bool operator!=(const directory_iterator& lhs, const directory_iterator&
|
||||
|
||||
bool is_directory(const file_status s);
|
||||
bool is_regular_file(const file_status s);
|
||||
bool file_exists(const path& file_path);
|
||||
bool is_directory(const path& file_path);
|
||||
|
||||
space_info space(const path& p);
|
||||
|
||||
@@ -245,6 +259,8 @@ uint32_t make_new_directory(const std::filesystem::path& dir_path);
|
||||
|
||||
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
|
||||
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory);
|
||||
|
||||
/* Gets an auto incrementing filename. */
|
||||
std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern);
|
||||
|
||||
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
|
||||
|
||||
@@ -229,7 +229,7 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
|
||||
{
|
||||
db.push_back({ frequency_a, frequency_b, description, type , modulation , bandwidth , step , tone });
|
||||
n++;
|
||||
if (n >= FREQMAN_MAX_PER_FILE) return true;
|
||||
if (n > FREQMAN_MAX_PER_FILE) return true;
|
||||
}
|
||||
|
||||
line_start = line_end + 1;
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
#include "string_format.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#define FREQMAN_DESC_MAX_LEN 30
|
||||
#define FREQMAN_MAX_PER_FILE 500 /* MAX PER FILES */
|
||||
#define FREQMAN_MAX_PER_FILE_STR "500" /*STRING OF FREQMAN_MAX_PER_FILE */
|
||||
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
|
||||
#define FREQMAN_MAX_PER_FILE 115 // Maximum of entries we can read. This is a hardware limit
|
||||
// It was tested and lowered to leave a bit of space to the caller
|
||||
#define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE
|
||||
|
||||
using namespace ui;
|
||||
using namespace std;
|
||||
|
||||
@@ -489,10 +489,13 @@ bool init() {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
if( !portapack::cpld::update_if_necessary(portapack_cpld_config()) ) {
|
||||
portapack::cpld::CpldUpdateStatus result = portapack::cpld::update_if_necessary(portapack_cpld_config());
|
||||
if ( result == portapack::cpld::CpldUpdateStatus::Program_failed ) {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
// If using a "2021/12 QFP100", press and hold the left button while booting. Should only need to do once.
|
||||
if (load_config() != 3 && load_config() != 4){
|
||||
// Mode left (R1) and right (R2,H2,H2+) bypass going into hackrf mode after failing CPLD update
|
||||
// Mode center (autodetect), up (R1) and down (R2,H2,H2+) will go into hackrf mode after failing CPLD update
|
||||
if (load_config() != 3 /* left */ && load_config() != 4 /* right */){
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -81,16 +81,21 @@ void Manager::feed(const Frame& frame) {
|
||||
const auto touch_raw = frame.touch;
|
||||
//const auto touch_stable = touch_debounce.state();
|
||||
const auto touch_stable = frame.touch;
|
||||
bool touch_pressure = false;
|
||||
bool touch_down_pressure = false;
|
||||
bool touch_up_pressure = false;
|
||||
|
||||
// Only feed coordinate averaging if there's a touch.
|
||||
// TODO: Separate threshold to gate coordinates for filtering?
|
||||
if( touch_raw ) {
|
||||
const auto metrics = calculate_metrics(frame);
|
||||
|
||||
// TODO: Add touch pressure hysteresis?
|
||||
touch_pressure = (metrics.r < r_touch_threshold);
|
||||
if( touch_pressure ) {
|
||||
constexpr float r_touch_down_threshold = 3200.0f;
|
||||
constexpr float r_touch_up_threshold = r_touch_down_threshold * 2.0f;
|
||||
|
||||
touch_down_pressure = (metrics.r < r_touch_down_threshold);
|
||||
touch_up_pressure = (metrics.r < r_touch_up_threshold);
|
||||
|
||||
if( touch_down_pressure ) {
|
||||
filter_x.feed(metrics.x * 1024);
|
||||
filter_y.feed(metrics.y * 1024);
|
||||
}
|
||||
@@ -101,7 +106,7 @@ void Manager::feed(const Frame& frame) {
|
||||
|
||||
switch(state) {
|
||||
case State::NoTouch:
|
||||
if( touch_stable && touch_pressure && !persistent_memory::disable_touchscreen()) {
|
||||
if( touch_stable && touch_down_pressure && !persistent_memory::disable_touchscreen()) {
|
||||
if( point_stable() ) {
|
||||
state = State::TouchDetected;
|
||||
touch_started();
|
||||
@@ -110,7 +115,7 @@ void Manager::feed(const Frame& frame) {
|
||||
break;
|
||||
|
||||
case State::TouchDetected:
|
||||
if( touch_stable && touch_pressure ) {
|
||||
if( touch_stable && touch_up_pressure ) {
|
||||
touch_moved();
|
||||
} else {
|
||||
state = State::NoTouch;
|
||||
|
||||
@@ -219,7 +219,6 @@ private:
|
||||
TouchDetected,
|
||||
};
|
||||
|
||||
static constexpr float r_touch_threshold = 640;
|
||||
static constexpr size_t touch_count_threshold { 3 };
|
||||
static constexpr uint32_t touch_stable_bound { 8 };
|
||||
|
||||
|
||||
@@ -163,6 +163,8 @@ void MenuView::clear() {
|
||||
}
|
||||
|
||||
menu_items.clear();
|
||||
highlighted_item = 0;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
void MenuView::add_item(MenuItem new_item) {
|
||||
@@ -209,7 +211,7 @@ MenuItemView* MenuView::item_view(size_t index) const {
|
||||
bool MenuView::set_highlighted(int32_t new_value) {
|
||||
int32_t item_count = (int32_t)menu_items.size();
|
||||
|
||||
if (new_value < 0)
|
||||
if (new_value < 0 || item_count == 0)
|
||||
return false;
|
||||
|
||||
if (new_value >= item_count)
|
||||
@@ -238,7 +240,7 @@ bool MenuView::set_highlighted(int32_t new_value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t MenuView::highlighted_index() {
|
||||
uint32_t MenuView::highlighted_index() const {
|
||||
return highlighted_item;
|
||||
}
|
||||
|
||||
@@ -262,7 +264,7 @@ bool MenuView::on_key(const KeyEvent key) {
|
||||
case KeyEvent::Select:
|
||||
case KeyEvent::Right:
|
||||
if( menu_items[highlighted_item].on_select ) {
|
||||
menu_items[highlighted_item].on_select();
|
||||
menu_items[highlighted_item].on_select(key);
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ struct MenuItem {
|
||||
std::string text;
|
||||
ui::Color color;
|
||||
const Bitmap* bitmap;
|
||||
std::function<void(void)> on_select;
|
||||
std::function<void(KeyEvent)> on_select;
|
||||
|
||||
// TODO: Prevent default-constructed MenuItems.
|
||||
// I managed to construct a menu with three extra, unspecified menu items
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
MenuItemView* item_view(size_t index) const;
|
||||
|
||||
bool set_highlighted(int32_t new_value);
|
||||
uint32_t highlighted_index();
|
||||
uint32_t highlighted_index() const;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void on_focus() override;
|
||||
|
||||
@@ -29,9 +29,25 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void text_prompt(NavigationView& nav, std::string& str, const size_t max_length, const std::function<void(std::string&)> on_done) {
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done
|
||||
) {
|
||||
text_prompt(nav, str, str.length(), max_length, on_done);
|
||||
}
|
||||
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
uint32_t cursor_pos,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done
|
||||
) {
|
||||
//if (persistent_memory::ui_config_textentry() == 0) {
|
||||
auto te_view = nav.push<AlphanumView>(str, max_length);
|
||||
te_view->set_cursor(cursor_pos);
|
||||
te_view->on_changed = [on_done](std::string& value) {
|
||||
if (on_done)
|
||||
on_done(value);
|
||||
@@ -54,7 +70,7 @@ TextField::TextField(
|
||||
uint32_t length
|
||||
) : Widget{ { position, { 8 * static_cast<int>(length), 16 } } },
|
||||
text_{ str },
|
||||
max_length_{ std::max<size_t>(max_length, 1) },
|
||||
max_length_{ std::max<size_t>(max_length, str.length()) },
|
||||
char_count_{ std::max<uint32_t>(length, 1) },
|
||||
cursor_pos_{ text_.length() },
|
||||
insert_mode_{ true }
|
||||
@@ -66,36 +82,11 @@ const std::string& TextField::value() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void TextField::set(const std::string& str) {
|
||||
// Assume that setting the string implies we want the whole thing.
|
||||
max_length_ = std::max(max_length_, str.length());
|
||||
|
||||
text_ = str;
|
||||
cursor_pos_ = str.length();
|
||||
set_cursor(str.length());
|
||||
}
|
||||
|
||||
void TextField::set_cursor(uint32_t pos) {
|
||||
cursor_pos_ = std::min<size_t>(pos, text_.length());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::set_max_length(size_t max_length) {
|
||||
// Doesn't make sense, ignore.
|
||||
if (max_length == 0)
|
||||
return;
|
||||
|
||||
if (max_length < text_.length()) {
|
||||
text_.erase(max_length - 1);
|
||||
text_.shrink_to_fit();
|
||||
} else {
|
||||
text_.reserve(max_length);
|
||||
}
|
||||
|
||||
max_length_ = max_length;
|
||||
set_cursor(cursor_pos_);
|
||||
}
|
||||
|
||||
void TextField::set_insert_mode() {
|
||||
insert_mode_ = true;
|
||||
}
|
||||
@@ -211,6 +202,10 @@ void TextEntryView::char_add(const char c) {
|
||||
text_input.char_add(c);
|
||||
}
|
||||
|
||||
void TextEntryView::set_cursor(uint32_t pos) {
|
||||
text_input.set_cursor(pos);
|
||||
}
|
||||
|
||||
void TextEntryView::focus() {
|
||||
text_input.focus();
|
||||
}
|
||||
@@ -227,7 +222,6 @@ TextEntryView::TextEntryView(
|
||||
});
|
||||
|
||||
button_ok.on_select = [this, &str, &nav](Button&) {
|
||||
str.shrink_to_fit(); // NB: str is the TextField string.
|
||||
if (on_changed)
|
||||
on_changed(str);
|
||||
nav.pop();
|
||||
|
||||
@@ -53,9 +53,7 @@ public:
|
||||
|
||||
const std::string& value() const;
|
||||
|
||||
void set(const std::string& str);
|
||||
void set_cursor(uint32_t pos);
|
||||
void set_max_length(size_t max_length);
|
||||
void set_insert_mode();
|
||||
void set_overwrite_mode();
|
||||
|
||||
@@ -82,6 +80,8 @@ public:
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Text entry"; };
|
||||
|
||||
void set_cursor(uint32_t pos);
|
||||
|
||||
protected:
|
||||
TextEntryView(NavigationView& nav, std::string& str, size_t max_length);
|
||||
@@ -101,7 +101,22 @@ protected:
|
||||
};
|
||||
};
|
||||
|
||||
void text_prompt(NavigationView& nav, std::string& str, size_t max_length, const std::function<void(std::string&)> on_done = nullptr);
|
||||
// Show the TextEntry view to receive keyboard input.
|
||||
// NB: This function returns immediately. 'str' is taken
|
||||
// by reference and its lifetime must be ensured by the
|
||||
// caller until the TextEntry view is dismissed.
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done = nullptr);
|
||||
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
uint32_t cursor_pos,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done = nullptr);
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
|
||||
@@ -76,6 +76,15 @@ namespace ui
|
||||
{
|
||||
return reinterpret_cast<T *>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
// Pushes a new view under the current on the stack so the current view returns into this new one.
|
||||
template <class T, class... Args>
|
||||
void push_under_current(Args &&...args)
|
||||
{
|
||||
auto new_view = std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...));
|
||||
view_stack.insert(view_stack.end() - 1, std::move(new_view));
|
||||
}
|
||||
|
||||
template <class T, class... Args>
|
||||
T *replace(Args &&...args)
|
||||
{
|
||||
|
||||
@@ -61,23 +61,29 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
} else if (tone_shape == 5) {
|
||||
// Square
|
||||
sample = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128;
|
||||
} else if (tone_shape == 6) { // taps: 6 5; feedback polynomial: x^6 + x^5 + 1 , Periode 63 = 2^n-1,it generates armonincs n x 20Khz
|
||||
// White Noise generator, pseudo random noise generator, 8 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
|
||||
} else if (tone_shape == 6) {
|
||||
// Noise generator, pseudo random noise generator, 16 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
|
||||
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
bit = ((lfsr >> 2) ^ (lfsr >> 3)) & 1;
|
||||
lfsr = (lfsr >> 1) | (bit << 7);
|
||||
sample = lfsr;
|
||||
} else if (tone_shape == 7) { // taps: 7 6; feedback polynomial: x^7 + x^6 + 1 , Periode 127 = 2^n-1,it generates armonincs n x 10Khz
|
||||
bit = ((lfsr >> 1) ^ (lfsr >> 2)) & 1;
|
||||
lfsr = (lfsr >> 1) | (bit << 7);
|
||||
sample = lfsr;
|
||||
} else if (tone_shape == 8) { //taps:8,6,5,4;feedback polynomial: x^8 + x^6 + x^5 + x^4 + 1,Periode 255= 2^n-1, armonics n x 5khz
|
||||
bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 4)) & 1;
|
||||
lfsr = (lfsr >> 1) | (bit << 7);
|
||||
sample = lfsr;
|
||||
}
|
||||
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
|
||||
// Periode 65535= 2^n-1, quite continuous .
|
||||
if (counter == 0) { // we slow down the shift register, because the pseudo random noise clock freq was too high for modulator.
|
||||
bit_16 = ((lfsr_16 >> 0) ^ (lfsr_16 >> 1) ^ (lfsr_16 >> 3) ^ (lfsr_16 >> 4) ^ (lfsr_16 >> 12) & 1);
|
||||
lfsr_16 = (lfsr_16 >> 1) | (bit_16 << 15);
|
||||
sample = (lfsr_16 & 0x00FF); // main pseudo random noise generator.
|
||||
}
|
||||
if (counter == 5) { // after many empiric test, that combination mix of >>4 and >>5, gives a reasonable trade off white noise / good rf power level .
|
||||
sample = ((lfsr_16 & 0b0000111111110000) >> 4); // just changing the spectrum shape .
|
||||
}
|
||||
if (counter == 10) {
|
||||
sample = ((lfsr_16 & 0b0001111111100000) >> 5); // just changing the spectrum shape .
|
||||
}
|
||||
counter++;
|
||||
if (counter ==15) {
|
||||
counter=0;
|
||||
}
|
||||
}
|
||||
|
||||
if (tone_shape < 6) {
|
||||
if (tone_shape < 6) { // we are in periodic signals, we need tone phases update.
|
||||
tone_phase += tone_delta;
|
||||
}
|
||||
|
||||
@@ -114,8 +120,8 @@ void SigGenProcessor::on_message(const Message* const msg) {
|
||||
fm_delta = message.bw * (0xFFFFFFULL / 1536000);
|
||||
tone_shape = message.shape;
|
||||
|
||||
// lfsr = 0x54DF0119;
|
||||
lfsr = seed_value ;
|
||||
// lfsr = seed_value ; // Finally not used , init lfsr 8 bits.
|
||||
lfsr_16 = seed_value_16; // init lfsr 16 bits.
|
||||
|
||||
configured = true;
|
||||
break;
|
||||
|
||||
@@ -42,11 +42,14 @@ private:
|
||||
uint8_t tone_shape { };
|
||||
uint32_t sample_count { 0 };
|
||||
bool auto_off { };
|
||||
int32_t phase { 0 }, sphase { 0 }, delta { 0 }; // they may have sign .
|
||||
int8_t sample { 0 }, re { 0 }, im { 0 }; // they may have sign .
|
||||
uint8_t seed_value = {0x56}; // seed : any nonzero start state will work.
|
||||
uint8_t lfsr { }, bit { }; // bit must be 8-bit to allow bit<<7 later in the code */
|
||||
|
||||
int32_t phase { 0 }, sphase { 0 }, delta { 0 }; // they may have sign in the pseudo random sample generation.
|
||||
int8_t sample { 0 }, re { 0 }, im { 0 }; // they have sign + and -.
|
||||
uint16_t seed_value_16 = {0xACE1}; // seed 16 bits lfsr : any nonzero start state will work.
|
||||
uint16_t lfsr_16 { }, bit_16 { }; // bit must be 16-bit to allow bit<<15 later in the code */
|
||||
uint8_t counter {0};
|
||||
// uint8_t seed_value = {0x56}; // Finally not used lfsr of 8 bits , seed 8blfsr : any nonzero start state will work.
|
||||
// uint8_t lfsr { }, bit { }; // Finally not used lfsr of 8 bits , bit must be 8-bit to allow bit<<7 later in the code */
|
||||
|
||||
TXProgressMessage txprogress_message { };
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
namespace portapack {
|
||||
namespace cpld {
|
||||
|
||||
bool update_if_necessary(
|
||||
CpldUpdateStatus update_if_necessary(
|
||||
const Config config
|
||||
) {
|
||||
jtag::GPIOTarget target {
|
||||
@@ -51,7 +51,7 @@ bool update_if_necessary(
|
||||
|
||||
/* Run-Test/Idle */
|
||||
if( !cpld.idcode_ok() ) {
|
||||
return false;
|
||||
return CpldUpdateStatus::Idcode_check_failed;
|
||||
}
|
||||
|
||||
cpld.sample();
|
||||
@@ -62,7 +62,7 @@ bool update_if_necessary(
|
||||
* in passive state.
|
||||
*/
|
||||
if( !cpld.silicon_id_ok() ) {
|
||||
return false;
|
||||
return CpldUpdateStatus::Silicon_id_check_failed;
|
||||
}
|
||||
|
||||
/* Verify CPLD contents against current bitstream. */
|
||||
@@ -86,7 +86,7 @@ bool update_if_necessary(
|
||||
cpld.disable();
|
||||
}
|
||||
|
||||
return ok;
|
||||
return ok ? CpldUpdateStatus::Success : CpldUpdateStatus::Program_failed;
|
||||
}
|
||||
|
||||
} /* namespace cpld */
|
||||
|
||||
@@ -27,7 +27,14 @@
|
||||
namespace portapack {
|
||||
namespace cpld {
|
||||
|
||||
bool update_if_necessary(
|
||||
enum class CpldUpdateStatus {
|
||||
Success = 0,
|
||||
Idcode_check_failed = 1,
|
||||
Silicon_id_check_failed = 2,
|
||||
Program_failed = 3
|
||||
};
|
||||
|
||||
CpldUpdateStatus update_if_necessary(
|
||||
const Config config
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Early 2023 joyel24 added meteomodem M20 support
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -113,9 +114,9 @@ GPS_data Packet::get_GPS_data() const
|
||||
}
|
||||
else if (type_ == Type::Meteomodem_M20)
|
||||
{
|
||||
result.alt = 0; //((reader_bi_m.read(8 * 8, 32) / 100) - 48) / 250 ; //Disable innacurate altitude for the moment.
|
||||
result.lat = reader_bi_m.read(28 * 8, 32) / 1000000.0 ; //https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
|
||||
result.lon = reader_bi_m.read(32 * 8, 32) / 1000000.0 ;
|
||||
result.alt = reader_bi_m.read(8 * 8, 24) / 100.0 ; // <|
|
||||
result.lat = reader_bi_m.read(28 * 8, 32) / 1000000.0 ; // <| Inspired by https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
|
||||
result.lon = reader_bi_m.read(32 * 8, 32) / 1000000.0 ; // <|
|
||||
}
|
||||
else if (type_ == Type::Vaisala_RS41_SG)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user