mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-01-25 22:16:14 -05:00
Merge pull request #950 from kallanreed/fileman_ux2
Fileman fit and finish
This commit is contained in:
commit
0742fc169d
@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
/* TODO:
|
/* TODO:
|
||||||
* - Paging menu items
|
* - Paging menu items
|
||||||
* - UI with empty SD card
|
|
||||||
* - Copy/Move
|
* - Copy/Move
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -101,6 +100,9 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
|||||||
|
|
||||||
// Returns the partner file path or an empty path if no partner is found.
|
// Returns the partner file path or an empty path if no partner is found.
|
||||||
fs::path get_partner_file(fs::path path) {
|
fs::path get_partner_file(fs::path path) {
|
||||||
|
if (fs::is_directory(path))
|
||||||
|
return { };
|
||||||
|
|
||||||
const fs::path txt_path{ u".TXT" };
|
const fs::path txt_path{ u".TXT" };
|
||||||
const fs::path c16_path{ u".C16" };
|
const fs::path c16_path{ u".C16" };
|
||||||
auto ext = path.extension();
|
auto ext = path.extension();
|
||||||
@ -113,12 +115,13 @@ fs::path get_partner_file(fs::path path) {
|
|||||||
return { };
|
return { };
|
||||||
|
|
||||||
path.replace_extension(ext);
|
path.replace_extension(ext);
|
||||||
return file_exists(path) ? path : fs::path{ };
|
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{ };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modal prompt to update the partner file if it exists.
|
// Modal prompt to update the partner file if it exists.
|
||||||
// Runs continuation on_partner_action to update the partner file.
|
// Runs continuation on_partner_action to update the partner file.
|
||||||
// Returns true is a partner is found, otherwise false.
|
// Returns true is a partner is found, otherwise false.
|
||||||
|
// Path must be the full path to the file.
|
||||||
bool partner_file_prompt(
|
bool partner_file_prompt(
|
||||||
NavigationView& nav,
|
NavigationView& nav,
|
||||||
const fs::path& path,
|
const fs::path& path,
|
||||||
@ -147,6 +150,8 @@ bool partner_file_prompt(
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
/* FileManBaseView ***********************************************************/
|
||||||
|
|
||||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||||
current_path = dir_path;
|
current_path = dir_path;
|
||||||
entry_list.clear();
|
entry_list.clear();
|
||||||
@ -180,6 +185,7 @@ fs::path FileManBaseView::get_selected_full_path() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||||
|
// TODO: return reference to an "empty" entry on OOB?
|
||||||
return entry_list[menu_view.highlighted_index()];
|
return entry_list[menu_view.highlighted_index()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,8 +218,7 @@ FileManBaseView::FileManBaseView(
|
|||||||
text_current.set("EMPTY SD CARD!");
|
text_current.set("EMPTY SD CARD!");
|
||||||
} else {
|
} else {
|
||||||
menu_view.on_left = [this]() {
|
menu_view.on_left = [this]() {
|
||||||
current_path = current_path.parent_path();
|
pop_dir();
|
||||||
reload_current();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,10 +231,32 @@ 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() {
|
void FileManBaseView::refresh_list() {
|
||||||
if (on_refresh_widgets)
|
if (on_refresh_widgets)
|
||||||
on_refresh_widgets(false);
|
on_refresh_widgets(false);
|
||||||
|
|
||||||
|
auto prev_highlight = menu_view.highlighted_index();
|
||||||
menu_view.clear();
|
menu_view.clear();
|
||||||
|
|
||||||
for (const auto& entry : entry_list) {
|
for (const auto& entry : entry_list) {
|
||||||
@ -262,7 +289,7 @@ void FileManBaseView::refresh_list() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu_view.set_highlighted(0); // Refresh
|
menu_view.set_highlighted(prev_highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileManBaseView::reload_current() {
|
void FileManBaseView::reload_current() {
|
||||||
@ -305,6 +332,8 @@ FileSaveView::FileSaveView(
|
|||||||
};
|
};
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
/* FileLoadView **************************************************************/
|
||||||
|
|
||||||
void FileLoadView::refresh_widgets(const bool) {
|
void FileLoadView::refresh_widgets(const bool) {
|
||||||
set_dirty();
|
set_dirty();
|
||||||
}
|
}
|
||||||
@ -329,35 +358,32 @@ FileLoadView::FileLoadView(
|
|||||||
|
|
||||||
on_select_entry = [this](KeyEvent) {
|
on_select_entry = [this](KeyEvent) {
|
||||||
if (get_selected_entry().is_directory) {
|
if (get_selected_entry().is_directory) {
|
||||||
current_path = get_selected_full_path();
|
push_dir(get_selected_entry().path);
|
||||||
reload_current();
|
|
||||||
} else {
|
} else {
|
||||||
nav_.pop();
|
|
||||||
if (on_changed)
|
if (on_changed)
|
||||||
on_changed(get_selected_full_path());
|
on_changed(get_selected_full_path());
|
||||||
|
nav_.pop();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FileManagerView ***********************************************************/
|
||||||
|
|
||||||
void FileManagerView::on_rename() {
|
void FileManagerView::on_rename() {
|
||||||
auto& entry = get_selected_entry();
|
auto& entry = get_selected_entry();
|
||||||
|
|
||||||
// Don't rename ".."
|
|
||||||
if (entry.path == parent_dir_path)
|
|
||||||
return;
|
|
||||||
|
|
||||||
name_buffer = entry.path.filename().string();
|
name_buffer = entry.path.filename().string();
|
||||||
|
|
||||||
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
||||||
if (auto pos = name_buffer.find_last_of("."); pos != name_buffer.npos)
|
|
||||||
|
if (auto pos = name_buffer.find_last_of(".");
|
||||||
|
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, &entry](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_, entry.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());
|
||||||
@ -373,20 +399,14 @@ void FileManagerView::on_rename() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileManagerView::on_delete() {
|
void FileManagerView::on_delete() {
|
||||||
auto& entry = get_selected_entry();
|
auto name = get_selected_entry().path.filename().string();
|
||||||
|
|
||||||
// Don't delete ".."
|
|
||||||
if (entry.path == parent_dir_path)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto name = 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, &entry](bool choice) {
|
[this](bool choice) {
|
||||||
if (choice) {
|
if (choice) {
|
||||||
delete_file(get_selected_full_path());
|
delete_file(get_selected_full_path());
|
||||||
|
|
||||||
auto has_partner = partner_file_prompt(
|
auto has_partner = partner_file_prompt(
|
||||||
nav_, entry.path, "Delete",
|
nav_, get_selected_full_path(), "Delete",
|
||||||
[this](const fs::path& partner, bool should_delete) {
|
[this](const fs::path& partner, bool should_delete) {
|
||||||
if (should_delete)
|
if (should_delete)
|
||||||
delete_file(current_path / partner);
|
delete_file(current_path / partner);
|
||||||
@ -409,6 +429,11 @@ void FileManagerView::on_new_dir() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileManagerView::selected_is_valid() const {
|
||||||
|
return !entry_list.empty() &&
|
||||||
|
get_selected_entry().path != parent_dir_path;
|
||||||
|
}
|
||||||
|
|
||||||
void FileManagerView::refresh_widgets(const bool v) {
|
void FileManagerView::refresh_widgets(const bool v) {
|
||||||
button_rename.hidden(v);
|
button_rename.hidden(v);
|
||||||
button_delete.hidden(v);
|
button_delete.hidden(v);
|
||||||
@ -438,26 +463,31 @@ FileManagerView::FileManagerView(
|
|||||||
});
|
});
|
||||||
|
|
||||||
menu_view.on_highlight = [this]() {
|
menu_view.on_highlight = [this]() {
|
||||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_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();
|
refresh_list();
|
||||||
|
|
||||||
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) {
|
||||||
load_directory_contents(get_selected_full_path());
|
push_dir(get_selected_entry().path);
|
||||||
refresh_list();
|
|
||||||
} else {
|
} else {
|
||||||
button_rename.focus();
|
button_rename.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
button_rename.on_select = [this](Button&) {
|
button_rename.on_select = [this](Button&) {
|
||||||
on_rename();
|
if (selected_is_valid())
|
||||||
|
on_rename();
|
||||||
};
|
};
|
||||||
|
|
||||||
button_delete.on_select = [this](Button&) {
|
button_delete.on_select = [this](Button&) {
|
||||||
on_delete();
|
if (selected_is_valid())
|
||||||
|
on_delete();
|
||||||
};
|
};
|
||||||
|
|
||||||
button_new_dir.on_select = [this](Button&) {
|
button_new_dir.on_select = [this](Button&) {
|
||||||
|
@ -69,6 +69,8 @@ protected:
|
|||||||
std::filesystem::path get_selected_full_path() const;
|
std::filesystem::path get_selected_full_path() const;
|
||||||
const fileman_entry& get_selected_entry() const;
|
const fileman_entry& get_selected_entry() const;
|
||||||
|
|
||||||
|
void push_dir(const std::filesystem::path& path);
|
||||||
|
void pop_dir();
|
||||||
void refresh_list();
|
void refresh_list();
|
||||||
void reload_current();
|
void reload_current();
|
||||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||||
@ -85,6 +87,7 @@ protected:
|
|||||||
std::filesystem::path extension_filter { u"" };
|
std::filesystem::path extension_filter { u"" };
|
||||||
|
|
||||||
std::vector<fileman_entry> entry_list { };
|
std::vector<fileman_entry> entry_list { };
|
||||||
|
std::vector<uint32_t> saved_index_stack { };
|
||||||
|
|
||||||
Labels labels {
|
Labels labels {
|
||||||
{ { 0, 0 }, "Path:", Color::light_grey() }
|
{ { 0, 0 }, "Path:", Color::light_grey() }
|
||||||
@ -152,6 +155,9 @@ private:
|
|||||||
void on_rename();
|
void on_rename();
|
||||||
void on_delete();
|
void on_delete();
|
||||||
void on_new_dir();
|
void on_new_dir();
|
||||||
|
|
||||||
|
// True if the selected entry is a real file item.
|
||||||
|
bool selected_is_valid() const;
|
||||||
|
|
||||||
Labels labels {
|
Labels labels {
|
||||||
{ { 0, 26 * 8 }, "Created ", Color::light_grey() }
|
{ { 0, 26 * 8 }, "Created ", Color::light_grey() }
|
||||||
|
@ -197,13 +197,6 @@ std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::
|
|||||||
return directory_list;
|
return directory_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool file_exists(const std::filesystem::path& file_path) {
|
|
||||||
FILINFO filinfo;
|
|
||||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
|
||||||
|
|
||||||
return fr == FR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t delete_file(const std::filesystem::path& file_path) {
|
uint32_t delete_file(const std::filesystem::path& file_path) {
|
||||||
return f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()));
|
return f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()));
|
||||||
}
|
}
|
||||||
@ -316,6 +309,10 @@ bool operator==(const path& lhs, const path& rhs) {
|
|||||||
return lhs.native() == rhs.native();
|
return lhs.native() == rhs.native();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator!=(const path& lhs, const path& rhs) {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
bool operator<(const path& lhs, const path& rhs) {
|
bool operator<(const path& lhs, const path& rhs) {
|
||||||
return lhs.native() < rhs.native();
|
return lhs.native() < rhs.native();
|
||||||
}
|
}
|
||||||
@ -343,7 +340,7 @@ directory_iterator::directory_iterator(
|
|||||||
{
|
{
|
||||||
impl = std::make_shared<Impl>();
|
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()));
|
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();
|
impl.reset();
|
||||||
// TODO: Throw exception if/when I enable exceptions...
|
// TODO: Throw exception if/when I enable exceptions...
|
||||||
}
|
}
|
||||||
@ -365,6 +362,20 @@ bool is_regular_file(const file_status s) {
|
|||||||
return !(s & AM_DIR);
|
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) {
|
space_info space(const path& p) {
|
||||||
DWORD free_clusters { 0 };
|
DWORD free_clusters { 0 };
|
||||||
FATFS* fs;
|
FATFS* fs;
|
||||||
|
@ -166,6 +166,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
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);
|
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);
|
||||||
@ -238,6 +239,8 @@ inline bool operator!=(const directory_iterator& lhs, const directory_iterator&
|
|||||||
|
|
||||||
bool is_directory(const file_status s);
|
bool is_directory(const file_status s);
|
||||||
bool is_regular_file(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);
|
space_info space(const path& p);
|
||||||
|
|
||||||
@ -249,7 +252,6 @@ struct FATTimestamp {
|
|||||||
uint16_t FAT_time;
|
uint16_t FAT_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool file_exists(const std::filesystem::path& file_path);
|
|
||||||
uint32_t delete_file(const std::filesystem::path& file_path);
|
uint32_t delete_file(const std::filesystem::path& file_path);
|
||||||
uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
|
uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
|
||||||
FATTimestamp file_created_date(const std::filesystem::path& file_path);
|
FATTimestamp file_created_date(const std::filesystem::path& file_path);
|
||||||
|
@ -163,6 +163,8 @@ void MenuView::clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menu_items.clear();
|
menu_items.clear();
|
||||||
|
highlighted_item = 0;
|
||||||
|
offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuView::add_item(MenuItem new_item) {
|
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) {
|
bool MenuView::set_highlighted(int32_t new_value) {
|
||||||
int32_t item_count = (int32_t)menu_items.size();
|
int32_t item_count = (int32_t)menu_items.size();
|
||||||
|
|
||||||
if (new_value < 0)
|
if (new_value < 0 || item_count == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (new_value >= item_count)
|
if (new_value >= item_count)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user