diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 95035c7b..ecfc9e3c 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -218,6 +218,7 @@ set(CPPSRC ui/ui_btngrid.cpp ui/ui_receiver.cpp ui/ui_rssi.cpp + ui/ui_freqlist.cpp ui/ui_tv.cpp ui/ui_spectrum.cpp ui/ui_tabview.cpp diff --git a/firmware/application/apps/ui_freqman.cpp b/firmware/application/apps/ui_freqman.cpp index fdf3a103..96416b68 100644 --- a/firmware/application/apps/ui_freqman.cpp +++ b/firmware/application/apps/ui_freqman.cpp @@ -97,8 +97,10 @@ void FreqManBaseView::change_category(int32_t category_id) { if (!load_freqman_file(file_list[categories[current_category_id].second], database)) error_ = ERROR_ACCESS; - else + else { + menu_view.set_db(database); refresh_list(); + } } void FreqManBaseView::refresh_list() { @@ -108,20 +110,6 @@ void FreqManBaseView::refresh_list() { } else { if (on_refresh_widgets) on_refresh_widgets(false); - - menu_view.clear(); - - for (size_t n = 0; n < database.size(); n++) { - menu_view.add_item({freqman_item_string(database[n], 30), - ui::Color::white(), - nullptr, - [this](KeyEvent) { - if (on_select_frequency) - on_select_frequency(); - }}); - } - - menu_view.set_highlighted(0); // Refresh } } @@ -206,18 +194,13 @@ FrequencyLoadView::FrequencyLoadView( // Resize menu view to fill screen menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8}); - // Just to allow exit on left - menu_view.on_left = [&nav, this]() { - nav.pop(); - }; - change_category(last_category_id); refresh_list(); - on_select_frequency = [&nav, this]() { + menu_view.on_select = [&nav, this](FreqManUIList&) { nav_.pop(); - auto& entry = database[menu_view.highlighted_index()]; + auto& entry = database[menu_view.get_index()]; if (entry.type == RANGE) { // User chose a frequency range entry @@ -235,14 +218,14 @@ FrequencyLoadView::FrequencyLoadView( } void FrequencyManagerView::on_edit_freq(rf::Frequency f) { - database[menu_view.highlighted_index()].frequency_a = f; + database[menu_view.get_index()].frequency_a = f; save_freqman_file(file_list[categories[current_category_id].second], database); refresh_list(); } void FrequencyManagerView::on_edit_desc(NavigationView& nav) { text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) { - database[menu_view.highlighted_index()].description = buffer; + database[menu_view.get_index()].description = buffer; refresh_list(); save_freqman_file(file_list[categories[current_category_id].second], database); }); @@ -258,7 +241,7 @@ void FrequencyManagerView::on_new_category(NavigationView& nav) { } void FrequencyManagerView::on_delete() { - database.erase(database.begin() + menu_view.highlighted_index()); + database.erase(database.begin() + menu_view.get_index()); save_freqman_file(file_list[categories[current_category_id].second], database); refresh_list(); } @@ -292,15 +275,10 @@ FrequencyManagerView::FrequencyManagerView( &button_edit_desc, &button_delete}); - // Just to allow exit on left - menu_view.on_left = [&nav, this]() { - nav.pop(); - }; - change_category(last_category_id); refresh_list(); - on_select_frequency = [this]() { + menu_view.on_select = [this](FreqManUIList&) { button_edit_freq.focus(); }; @@ -310,14 +288,14 @@ FrequencyManagerView::FrequencyManagerView( }; button_edit_freq.on_select = [this, &nav](Button&) { - auto new_view = nav.push(database[menu_view.highlighted_index()].frequency_a); + auto new_view = nav.push(database[menu_view.get_index()].frequency_a); new_view->on_changed = [this](rf::Frequency f) { on_edit_freq(f); }; }; button_edit_desc.on_select = [this, &nav](Button&) { - desc_buffer = database[menu_view.highlighted_index()].description; + desc_buffer = database[menu_view.get_index()].description; on_edit_desc(nav); }; diff --git a/firmware/application/apps/ui_freqman.hpp b/firmware/application/apps/ui_freqman.hpp index 2a727064..15491f2b 100644 --- a/firmware/application/apps/ui_freqman.hpp +++ b/firmware/application/apps/ui_freqman.hpp @@ -28,6 +28,7 @@ #include "ui_receiver.hpp" #include "ui_textentry.hpp" #include "freqman.hpp" +#include "ui_freqlist.hpp" namespace ui { @@ -65,9 +66,9 @@ class FreqManBaseView : public View { 14, {}}; - MenuView menu_view{ - {0, 3 * 8, 240, 23 * 8}, - true}; + FreqManUIList menu_view{ + {0, 3 * 8, 240, 23 * 8}}; + Text text_empty{ {7 * 8, 12 * 8, 16 * 8, 16}, "Empty category !", diff --git a/firmware/application/apps/ui_recon.cpp b/firmware/application/apps/ui_recon.cpp index b772a34d..8646c6be 100644 --- a/firmware/application/apps/ui_recon.cpp +++ b/firmware/application/apps/ui_recon.cpp @@ -36,7 +36,11 @@ void ReconView::clear_freqlist_for_ui_action() { freqlist_cleared_for_ui_action = true; // if in manual mode, there is enough memory to load freqman files, else we have to unload/reload if (!manual_mode) { - frequency_list.clear(); + // clear and shrink_to_fit are not enough to really start with a new, clean, empty vector + // swap is the only way to achieve a perfect memory liberation + std::vector().swap(frequency_list); + } else { + frequency_list.shrink_to_fit(); } } @@ -687,7 +691,10 @@ ReconView::ReconView(NavigationView& nav) } else { audio::output::stop(); - frequency_list.clear(); + // clear and shrink_to_fit are not enough to really start with a new, clean, empty vector + // swap is the only way to achieve a perfect memory liberation + std::vector().swap(frequency_list); + freqman_entry manual_freq_entry; def_step = step_mode.selected_index(); // max range val @@ -895,8 +902,10 @@ void ReconView::frequency_file_load(bool stop_all_before) { audio::output::stop(); 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 + // clear and shrink_to_fit are not enough to really start with a new, clean, empty vector + // swap is the only way to achieve a perfect memory liberation + std::vector().swap(frequency_list); // clear the existing frequency list (expected behavior) + std::string file_input = input_file; // default recon mode if (scanner_mode) { file_input = output_file; file_name.set_style(&style_red); diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp index b2656c1f..82ce324d 100644 --- a/firmware/application/freqman.cpp +++ b/firmware/application/freqman.cpp @@ -114,35 +114,40 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq char* line_end; std::string description; rf::Frequency frequency_a, frequency_b; - char file_data[257]; + const int read_buf_size = 128; + char file_data[read_buf_size + 1]; freqman_entry_type type; freqman_index_t modulation = 0; freqman_index_t bandwidth = 0; freqman_index_t step = 0; freqman_index_t tone = 0; - db.clear(); + // these are not enough to really start with a new, clean, empty vector + // swap is the only way to achieve a perfect memory liberation + // db.clear(); + // db.shrink_to_fit(): + std::vector().swap(db); auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT"); if (result.is_valid()) return false; while (1) { - // Read a 256 bytes block from file + // Read a read_buf_size bytes block from file freqman_file.seek(file_position); - memset(file_data, 0, 257); - auto read_size = freqman_file.read(file_data, 256); + memset(file_data, 0, read_buf_size + 1); + auto read_size = freqman_file.read(file_data, read_buf_size); if (read_size.is_error()) return false; // Read error - file_position += 256; + file_position += read_buf_size; // Reset line_start to beginning of buffer line_start = file_data; // If EOF reached, insert 0x0A after, in case the last line doesn't have a C/R - if (read_size.value() < 256) + if (read_size.value() < read_buf_size) *(line_start + read_size.value()) = 0x0A; // Look for complete lines in buffer @@ -222,8 +227,7 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq pos += 2; length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN); description = string(pos, length); - } else - description = "-"; + } if ((type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios)) { db.push_back({frequency_a, frequency_b, description, type, modulation, bandwidth, step, tone}); n++; @@ -231,14 +235,14 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq } line_start = line_end + 1; - if (line_start - file_data >= 256) break; + if (line_start - file_data >= read_buf_size) break; } - if (read_size.value() != 256) + if (read_size.value() != read_buf_size) break; // End of file // Restart at beginning of last incomplete line - file_position -= (file_data + 256 - line_start); + file_position -= (file_data + read_buf_size - line_start); } /* populate implicitly specified modulation / bandwidth */ diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index 3698cc1e..0d93acde 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -32,10 +32,10 @@ #include "string_format.hpp" #include "ui_widget.hpp" -#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 +#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 60 // Maximum of entries we can read. This is a hardware limit + // It was tested and lowered to leave a bit of space to the caller +#define FREQMAN_MAX_PER_FILE_STR "60" // STRING OF FREQMAN_MAX_PER_FILE using namespace ui; using namespace std; diff --git a/firmware/application/ui/ui_freqlist.cpp b/firmware/application/ui/ui_freqlist.cpp new file mode 100644 index 00000000..b7acefc2 --- /dev/null +++ b/firmware/application/ui/ui_freqlist.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_freqlist.hpp" + +#include "baseband_api.hpp" +#include "utility.hpp" + +#include + +namespace ui { +FreqManUIList::FreqManUIList( + Rect parent_rect, + bool instant_exec) + : Widget{parent_rect}, + instant_exec_{instant_exec} { + this->set_focusable(true); +} + +void FreqManUIList::set_highlighted_index(int index) { + if ((unsigned)(current_index + index) >= freqlist_db.size()) + return; + if (index < 0) { + index = 0; + if (current_index > 0) + current_index--; + } + if (index >= freqlist_nb_lines) { + index = freqlist_nb_lines - 1; + if ((unsigned)(current_index + index) < freqlist_db.size()) + current_index++; + else + current_index = freqlist_db.size() - freqlist_nb_lines - 1; + } + highlighted_index = index; +} + +uint8_t FreqManUIList::get_index() { + return current_index + highlighted_index; +} + +void FreqManUIList::paint(Painter& painter) { + freqlist_nb_lines = 0; + const auto r = screen_rect(); + uint8_t focused = has_focus(); + const Rect r_widget_screen{r.left() + focused, r.top() + focused, r.width() - 2 * focused, r.height() - 2 * +focused}; + painter.fill_rectangle( + r_widget_screen, + Color::black()); + if (freqlist_db.size() == 0) + return; + uint8_t nb_lines = 0; + for (uint8_t it = current_index; it < freqlist_db.size(); it++) { + uint8_t line_height = (int)nb_lines * char_height; + if (line_height < (r.height() - char_height)) { + std::string description = freqman_item_string(freqlist_db[it], 30); + // line is within the widget + painter.draw_string( + {0, r.location().y() + (int)nb_lines * char_height}, + style_default, description); + if (nb_lines == highlighted_index) { + const Rect r_highlighted_freq{0, r.location().y() + (int)nb_lines * char_height, 240, char_height}; + painter.draw_rectangle( + r_highlighted_freq, + Color::white()); + } + nb_lines++; + } else { + // rect is filled, we can break + break; + } + } + freqlist_nb_lines = nb_lines; + if (has_focus() || highlighted()) { + const Rect r_focus{r.left(), r.top(), r.width(), r.height()}; + painter.draw_rectangle( + r_focus, + Color::white()); + } +} + +void FreqManUIList::set_db(freqman_db& db) { + freqlist_db = db; + current_index = 0; + highlighted_index = 0; +} + +void FreqManUIList::on_focus() { + if (on_highlight) + on_highlight(*this); +} + +bool FreqManUIList::on_key(const KeyEvent key) { + if (key == KeyEvent::Select) { + if (on_select) { + on_select(*this); + return true; + } + } else { + if (on_dir) { + return on_dir(*this, key); + } + } + return false; +} + +bool FreqManUIList::on_touch(const TouchEvent event) { + switch (event.type) { + case TouchEvent::Type::Start: + set_highlighted(true); + set_dirty(); + if (on_touch_press) { + on_touch_press(*this); + } + if (on_select && instant_exec_) { + on_select(*this); + } + return true; + case TouchEvent::Type::End: + set_highlighted(false); + set_dirty(); + if (on_touch_release) { + on_touch_release(*this); + } + if (on_select && !instant_exec_) { + on_select(*this); + } + return true; + default: + return false; + } +} + +bool FreqManUIList::on_encoder(EncoderEvent delta) { + set_highlighted_index((int)highlighted_index + delta); + set_dirty(); + return true; +} +} /* namespace ui */ diff --git a/firmware/application/ui/ui_freqlist.hpp b/firmware/application/ui/ui_freqlist.hpp new file mode 100644 index 00000000..cf37a928 --- /dev/null +++ b/firmware/application/ui/ui_freqlist.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __UI_FREQLIST_H__ +#define __UI_FREQLIST_H__ + +#include "ui.hpp" +#include "ui_font_fixed_5x8.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "event_m0.hpp" +#include "message.hpp" +#include "freqman.hpp" +#include + +namespace ui { + +class FreqManUIList : public Widget { + public: + std::function on_select{}; + std::function on_touch_release{}; // Executed when releasing touch, after on_select. + std::function on_touch_press{}; // Executed when touching, before on_select. + std::function on_dir{}; + std::function on_highlight{}; + + FreqManUIList(Rect parent_rect, bool instant_exec); // instant_exec: Execute on_select when you touching instead of releasing + FreqManUIList( + Rect parent_rect) + : FreqManUIList{parent_rect, false} { + } + FreqManUIList() + : FreqManUIList{{}, {}} { + } + + void paint(Painter& painter) override; + void on_focus() override; + bool on_key(const KeyEvent key) override; + bool on_touch(const TouchEvent event) override; + bool on_encoder(EncoderEvent delta) override; + + bool set_highlighted_index(int index); // set highlighted_index and return capped highlighted_index value to set in caller + uint8_t get_index(); // return highlighed + index + void set_db(freqman_db& db); + + private: + static constexpr Style style_default{ + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::white(), + }; + + static constexpr int8_t char_height = 16; + bool instant_exec_{false}; + freqman_db freqlist_db{}; + int current_index{0}; + int highlighted_index{0}; + int freqlist_nb_lines{0}; +}; + +} // namespace ui + +#endif /*__UI_FREQLIST_H__*/