From e5728b35010295a4aa80e022d6bfe7250c10c7fd Mon Sep 17 00:00:00 2001 From: Kyle Reed Date: Sat, 3 Jun 2023 19:26:39 -0700 Subject: [PATCH] Fileman default open and Screenshot viewer (#1102) * WIP expensive way * Add default file handling to fileman * Remove screenshot_reader and tests * Read data in chunks * Format * Fix error text position * PR feedback --------- Co-authored-by: kallanreed --- firmware/application/CMakeLists.txt | 1 + firmware/application/apps/ui_fileman.cpp | 143 +++++++++++-------- firmware/application/apps/ui_fileman.hpp | 2 + firmware/application/apps/ui_ss_viewer.cpp | 103 +++++++++++++ firmware/application/apps/ui_ss_viewer.hpp | 46 ++++++ firmware/application/apps/ui_text_editor.hpp | 2 +- firmware/application/file.cpp | 6 +- firmware/application/file.hpp | 1 + 8 files changed, 243 insertions(+), 61 deletions(-) create mode 100644 firmware/application/apps/ui_ss_viewer.cpp create mode 100644 firmware/application/apps/ui_ss_viewer.hpp diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index ecfc9e3c..23400164 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -260,6 +260,7 @@ set(CPPSRC apps/ui_siggen.cpp apps/ui_sonde.cpp apps/ui_sstvtx.cpp + apps/ui_ss_viewer.cpp # apps/ui_test.cpp apps/ui_text_editor.cpp apps/ui_tone_search.cpp diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp index 7e319ad8..c9c990be 100644 --- a/firmware/application/apps/ui_fileman.cpp +++ b/firmware/application/apps/ui_fileman.cpp @@ -26,6 +26,7 @@ #include #include "ui_fileman.hpp" +#include "ui_ss_viewer.hpp" #include "ui_text_editor.hpp" #include "string_format.hpp" #include "portapack.hpp" @@ -67,15 +68,16 @@ bool iequal( // Inserts the entry into the entry list sorted directories first then by file name. void insert_sorted(std::vector& 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; - }); + 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)); } @@ -398,6 +400,18 @@ void FileSaveView::refresh_widgets() { */ /* FileManagerView ***********************************************************/ +void FileManagerView::refresh_widgets(const bool v) { + button_rename.hidden(v); + button_delete.hidden(v); + button_cut.hidden(v); + button_copy.hidden(v); + button_paste.hidden(v); + button_new_dir.hidden(v); + button_new_file.hidden(v); + + set_dirty(); +} + void FileManagerView::on_rename() { auto& entry = get_selected_entry(); name_buffer = entry.path.filename().string(); @@ -407,44 +421,47 @@ void FileManagerView::on_rename() { pos != name_buffer.npos && !entry.is_directory) cursor_pos = pos; - 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); + 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); - 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(partner, current_path / new_name); - } - reload_current(); - }); - - if (!has_partner) - reload_current(); + 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(partner, current_path / new_name); + } + reload_current(); }); + + if (!has_partner) + reload_current(); + }); } void FileManagerView::on_delete() { auto name = get_selected_entry().path.filename().string(); - nav_.push("Delete", "Delete " + name + "\nAre you sure?", YESNO, - [this](bool choice) { - if (choice) { - delete_file(get_selected_full_path()); + nav_.push( + "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(partner); - reload_current(); - }); + 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(partner); + reload_current(); + }); - if (!has_partner) - reload_current(); - } - }); + if (!has_partner) + reload_current(); + } + }); } void FileManagerView::on_new_dir() { @@ -455,14 +472,6 @@ void FileManagerView::on_new_dir() { }); } -void FileManagerView::on_new_file() { - name_buffer = ""; - text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) { - make_new_file(current_path / file_name); - reload_current(); - }); -} - void FileManagerView::on_paste() { // TODO: handle partner file. Need to fix nav stack first. auto new_name = get_unique_filename(current_path, clipboard_path.filename()); @@ -483,23 +492,37 @@ void FileManagerView::on_paste() { reload_current(); } +void FileManagerView::on_new_file() { + name_buffer = ""; + text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) { + make_new_file(current_path / file_name); + reload_current(); + }); +} + +bool FileManagerView::handle_file_open() { + if (!selected_is_valid()) + return false; + + auto path = get_selected_full_path(); + auto ext = path.extension(); + + if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) { + nav_.push(path); + return true; + } else if (iequal(u".PNG", ext)) { + nav_.push(path); + return true; + } + + return false; +} + 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_delete.hidden(v); - button_cut.hidden(v); - button_copy.hidden(v); - button_paste.hidden(v); - button_new_dir.hidden(v); - button_new_file.hidden(v); - - set_dirty(); -} - FileManagerView::FileManagerView( NavigationView& nav) : FileManBaseView(nav, "") { @@ -537,6 +560,8 @@ FileManagerView::FileManagerView( on_select_entry = [this](KeyEvent key) { if (key == KeyEvent::Select && get_selected_entry().is_directory) { push_dir(get_selected_entry().path); + } else if (key == KeyEvent::Select && handle_file_open()) { + return; } else { button_rename.focus(); } diff --git a/firmware/application/apps/ui_fileman.hpp b/firmware/application/apps/ui_fileman.hpp index 2fc76209..13bd43b0 100644 --- a/firmware/application/apps/ui_fileman.hpp +++ b/firmware/application/apps/ui_fileman.hpp @@ -213,6 +213,8 @@ class FileManagerView : public FileManBaseView { void on_new_dir(); void on_new_file(); + bool handle_file_open(); + // True if the selected entry is a real file item. bool selected_is_valid() const; diff --git a/firmware/application/apps/ui_ss_viewer.cpp b/firmware/application/apps/ui_ss_viewer.cpp new file mode 100644 index 00000000..2e48e603 --- /dev/null +++ b/firmware/application/apps/ui_ss_viewer.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 Kyle Reed + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_ss_viewer.hpp" + +using namespace portapack; +namespace fs = std::filesystem; + +namespace ui { + +ScreenshotViewer::ScreenshotViewer( + NavigationView& nav, + const std::filesystem::path& path) + : nav_{nav}, + path_{path} { + set_focusable(true); +} + +bool ScreenshotViewer::on_key(KeyEvent) { + nav_.pop(); + return true; +} + +void ScreenshotViewer::paint(Painter& painter) { + constexpr size_t pixel_width = 240; + constexpr size_t pixel_height = 320; + Style style_default{ + .font = ui::font::fixed_8x16, + .background = ui::Color::black(), + .foreground = ui::Color::white()}; + File file{}; + + painter.fill_rectangle({0, 0, pixel_width, pixel_height}, Color::black()); + + auto show_invalid = [&]() { + painter.draw_string({10, 160}, style_default, "Not a valid screenshot."); + }; + + auto error = file.open(path_); + if (error) { + painter.draw_string({10, 160}, style_default, error->what()); + return; + } + + // Screenshots from PNGWriter are all this size. + if (file.size() != 232383) { + show_invalid(); + return; + } + + constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width. + constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk; + uint8_t buffer[buffer_size]; + std::array pixel_data; + + // Seek past all the headers. + file.seek(43); + + for (auto line = 0u; line < pixel_height; ++line) { + // Seek past the per-line header. + file.seek(file.tell() + 6); + + // Per comment in PNGWriter, read in chunks of 80. + // NB: Reading in one large chunk caused corruption so there's + // likely a bug lurking in the SD Card/FatFs layer. + for (auto offset = 0u; offset < pixel_width; offset += read_chunk) { + auto read = file.read(buffer, buffer_size); + + if (!read || *read != buffer_size) { + show_invalid(); + return; + } + + auto c8 = (ColorRGB888*)buffer; + for (auto i = 0u; i < read_chunk; ++i) { + pixel_data[i + offset] = Color(c8->r, c8->g, c8->b); + ++c8; + } + } + + display.draw_pixels({0, (int)line, pixel_width, 1}, pixel_data); + } +} + +} // namespace ui \ No newline at end of file diff --git a/firmware/application/apps/ui_ss_viewer.hpp b/firmware/application/apps/ui_ss_viewer.hpp new file mode 100644 index 00000000..dad532c1 --- /dev/null +++ b/firmware/application/apps/ui_ss_viewer.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 Kyle Reed + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __UI_SS_VIEWER_H__ +#define __UI_SS_VIEWER_H__ + +#include "ui.hpp" +#include "ui_navigation.hpp" +#include "ui_painter.hpp" +#include "ui_widget.hpp" +#include "file.hpp" + +namespace ui { + +class ScreenshotViewer : public View { + public: + ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path); + bool on_key(KeyEvent key) override; + void paint(Painter& painter) override; + + private: + NavigationView& nav_; + std::filesystem::path path_{}; +}; + +} // namespace ui + +#endif // __UI_SS_VIEWER_H__ diff --git a/firmware/application/apps/ui_text_editor.hpp b/firmware/application/apps/ui_text_editor.hpp index 2314af05..680e989e 100644 --- a/firmware/application/apps/ui_text_editor.hpp +++ b/firmware/application/apps/ui_text_editor.hpp @@ -59,7 +59,7 @@ class TextViewer : public Widget { std::function on_cursor_moved{}; void paint(Painter& painter) override; - bool on_key(KeyEvent delta) override; + bool on_key(KeyEvent key) override; bool on_encoder(EncoderEvent delta) override; void redraw(bool redraw_text = false); diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index ea7bdb33..2982e1de 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -85,9 +85,13 @@ File::Result File::write(const void* data, Size bytes_to_write) { } } +File::Offset File::tell() const { + return f_tell(&f); +} + File::Result File::seek(Offset new_position) { /* NOTE: Returns *old* position, not new position */ - const auto old_position = f_tell(&f); + const auto old_position = tell(); const auto result = f_lseek(&f, new_position); if (result != FR_OK) { return {static_cast(result)}; diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index a93d5492..67a70cb8 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -371,6 +371,7 @@ class File { Result read(void* data, const Size bytes_to_read); Result write(const void* data, Size bytes_to_write); + Offset tell() const; Result seek(uint64_t Offset); Result truncate(); // Timestamp created_date() const;