Replace Replay with Playlist (#1202)

* Replace Replay with Playlist
* Suggestions from test-drive. Launch from Fileman
This commit is contained in:
Kyle Reed 2023-06-28 10:02:06 -07:00 committed by GitHub
parent 3e584a9652
commit 4e985420d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 100 additions and 53 deletions

View File

@ -287,7 +287,7 @@ set(CPPSRC
apps/ert_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
apps/replay_app.cpp
# apps/replay_app.cpp
apps/ui_playlist.cpp
apps/gps_sim_app.cpp
apps/soundboard_app.cpp

View File

@ -26,6 +26,7 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_playlist.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "string_format.hpp"
@ -35,6 +36,13 @@
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
static const fs::path txt_ext{u".TXT"};
static const fs::path ppl_ext{u".PPL"};
static const fs::path c16_ext{u".C16"};
static const fs::path png_ext{u".PNG"};
} // namespace ui
namespace {
using namespace ui;
@ -47,25 +55,6 @@ std::string truncate(const fs::path& path, size_t max_length) {
return ::truncate(path.string(), max_length);
}
// Case insensitive path equality on underlying "native" string.
bool iequal(
const fs::path& lhs,
const fs::path& rhs) {
const auto& lhs_str = lhs.native();
const auto& rhs_str = rhs.native();
// NB: Not correct for Unicode/locales.
if (lhs_str.length() == rhs_str.length()) {
for (size_t i = 0; i < lhs_str.length(); ++i)
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
return false;
return true;
}
return false;
}
// 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) {
auto it = std::lower_bound(
@ -86,15 +75,12 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
fs::path get_partner_file(fs::path path) {
if (fs::is_directory(path))
return {};
const fs::path txt_path{u".TXT"};
const fs::path c16_path{u".C16"};
auto ext = path.extension();
if (iequal(ext, txt_path))
ext = c16_path;
else if (iequal(ext, c16_path))
ext = txt_path;
if (path_iequal(ext, txt_ext))
ext = c16_ext;
else if (path_iequal(ext, c16_ext))
ext = txt_ext;
else
return {};
@ -163,7 +149,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
continue;
if (fs::is_regular_file(entry.status())) {
if (!filtering || iequal(entry.path().extension(), extension_filter))
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path(), 0, true});
@ -301,7 +287,7 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
size_t index = 0;
for (; index < file_types.size() - 1; ++index)
if (iequal(ext, file_types[index].extension))
if (path_iequal(ext, file_types[index].extension))
return file_types[index];
// Default to last entry in the list.
@ -507,10 +493,14 @@ bool FileManagerView::handle_file_open() {
auto path = get_selected_full_path();
auto ext = path.extension();
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
if (path_iequal(txt_ext, ext)) {
nav_.push<TextEditorView>(path);
return true;
} else if (iequal(u".PNG", ext)) {
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
// TODO: Enough memory to push?
nav_.push<PlaylistView>(path);
return true;
} else if (path_iequal(png_ext, ext)) {
nav_.push<ScreenshotViewer>(path);
return true;
}

View File

@ -42,10 +42,15 @@ namespace fs = std::filesystem;
/* TODO
* - Should frequency overrides be saved in the playlist?
* - Start playing from current track (vs start over)?
*/
namespace ui {
// TODO: consolidate extesions into a shared header?
static const fs::path c16_ext = u".C16";
static const fs::path ppl_ext = u".PPL";
void PlaylistView::load_file(const fs::path& playlist_path) {
File playlist_file;
auto error = playlist_file.open(playlist_path.string());
@ -103,7 +108,6 @@ void PlaylistView::on_file_changed(const fs::path& new_file_path) {
load_file(playlist_path_);
update_ui();
button_play.focus();
}
void PlaylistView::open_file(bool prompt_save) {
@ -119,8 +123,10 @@ void PlaylistView::open_file(bool prompt_save) {
}
auto open_view = nav_.push<FileLoadView>(".PPL");
open_view->push_dir(u"PLAYLIST");
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
button_play.focus();
};
}
@ -156,10 +162,8 @@ void PlaylistView::save_file(bool show_dialogs) {
}
void PlaylistView::add_entry(fs::path&& path) {
if (playlist_path_.empty()) {
if (playlist_path_.empty())
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
button_play.focus();
}
auto entry = load_entry(std::move(path));
if (entry) {
@ -252,7 +256,7 @@ void PlaylistView::send_current_track() {
return;
}
// ReplayThread starts immediately on contruction so
// ReplayThread starts immediately on construction so
// these need to be set before creating the ReplayThread.
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
transmitter_model.set_sampling_rate(current()->metadata.sample_rate * 8);
@ -391,25 +395,40 @@ PlaylistView::PlaylistView(
};
button_add.on_select = [this, &nav]() {
if (is_active())
return;
auto open_view = nav_.push<FileLoadView>(".C16");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
// Set focus to play only on the first "add".
auto set_focus = playlist_path_.empty();
add_entry(std::move(path));
if (set_focus)
button_play.focus();
};
};
button_delete.on_select = [this, &nav]() {
if (is_active())
return;
delete_entry();
};
button_open.on_select = [this, &nav]() {
if (is_active())
stop();
open_file();
};
button_save.on_select = [this]() {
if (is_active())
stop();
save_file();
};
button_prev.on_select = [this]() {
if (is_active())
return;
--current_index_;
if (at_end())
current_index_ = playlist_db_.size() - 1;
@ -417,6 +436,8 @@ PlaylistView::PlaylistView(
};
button_next.on_select = [this]() {
if (is_active())
return;
++current_index_;
if (at_end())
current_index_ = 0;
@ -426,6 +447,17 @@ PlaylistView::PlaylistView(
update_ui();
}
PlaylistView::PlaylistView(
NavigationView& nav,
const fs::path& path)
: PlaylistView(nav) {
auto ext = path.extension();
if (path_iequal(ext, ppl_ext))
on_file_changed(path);
else if (path_iequal(ext, c16_ext))
add_entry(fs::path{path});
}
PlaylistView::~PlaylistView() {
transmitter_model.disable();
baseband::shutdown();
@ -446,8 +478,11 @@ void PlaylistView::on_hide() {
View::on_hide();
}
void PlaylistView::focus() {
button_open.focus();
void PlaylistView::on_show() {
if (playlist_path_.empty())
button_add.focus();
else
button_play.focus();
}
} /* namespace ui */

View File

@ -45,13 +45,14 @@ namespace ui {
class PlaylistView : public View {
public:
PlaylistView(NavigationView& nav);
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
~PlaylistView();
void set_parent_rect(Rect new_parent_rect) override;
void on_hide() override;
void focus() override;
void on_show() override;
std::string title() const override { return "Playlist"; };
std::string title() const override { return "Replay"; };
private:
NavigationView& nav_;
@ -146,41 +147,41 @@ class PlaylistView : public View {
{0 * 8, 3 * 16, 30 * 8, 16}};
NewButton button_prev{
{0 * 8, 4 * 16, 4 * 8, 2 * 16},
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_left,
Color::dark_grey()};
NewButton button_add{
NewButton button_next{
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_right,
Color::dark_grey()};
NewButton button_add{
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_new_file,
Color::orange()};
NewButton button_delete{
{10 * 8, 4 * 16, 4 * 8, 2 * 16},
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_delete,
Color::orange()};
NewButton button_open{
{16 * 8, 4 * 16, 4 * 8, 2 * 16},
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_load,
Color::dark_blue()};
NewButton button_save{
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_save,
Color::dark_blue()};
NewButton button_next{
{26 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_right,
Color::dark_grey()};
spectrum::WaterfallWidget waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{

View File

@ -488,6 +488,24 @@ path operator/(const path& lhs, const path& rhs) {
return result;
}
bool path_iequal(
const path& lhs,
const path& rhs) {
const auto& lhs_str = lhs.native();
const auto& rhs_str = rhs.native();
// NB: Not correct for Unicode/locales.
if (lhs_str.length() == rhs_str.length()) {
for (size_t i = 0; i < lhs_str.length(); ++i)
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
return false;
return true;
}
return false;
}
directory_iterator::directory_iterator(
std::filesystem::path path,
std::filesystem::path wild)

View File

@ -168,6 +168,9 @@ bool operator>(const path& lhs, const path& rhs);
path operator+(const path& lhs, const path& rhs);
path operator/(const path& lhs, const path& rhs);
/* Case insensitive path equality on underlying "native" string. */
bool path_iequal(const path& lhs, const path& rhs);
using file_status = BYTE;
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");

View File

@ -584,7 +584,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
{"SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav]() { nav.push<SSTVTXView>(); }},
{"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }},
{"TouchTune", ui::Color::green(), &bitmap_icon_touchtunes, [&nav]() { nav.push<TouchTunesView>(); }},
{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
//{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
{"S.Painter", ui::Color::orange(), &bitmap_icon_paint, [&nav]() { nav.push<SpectrumPainterView>(); }},
//{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push<RemoteView>(); } },
});
@ -630,7 +630,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
{"Receive", Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
{"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
{"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<ReplayAppView>(); }},
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<PlaylistView>(); }},
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},