diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index f2198d33..ed48f7db 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -140,13 +140,13 @@ set(CPPSRC afsk.cpp rds.cpp ctcss.cpp + freqman.cpp ${COMMON}/lcd_ili9341.cpp ${COMMON}/ui.cpp ${COMMON}/ui_text.cpp ${COMMON}/ui_widget.cpp ${COMMON}/ui_painter.cpp ${COMMON}/ui_focus.cpp - ${COMMON}/msgpack.cpp ui_about.cpp ui_adsbtx.cpp ui_afsksetup.cpp diff --git a/firmware/application/Makefile b/firmware/application/Makefile index bd462e28..1a7bcd33 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -2408,33 +2408,6 @@ __/common/message_queue.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/__/common/message_queue.cpp.s .PHONY : __/common/message_queue.cpp.s -__/common/msgpack.obj: __/common/msgpack.cpp.obj - -.PHONY : __/common/msgpack.obj - -# target to build an object file -__/common/msgpack.cpp.obj: - cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/__/common/msgpack.cpp.obj -.PHONY : __/common/msgpack.cpp.obj - -__/common/msgpack.i: __/common/msgpack.cpp.i - -.PHONY : __/common/msgpack.i - -# target to preprocess a source file -__/common/msgpack.cpp.i: - cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/__/common/msgpack.cpp.i -.PHONY : __/common/msgpack.cpp.i - -__/common/msgpack.s: __/common/msgpack.cpp.s - -.PHONY : __/common/msgpack.s - -# target to generate assembly for a file -__/common/msgpack.cpp.s: - cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/__/common/msgpack.cpp.s -.PHONY : __/common/msgpack.cpp.s - __/common/png_writer.obj: __/common/png_writer.cpp.obj .PHONY : __/common/png_writer.obj @@ -3299,6 +3272,33 @@ filewriter.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/filewriter.cpp.s .PHONY : filewriter.cpp.s +freqman.obj: freqman.cpp.obj + +.PHONY : freqman.obj + +# target to build an object file +freqman.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/freqman.cpp.obj +.PHONY : freqman.cpp.obj + +freqman.i: freqman.cpp.i + +.PHONY : freqman.i + +# target to preprocess a source file +freqman.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/freqman.cpp.i +.PHONY : freqman.cpp.i + +freqman.s: freqman.cpp.s + +.PHONY : freqman.s + +# target to generate assembly for a file +freqman.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/freqman.cpp.s +.PHONY : freqman.cpp.s + hackrf_cpld_data.obj: hackrf_cpld_data.cpp.obj .PHONY : hackrf_cpld_data.obj @@ -5316,9 +5316,6 @@ help: @echo "... __/common/message_queue.obj" @echo "... __/common/message_queue.i" @echo "... __/common/message_queue.s" - @echo "... __/common/msgpack.obj" - @echo "... __/common/msgpack.i" - @echo "... __/common/msgpack.s" @echo "... __/common/png_writer.obj" @echo "... __/common/png_writer.i" @echo "... __/common/png_writer.s" @@ -5415,6 +5412,9 @@ help: @echo "... filewriter.obj" @echo "... filewriter.i" @echo "... filewriter.s" + @echo "... freqman.obj" + @echo "... freqman.i" + @echo "... freqman.s" @echo "... hackrf_cpld_data.obj" @echo "... hackrf_cpld_data.i" @echo "... hackrf_cpld_data.s" diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index c157ecfc..d0e450b2 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -76,26 +76,18 @@ static constexpr Bitmap bitmap_speaker { }; static constexpr uint8_t bitmap_more_data[] = { - 0x00, 0x00, - 0xC0, 0x03, - 0xC0, 0x03, - 0xC0, 0x03, - 0xC0, 0x03, - 0xC0, 0x03, - 0xC0, 0x03, - 0xC0, 0x03, - 0xC6, 0x63, - 0xCF, 0xF3, - 0xDE, 0x7B, - 0xFC, 0x3F, - 0xF8, 0x1F, - 0xF0, 0x0F, - 0xE0, 0x07, - 0xC0, 0x03 + 0x18, + 0x18, + 0x18, + 0x18, + 0xFF, + 0x7E, + 0x3C, + 0x18, }; static constexpr Bitmap bitmap_more { - { 16, 16 }, bitmap_more_data + { 8, 8 }, bitmap_more_data }; static constexpr uint8_t bitmap_rssipwm_data[] = { diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp new file mode 100644 index 00000000..8d5bfd19 --- /dev/null +++ b/firmware/application/freqman.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 "freqman.hpp" + +bool load_freqman_file(std::vector &frequencies) { + File freqs_file; + size_t end, n = 0; + char * file_buffer; + char * desc_pos; + char desc_buffer[32] = { 0 }; + std::string description; + rf::Frequency value; + + file_buffer = (char *)chHeapAlloc(0, 2048); + + while (freqs_file.open("freqman.txt").is_valid()) { + auto result = freqs_file.create("freqman.txt"); + if (result.is_valid()) { + return false; + } + } + + freqs_file.read(file_buffer, 2048); + + char * pos = file_buffer; + while ((pos = strstr(pos, "f=")) && n < 32) { + pos += 2; + + value = strtol(pos, nullptr, 10); + + desc_pos = strstr(pos, "d="); + if (desc_pos) { + desc_pos += 2; + end = strcspn(desc_pos, ",\x0D\x0A"); // CR LF + if (end > 31) end = 31; + memcpy(desc_buffer, desc_pos, end); + desc_buffer[end] = (char)0; + description = desc_buffer; + pos = desc_pos; + } else { + description = "-"; + } + + frequencies.push_back({ value, "", description }); + } + + chHeapFree(file_buffer); + + return true; +} + +bool save_freqman_file(std::vector &frequencies) { + File freqs_file; + size_t n; + std::string item_string; + + auto result = freqs_file.create("freqman.txt"); + if (result.is_valid()) return false; + + for (n = 0; n < frequencies.size(); n++) { + item_string = "f=" + to_string_dec_uint(frequencies[n].value); + + if (frequencies[n].description.size()) + item_string += ",d=" + frequencies[n].description; + + freqs_file.write_line(item_string); + } + + return true; +} + +std::string freqman_item_string(freqman_entry &entry) { + std::string item_string, frequency_str, description; + char temp_buffer[32]; + rf::Frequency value; + + value = entry.value; + entry.frequency_str = to_string_dec_int(value / 1000000, 4) + "." + + to_string_dec_int((value / 100) % 10000, 4, '0'); + + if (entry.description.size() <= 19) { + item_string = entry.frequency_str + ":" + entry.description; + } else { + memcpy(temp_buffer, entry.description.c_str(), 16); + temp_buffer[16] = (char)0; + item_string = entry.frequency_str + ":" + temp_buffer + "..."; + } + + return item_string; +} diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp new file mode 100644 index 00000000..6523b3af --- /dev/null +++ b/firmware/application/freqman.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 +#include +#include "file.hpp" +#include "ui_receiver.hpp" +#include "string_format.hpp" + +#ifndef __FREQMAN_H__ +#define __FREQMAN_H__ + +using namespace ui; + +struct freqman_entry { + rf::Frequency value; + std::string frequency_str; + std::string description; +}; + +bool load_freqman_file(std::vector &frequencies); +bool save_freqman_file(std::vector &frequencies); +std::string freqman_item_string(freqman_entry &frequencies); + +#endif/*__FREQMAN_H__*/ diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 3d707773..a3bbf576 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -23,30 +23,29 @@ // Color bitmaps generated with: // Gimp image > indexed colors (16), then "xxd -i *.bmp" +//BUG: Xylos doesn't play last tone ? +//BUG (fixed ?): Unistroke text entry screen doesn't care about string max length parameter //TEST: Imperial in whipcalc //TEST: Numbers //TEST: Jammer +//TEST: Frequency manager + save/load -//BUG: Xylos doesn't play last tone ? -//BUG (fixed ?): Soundboard crashes on exit if no wav files on sd card -//BUG (fixed ?): Unistroke text entry screen doesn't care about string max length parameter //BUG: POCSAG RX sometimes misses the first codeword after SYNC //BUG: Check AFSK transmit end, skips last bits ? //BUG: RDS doesn't stop baseband when stopping tx ? +//TODO: File browser ? //TODO: Mousejack ? //TODO: Waveform widget ? -//TODO: Frequency manager save/load (ui_freqman) +//TODO: Wav visualizer //TODO: Move frequencykeypad from ui_receiver to ui_widget (used everywhere) //TODO: ADS-B draw trajectory + GPS coordinates + scale, and playback //TODO: Finish EPAR tx -//TODO: Wav visualizer //TODO: Analog TV tx with camcorder font character generator //TODO: Test dual tone in proc_tones and remove proc_dtmf_tx //TODO: Morse coder for foxhunts //TODO: Make Morse coder and Whistle use proc_tones //TODO: RDS multiple groups (sequence) -//TODO: Frequency manager //TODO: IQ replay //TODO: Use ModalMessageView confirmation for TX ? //TODO: Show address/data bit fields in OOK TX diff --git a/firmware/application/ui_alphanum.hpp b/firmware/application/ui_alphanum.hpp index 0e120889..edd5d8ac 100644 --- a/firmware/application/ui_alphanum.hpp +++ b/firmware/application/ui_alphanum.hpp @@ -62,7 +62,7 @@ private: void update_text(); Text text_input { - { 8, 0, 224, 16 } + { 8, 0, 232, 16 } }; std::array buttons; diff --git a/firmware/application/ui_freqman.cpp b/firmware/application/ui_freqman.cpp index 8c518170..775198aa 100644 --- a/firmware/application/ui_freqman.cpp +++ b/firmware/application/ui_freqman.cpp @@ -24,7 +24,6 @@ #include "portapack.hpp" #include "event_m0.hpp" -#include "portapack_shared_memory.hpp" #include @@ -32,73 +31,197 @@ using namespace portapack; namespace ui { +void FrequencySaveView::on_save_name(NavigationView& nav) { + textentry(nav, desc_buffer, 28); + frequencies.push_back({ value_, "", desc_buffer }); + nav.pop(); +} +void FrequencySaveView::on_save_timestamp(NavigationView& nav) { + frequencies.push_back({ value_, "", text_timestamp.text() }); + nav.pop(); +} + void FrequencySaveView::focus() { button_save_timestamp.focus(); + + if (error) + nav_.display_modal("Error", "File acces error !", ABORT, nullptr); +} + +void FrequencySaveView::on_tick_second() { + rtcGetTime(&RTCD1, &datetime); + text_timestamp.set(to_string_dec_uint(datetime.month(), 2, '0') + "/" + to_string_dec_uint(datetime.day(), 2, '0') + " " + + to_string_dec_uint(datetime.hour(), 2, '0') + ":" + to_string_dec_uint(datetime.minute(), 2, '0')); +} + +FrequencySaveView::~FrequencySaveView() { + time::signal_tick_second -= signal_token_tick_second; + save_freqman_file(frequencies); } FrequencySaveView::FrequencySaveView( NavigationView& nav, const rf::Frequency value -) { +) : nav_ (nav), + value_ (value) +{ + error = !load_freqman_file(frequencies); + + signal_token_tick_second = time::signal_tick_second += [this]() { + this->on_tick_second(); + }; + add_children({ { &big_display, &text_save, &button_save_name, &button_save_timestamp, + &text_timestamp, &button_cancel } }); + on_tick_second(); + big_display.set(value); + button_save_name.on_select = [this, &nav](Button&) { + on_save_name(nav); + }; + button_save_timestamp.on_select = [this, &nav](Button&) { + on_save_timestamp(nav); + }; + button_cancel.on_select = [this, &nav](Button&) { + nav.pop(); + }; +} + +void FrequencyLoadView::setup_list() { + size_t n; + + menu_view.clear(); + + for (n = 0; n < frequencies.size(); n++) { + menu_view.add_item({ freqman_item_string(frequencies[n]), ui::Color::white(), [this](){ on_frequency_select(); } }); + } + + menu_view.set_parent_rect({ 0, 0, 240, 216 }); + menu_view.set_highlighted(menu_view.highlighted()); // Refresh +} + +void FrequencyLoadView::on_frequency_select() { + nav_.pop(); + if (on_changed) on_changed(frequencies[menu_view.highlighted()].value); +} + +void FrequencyLoadView::focus() { + menu_view.focus(); + + if (error) + nav_.display_modal("Error", "File acces error !", ABORT, nullptr); +} + +FrequencyLoadView::FrequencyLoadView( + NavigationView& nav +) : nav_ (nav) +{ + error = !load_freqman_file(frequencies); + + add_children({ { + &menu_view, + &button_cancel + } }); + + setup_list(); + button_cancel.on_select = [this, &nav](Button&) { nav.pop(); }; } -void FrequencyLoadView::focus() { - button_exit.focus(); +void FreqManView::on_frequency_select() { + button_edit_freq.focus(); } -FrequencyLoadView::FrequencyLoadView( - NavigationView& nav, - const rf::Frequency value -) { +void FreqManView::on_edit_freq(rf::Frequency f) { + frequencies[menu_view.highlighted()].value = f; + setup_list(); +} - add_children({ { - &button_exit - } }); +void FreqManView::on_edit_desc(NavigationView& nav) { + char desc_buffer[32] = { 0 }; - button_exit.on_select = [this, &nav](Button&) { - nav.pop(); - }; + strcpy(desc_buffer, frequencies[menu_view.highlighted()].description.c_str()); + textentry(nav, desc_buffer, 28, [this, &desc_buffer](char * buffer) { + frequencies[menu_view.highlighted()].description = buffer; + setup_list(); + }); +} + +void FreqManView::on_delete() { + frequencies.erase(frequencies.begin() + menu_view.highlighted()); + setup_list(); +} + +void FreqManView::setup_list() { + size_t n; + + menu_view.clear(); + + for (n = 0; n < frequencies.size(); n++) { + menu_view.add_item({ freqman_item_string(frequencies[n]), ui::Color::white(), [this](){ on_frequency_select(); } }); + } + + menu_view.set_parent_rect({ 0, 0, 240, 168 }); + menu_view.set_highlighted(menu_view.highlighted()); // Refresh } void FreqManView::focus() { - button_exit.focus(); + menu_view.focus(); + + if (error) + nav_.display_modal("Error", "File acces error !", ABORT, nullptr); +} + +FreqManView::~FreqManView() { + save_freqman_file(frequencies); } FreqManView::FreqManView( NavigationView& nav -) { - +) : nav_ (nav) +{ + error = !load_freqman_file(frequencies); + add_children({ { + &menu_view, + &button_edit_freq, + &button_edit_desc, + &button_del, &button_exit } }); - - size_t n = 0; - for(auto& text : text_list) { - add_child(&text); - text.set_parent_rect({ - static_cast(0), - static_cast(16 + (n * 16)), - 240, 16 - }); - const std::string label { - (char)(n + 0x30) + + setup_list(); + + button_edit_freq.on_select = [this, &nav](Button&) { + auto new_view = nav.push(frequencies[menu_view.highlighted()].value); + new_view->on_changed = [this](rf::Frequency f) { + on_edit_freq(f); }; - text.set(label); - n++; - } + }; + + button_edit_desc.on_select = [this, &nav](Button&) { + on_edit_desc(nav); + }; + + button_del.on_select = [this, &nav](Button&) { + nav.push("Confirm", "Are you sure ?", YESNO, + [this](bool choice) { + if (choice) { + on_delete(); + } + } + ); + }; button_exit.on_select = [this, &nav](Button&) { nav.pop(); diff --git a/firmware/application/ui_freqman.hpp b/firmware/application/ui_freqman.hpp index 40215031..b92c509c 100644 --- a/firmware/application/ui_freqman.hpp +++ b/firmware/application/ui_freqman.hpp @@ -25,35 +25,57 @@ #include "ui_painter.hpp" #include "ui_menu.hpp" #include "ui_navigation.hpp" +#include "ui_receiver.hpp" +#include "ui_textentry.hpp" +#include "freqman.hpp" +#include "time.hpp" namespace ui { class FrequencySaveView : public View { public: FrequencySaveView(NavigationView& nav, const rf::Frequency value); - //~FrequencySaveView(); + ~FrequencySaveView(); void focus() override; std::string title() const override { return "Save frequency"; }; private: + NavigationView& nav_; + bool error = false; + char desc_buffer[32] = { 0 }; + rtc::RTC datetime; + rf::Frequency value_; + + void on_save_name(NavigationView& nav); + void on_save_timestamp(NavigationView& nav); + void on_tick_second(); + + std::vector frequencies; + + SignalToken signal_token_tick_second; + BigFrequency big_display { { 4, 2 * 16, 28 * 8, 32 }, 0 }; Text text_save { - { 72, 124, 10 * 8, 16 }, - "Save with:", + { 88, 120, 8 * 8, 16 }, + "Save as:", }; Button button_save_name { { 72, 144, 96, 32 }, - "Name" + "Name (set)" }; Button button_save_timestamp { { 72, 184, 96, 32 }, - "Timestamp" + "Timestamp:" + }; + Text text_timestamp { + { 76, 220, 11 * 8, 16 }, + "MM/DD HH:MM", }; Button button_cancel { @@ -64,34 +86,69 @@ private: class FrequencyLoadView : public View { public: - FrequencyLoadView(NavigationView& nav, const rf::Frequency value); - //~FrequencySaveView(); + std::function on_changed; + + FrequencyLoadView(NavigationView& nav); void focus() override; std::string title() const override { return "Load frequency"; }; private: - Button button_exit { + NavigationView& nav_; + bool error = false; + + void on_frequency_select(); + void setup_list(); + + std::vector frequencies; + + MenuView menu_view; + + Button button_cancel { { 72, 264, 96, 32 }, - "Exit" + "Cancel" }; }; class FreqManView : public View { public: FreqManView(NavigationView& nav); - //~FreqManView(); + ~FreqManView(); void focus() override; std::string title() const override { return "Freq. manager"; }; private: - std::array text_list; + NavigationView& nav_; + bool error = false; + + void on_frequency_select(); + void on_edit_freq(rf::Frequency f); + void on_edit_desc(NavigationView& nav); + void on_delete(); + void setup_list(); + + std::vector frequencies; + MenuView menu_view { true }; + + Button button_edit_freq { + { 52, 194, 106, 30 }, + "Edit freq." + }; + Button button_edit_desc { + { 52, 192 + 32, 106, 30 }, + "Edit desc." + }; + Button button_del { + { 168, 192, 64, 64 }, + "Del" + }; + Button button_exit { - { 72, 264, 96, 32 }, + { 168, 264, 64, 32 }, "Exit" }; }; diff --git a/firmware/application/ui_handwrite.cpp b/firmware/application/ui_handwrite.cpp index 2ff568a5..10f92463 100644 --- a/firmware/application/ui_handwrite.cpp +++ b/firmware/application/ui_handwrite.cpp @@ -142,7 +142,7 @@ void HandWriteView::clear_zone(const Color color, const bool flash) { color ); if (flash) { - flash_timer = 4; + flash_timer = 8; } else { // Draw grid _painter->draw_rectangle( @@ -239,7 +239,7 @@ void HandWriteView::guess_letter() { } } else { // Short tap is space - txtinput[txtidx++] = ' '; + char_add(' '); clear_zone(Color::green(), true); // Green flash } update_text(); diff --git a/firmware/application/ui_handwrite.hpp b/firmware/application/ui_handwrite.hpp index 056381ad..7b66fec4 100644 --- a/firmware/application/ui_handwrite.hpp +++ b/firmware/application/ui_handwrite.hpp @@ -71,7 +71,7 @@ private: void update_text(); Text text_input { - { 8, 0, 224, 16 } + { 8, 0, 232, 16 } }; std::array num_buttons; diff --git a/firmware/application/ui_menu.cpp b/firmware/application/ui_menu.cpp index 57968b52..9054cf44 100644 --- a/firmware/application/ui_menu.cpp +++ b/firmware/application/ui_menu.cpp @@ -46,7 +46,7 @@ void MenuItemView::unhighlight() { void MenuItemView::paint(Painter& painter) { const auto r = screen_rect(); - const auto paint_style = (highlighted() && parent()->has_focus()) ? style().invert() : style(); + const auto paint_style = (highlighted() && (parent()->has_focus() || keep_highlight_)) ? style().invert() : style(); const auto font_height = paint_style.font.line_height(); @@ -75,15 +75,18 @@ void MenuItemView::paint(Painter& painter) { /* MenuView **************************************************************/ -MenuView::MenuView() { +MenuView::MenuView( + bool keep_highlight +) : keep_highlight_ { keep_highlight } +{ set_focusable(true); - add_child(&arrow_more); signal_token_tick_second = time::signal_tick_second += [this]() { this->on_tick_second(); }; - arrow_more.set_parent_rect( { 216, 320 - 16 - 24, 16, 16 } ); + add_child(&arrow_more); + arrow_more.id = 1; // Special flag arrow_more.set_focusable(false); arrow_more.set_foreground(Color::black()); } @@ -97,39 +100,47 @@ void MenuView::on_tick_second() { arrow_more.set_foreground(Color::white()); else arrow_more.set_foreground(Color::black()); - + blink_ = !blink_; arrow_more.set_dirty(); } +void MenuView::clear() { + children_.erase(children_.begin() + 1, children_.end()); +} + void MenuView::add_item(const MenuItem item) { - add_child(new MenuItemView { item }); + add_child(new MenuItemView { item, keep_highlight_ }); } void MenuView::set_parent_rect(const Rect new_parent_rect) { View::set_parent_rect(new_parent_rect); + + displayed_max_ = new_parent_rect.size.h / 24; + arrow_more.set_parent_rect( { 228, (Coord)(displayed_max_ * item_height), 8, 8 } ); + update_items(); } void MenuView::update_items() { - constexpr size_t item_height = 24; size_t i = 0; - Coord y_pos; + int32_t y_pos; - if ((children_.size() - 1) > MENU_MAX + offset_) + if ((children_.size() - 1) > displayed_max_ + offset_) { more_ = true; - else + blink_ = true; + } else more_ = false; for (auto child : children_) { - if (i) { // Skip arrow widget - y_pos = (i - 1 - offset_) * item_height; + if (!child->id) { + y_pos = (i - offset_ - 1) * item_height; child->set_parent_rect({ { 0, y_pos }, - { size().w, item_height } + { size().w, (Coord)item_height } }); - if ((y_pos < 0) || (y_pos >= (320 - 16 - 24 - 16))) + if ((y_pos < 0) || (y_pos > (Coord)(screen_rect().size.h - item_height))) child->hidden(true); else child->hidden(false); @@ -142,20 +153,24 @@ MenuItemView* MenuView::item_view(size_t index) const { /* TODO: Terrible cast! Take it as a sign I must be doing something * shamefully wrong here, right? */ - return static_cast(children_[index + 1]); // Skip arrow widget + return static_cast(children_[index + 1]); } size_t MenuView::highlighted() const { return highlighted_; } -bool MenuView::set_highlighted(const size_t new_value) { - if( new_value >= (children_.size() - 1) ) // Skip arrow widget +bool MenuView::set_highlighted(int32_t new_value) { + int32_t item_count = (int32_t)children_.size() - 1; + if (new_value < 0) return false; - if ((new_value > offset_) && ((new_value - offset_ + 1) >= MENU_MAX)) { + if (new_value >= item_count) + new_value = item_count - 1; + + if ((new_value > offset_) && ((new_value - offset_ + 1) >= displayed_max_)) { // Shift MenuView up - offset_ = new_value - MENU_MAX + 1; + offset_ = new_value - displayed_max_ + 1; update_items(); } else if (new_value < offset_) { // Shift MenuView down @@ -175,7 +190,7 @@ void MenuView::on_focus() { } void MenuView::on_blur() { - item_view(highlighted())->unhighlight(); + if (!keep_highlight_) item_view(highlighted())->unhighlight(); } bool MenuView::on_key(const KeyEvent key) { diff --git a/firmware/application/ui_menu.hpp b/firmware/application/ui_menu.hpp index cfc22adc..3355a879 100644 --- a/firmware/application/ui_menu.hpp +++ b/firmware/application/ui_menu.hpp @@ -33,8 +33,6 @@ #include #include -#define MENU_MAX 11 - namespace ui { struct MenuItem { @@ -50,8 +48,10 @@ struct MenuItem { class MenuItemView : public Widget { public: MenuItemView( - MenuItem item - ) : item(item) + MenuItem item, + bool keep_highlight + ) : item(item), + keep_highlight_ { keep_highlight } { } @@ -63,16 +63,19 @@ public: private: const MenuItem item; + bool keep_highlight_ = false; }; class MenuView : public View { public: std::function on_left; - MenuView(); + MenuView(bool keep_highlight = false); + ~MenuView(); void add_item(const MenuItem item); + void clear(); template void add_items(const std::array& items) { @@ -86,7 +89,7 @@ public: MenuItemView* item_view(size_t index) const; size_t highlighted() const; - bool set_highlighted(const size_t new_value); + bool set_highlighted(int32_t new_value); void on_focus() override; void on_blur() override; @@ -98,17 +101,21 @@ private: void update_items(); void on_tick_second(); + bool keep_highlight_ = false; + SignalToken signal_token_tick_second; Image arrow_more { - { 216, 320 - 16 - 24, 16, 16 }, + { 228, 320 - 8, 8, 8 }, &bitmap_more, Color::white(), Color::black() }; + const size_t item_height = 24; bool blink_ = false; bool more_ = false; + size_t displayed_max_; size_t highlighted_ { 0 }; size_t offset_ { 0 }; }; diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 12629b18..0a22892e 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -76,7 +76,7 @@ SystemStatusView::SystemStatusView() { &sd_card_status_view, } }); - if (portapack::persistent_memory::ui_config_textentry()) + if (!portapack::persistent_memory::ui_config_textentry()) button_textentry.set_bitmap(&bitmap_keyboard); else button_textentry.set_bitmap(&bitmap_unistroke); @@ -326,6 +326,16 @@ UtilitiesView::UtilitiesView(NavigationView& nav) { } /* SystemMenuView ********************************************************/ +void SystemMenuView::hackrf_mode(NavigationView& nav) { + nav.push("Confirm", "Switch to HackRF mode ?", YESNO, + [this](bool choice) { + if (choice) { + EventDispatcher::request_stop(); + } + } + ); +} + SystemMenuView::SystemMenuView(NavigationView& nav) { add_items<11>({ { { "Play dead", ui::Color::red(), [&nav](){ nav.push(); } }, @@ -339,7 +349,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) { //{ "Analyze", ui::Color::white(), [&nav](){ nav.push(); } }, { "Setup", ui::Color::white(), [&nav](){ nav.push(); } }, //{ "Debug", ui::Color::white(), [&nav](){ nav.push(); } }, - { "HackRF mode", ui::Color::white(), [&nav](){ nav.push(); } }, + { "HackRF mode", ui::Color::white(), [this, &nav](){ hackrf_mode(nav); } }, { "About", ui::Color::white(), [&nav](){ nav.push(); } } } }); @@ -399,28 +409,6 @@ Context& SystemView::context() const { return context_; } -/* HackRFFirmwareView ****************************************************/ - -HackRFFirmwareView::HackRFFirmwareView(NavigationView& nav) { - button_yes.on_select = [](Button&){ - EventDispatcher::request_stop(); - }; - - button_no.on_select = [&nav](Button&){ - nav.pop(); - }; - - add_children({ { - &text_title, - &text_description_1, - &text_description_2, - &text_description_3, - &text_description_4, - &button_yes, - &button_no, - } }); -} - /* ***********************************************************************/ void BMPView::focus() { @@ -490,10 +478,6 @@ PlayDeadView::PlayDeadView(NavigationView& nav) { }; } -void HackRFFirmwareView::focus() { - button_no.focus(); -} - /* NotImplementedView ****************************************************/ NotImplementedView::NotImplementedView(NavigationView& nav) { @@ -563,7 +547,7 @@ ModalMessageView::ModalMessageView( } void ModalMessageView::paint(Painter&) { - portapack::display.drawBMP({96, 64}, modal_warning_bmp, false); + portapack::display.drawBMP({ 100, 64 }, modal_warning_bmp, false); } void ModalMessageView::focus() { diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index 8ee57bc1..f942eeb4 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -223,6 +223,8 @@ public: class SystemMenuView : public MenuView { public: SystemMenuView(NavigationView& nav); +private: + void hackrf_mode(NavigationView& nav); }; class SystemView : public View { @@ -240,49 +242,6 @@ private: Context& context_; }; -class HackRFFirmwareView : public View { -public: - HackRFFirmwareView(NavigationView& nav); - - void focus() override; - -private: - Text text_title { - { 76, 4 * 16, 19 * 8, 16 }, - "HackRF Mode" - }; - - Text text_description_1 { - { 4, 7 * 16, 19 * 8, 16 }, - "Run stock HackRF firmware and" - }; - - Text text_description_2 { - { 12, 8 * 16, 19 * 8, 16 }, - "disable PortaPack until the" - }; - - Text text_description_3 { - { 4, 9 * 16, 19 * 8, 16 }, - "unit is reset or disconnected" - }; - - Text text_description_4 { - { 76, 10 * 16, 19 * 8, 16 }, - "from power?" - }; - - Button button_yes { - { 4 * 8, 13 * 16, 8 * 8, 24 }, - "Yes", - }; - - Button button_no { - { 18 * 8, 13 * 16, 8 * 8, 24 }, - "No", - }; -}; - class NotImplementedView : public View { public: NotImplementedView(NavigationView& nav); @@ -329,11 +288,12 @@ private: }; Button button_yes { - { 40, 13 * 16, 64, 24 }, + { 5 * 8, 13 * 16, 8 * 8, 48 }, "YES", }; + Button button_no { - { 152, 13 * 16, 64, 24 }, + { 17 * 8, 13 * 16, 8 * 8, 48 }, "NO", }; }; diff --git a/firmware/application/ui_receiver.cpp b/firmware/application/ui_receiver.cpp index 19dc37a0..2e8752a7 100644 --- a/firmware/application/ui_receiver.cpp +++ b/firmware/application/ui_receiver.cpp @@ -158,7 +158,10 @@ FrequencyKeypadView::FrequencyKeypadView( nav.push(this->value()); }; button_load.on_select = [this, &nav](Button&) { - nav.push(this->value()); + auto load_view = nav.push(); + load_view->on_changed = [this](rf::Frequency value) { + set_value(value); + }; }; button_close.on_select = [this, &nav](Button&) { diff --git a/firmware/application/ui_textentry.cpp b/firmware/application/ui_textentry.cpp index be649267..db3e5be1 100644 --- a/firmware/application/ui_textentry.cpp +++ b/firmware/application/ui_textentry.cpp @@ -24,16 +24,19 @@ namespace ui { -bool textentry(NavigationView& nav, char * str, size_t max_length) { +bool textentry(NavigationView& nav, char * str, const size_t max_length, const std::function on_done) { + if (portapack::persistent_memory::ui_config_textentry() == 0) { - auto an_view = nav.push(str, max_length); - an_view->on_changed = [str, max_length](char * value) { - memcpy(str, value, max_length + 1); + auto te_view = nav.push(str, max_length); + te_view->on_changed = [str, max_length, on_done](char * value) { + //memcpy(str, value, max_length + 1); + if (on_done) on_done(value); }; } else { - auto an_view = nav.push(str, max_length); - an_view->on_changed = [str, max_length](char * value) { - memcpy(str, value, max_length + 1); + auto te_view = nav.push(str, max_length); + te_view->on_changed = [str, max_length, on_done](char * value) { + //memcpy(str, value, max_length + 1); + if (on_done) on_done(value); }; } diff --git a/firmware/application/ui_textentry.hpp b/firmware/application/ui_textentry.hpp index 4b799894..39272912 100644 --- a/firmware/application/ui_textentry.hpp +++ b/firmware/application/ui_textentry.hpp @@ -20,14 +20,21 @@ * Boston, MA 02110-1301, USA. */ +#ifndef __UI_TEXTENTRY_H__ +#define __UI_TEXTENTRY_H__ + #include "ui.hpp" #include "ui_navigation.hpp" #include "ui_handwrite.hpp" #include "ui_alphanum.hpp" #include "portapack_persistent_memory.hpp" +// TODO: Make class + namespace ui { -bool textentry(NavigationView& nav, char * str, size_t max_length); +bool textentry(NavigationView& nav, char * str, size_t max_length, const std::function on_done = nullptr); } /* namespace ui */ + +#endif/*__UI_TEXTENTRY_H__*/ diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 14dbba3e..c05dba72 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -316,7 +316,7 @@ Text::Text( Rect parent_rect, std::string text ) : Widget { parent_rect }, - text { text } + text_ { text } { } @@ -327,10 +327,14 @@ Text::Text( } void Text::set(const std::string value) { - text = value; + text_ = value; set_dirty(); } +std::string Text::text() const { + return text_; +} + void Text::paint(Painter& painter) { const auto rect = screen_rect(); const auto s = style(); @@ -340,7 +344,7 @@ void Text::paint(Painter& painter) { painter.draw_string( rect.pos, s, - text + text_ ); } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 6568e1b9..54582d4f 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -104,6 +104,7 @@ public: void set_style(const Style* new_style); const Style& style() const; + uint16_t id = 0; // State management methods. void set_dirty(); @@ -115,8 +116,6 @@ public: bool highlighted() const; void set_highlighted(const bool value); - - uint16_t id = 0; protected: void dirty_overlapping_children_in_rect(const Rect& child_rect); @@ -192,18 +191,19 @@ private: class Text : public Widget { public: Text( - ) : text { "" } { + ) : text_ { "" } { } Text(Rect parent_rect, std::string text); Text(Rect parent_rect); void set(const std::string value); + std::string text() const; void paint(Painter& painter) override; private: - std::string text; + std::string text_; }; class BigFrequency : public Widget { @@ -265,7 +265,6 @@ public: } void set_text(const std::string value); - // std::string text() const; bool set_value(const bool value); bool value() const; diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index 68f26417..99d125f8 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ