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 <kallanreed@outlook.com>
This commit is contained in:
Kyle Reed 2023-06-03 19:26:39 -07:00 committed by GitHub
parent f66f438487
commit e5728b3501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 243 additions and 61 deletions

View File

@ -260,6 +260,7 @@ set(CPPSRC
apps/ui_siggen.cpp apps/ui_siggen.cpp
apps/ui_sonde.cpp apps/ui_sonde.cpp
apps/ui_sstvtx.cpp apps/ui_sstvtx.cpp
apps/ui_ss_viewer.cpp
# apps/ui_test.cpp # apps/ui_test.cpp
apps/ui_text_editor.cpp apps/ui_text_editor.cpp
apps/ui_tone_search.cpp apps/ui_tone_search.cpp

View File

@ -26,6 +26,7 @@
#include <algorithm> #include <algorithm>
#include "ui_fileman.hpp" #include "ui_fileman.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp" #include "ui_text_editor.hpp"
#include "string_format.hpp" #include "string_format.hpp"
#include "portapack.hpp" #include "portapack.hpp"
@ -67,7 +68,8 @@ bool iequal(
// Inserts the entry into the entry list sorted directories first then by file name. // 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) { void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry, auto it = std::lower_bound(
std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) { [](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory) if (lhs.is_directory && !rhs.is_directory)
return true; return true;
@ -398,6 +400,18 @@ void FileSaveView::refresh_widgets() {
*/ */
/* FileManagerView ***********************************************************/ /* 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() { void FileManagerView::on_rename() {
auto& entry = get_selected_entry(); auto& entry = get_selected_entry();
name_buffer = entry.path.filename().string(); name_buffer = entry.path.filename().string();
@ -407,12 +421,14 @@ void FileManagerView::on_rename() {
pos != name_buffer.npos && !entry.is_directory) pos != name_buffer.npos && !entry.is_directory)
cursor_pos = pos; cursor_pos = pos;
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length, text_prompt(
nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) { [this](std::string& renamed) {
auto renamed_path = fs::path{renamed}; auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path); rename_file(get_selected_full_path(), current_path / renamed_path);
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename", auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable { [this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) { if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension()); auto new_name = renamed_path.replace_extension(partner.extension());
@ -428,7 +444,8 @@ void FileManagerView::on_rename() {
void FileManagerView::on_delete() { void FileManagerView::on_delete() {
auto name = get_selected_entry().path.filename().string(); auto name = get_selected_entry().path.filename().string();
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO, nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) { [this](bool choice) {
if (choice) { if (choice) {
delete_file(get_selected_full_path()); delete_file(get_selected_full_path());
@ -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() { void FileManagerView::on_paste() {
// TODO: handle partner file. Need to fix nav stack first. // TODO: handle partner file. Need to fix nav stack first.
auto new_name = get_unique_filename(current_path, clipboard_path.filename()); auto new_name = get_unique_filename(current_path, clipboard_path.filename());
@ -483,23 +492,37 @@ void FileManagerView::on_paste() {
reload_current(); 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<TextEditorView>(path);
return true;
} else if (iequal(u".PNG", ext)) {
nav_.push<ScreenshotViewer>(path);
return true;
}
return false;
}
bool FileManagerView::selected_is_valid() const { bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() && return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path; 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( FileManagerView::FileManagerView(
NavigationView& nav) NavigationView& nav)
: FileManBaseView(nav, "") { : FileManBaseView(nav, "") {
@ -537,6 +560,8 @@ FileManagerView::FileManagerView(
on_select_entry = [this](KeyEvent key) { on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) { if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path); push_dir(get_selected_entry().path);
} else if (key == KeyEvent::Select && handle_file_open()) {
return;
} else { } else {
button_rename.focus(); button_rename.focus();
} }

View File

@ -213,6 +213,8 @@ class FileManagerView : public FileManBaseView {
void on_new_dir(); void on_new_dir();
void on_new_file(); void on_new_file();
bool handle_file_open();
// True if the selected entry is a real file item. // True if the selected entry is a real file item.
bool selected_is_valid() const; bool selected_is_valid() const;

View File

@ -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<Color, pixel_width> 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

View File

@ -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__

View File

@ -59,7 +59,7 @@ class TextViewer : public Widget {
std::function<void()> on_cursor_moved{}; std::function<void()> on_cursor_moved{};
void paint(Painter& painter) override; void paint(Painter& painter) override;
bool on_key(KeyEvent delta) override; bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override; bool on_encoder(EncoderEvent delta) override;
void redraw(bool redraw_text = false); void redraw(bool redraw_text = false);

View File

@ -85,9 +85,13 @@ File::Result<File::Size> File::write(const void* data, Size bytes_to_write) {
} }
} }
File::Offset File::tell() const {
return f_tell(&f);
}
File::Result<File::Offset> File::seek(Offset new_position) { File::Result<File::Offset> File::seek(Offset new_position) {
/* NOTE: Returns *old* position, not 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); const auto result = f_lseek(&f, new_position);
if (result != FR_OK) { if (result != FR_OK) {
return {static_cast<Error>(result)}; return {static_cast<Error>(result)};

View File

@ -371,6 +371,7 @@ class File {
Result<Size> read(void* data, const Size bytes_to_read); Result<Size> read(void* data, const Size bytes_to_read);
Result<Size> write(const void* data, Size bytes_to_write); Result<Size> write(const void* data, Size bytes_to_write);
Offset tell() const;
Result<Offset> seek(uint64_t Offset); Result<Offset> seek(uint64_t Offset);
Result<Offset> truncate(); Result<Offset> truncate();
// Timestamp created_date() const; // Timestamp created_date() const;