424 lines
No EOL
17 KiB
C++
424 lines
No EOL
17 KiB
C++
#include "main_window.hpp"
|
|
#include <gtkmm.h>
|
|
#include "my_slider.hpp"
|
|
#include <filesystem>
|
|
#include <format>
|
|
#include <gtkmm/menubutton.h>
|
|
#include <util.hpp>
|
|
void MainWindow::set_song_loaded(bool loaded) {
|
|
bool unloaded = !loaded;
|
|
seek_bar.set_sensitive(loaded);
|
|
pause_btn.set_sensitive(loaded);
|
|
restart_btn.set_sensitive(loaded);
|
|
stop_btn.set_sensitive(loaded);
|
|
action_enabled_changed("toggle_pause", loaded);
|
|
action_enabled_changed("stop_playback", loaded);
|
|
action_enabled_changed("restart_playback", loaded);
|
|
}
|
|
void MainWindow::set_subtitle(std::optional<Glib::ustring> subtitle) {
|
|
subTitleLabel.set_visible(subtitle.has_value());
|
|
if (subtitle.has_value()) {
|
|
subTitleLabel.set_text(subtitle.value());
|
|
set_title(subtitle.value() + Glib::ustring(" - ") + titleLabel.get_text());
|
|
} else {
|
|
set_title(titleLabel.get_text());
|
|
}
|
|
}
|
|
void MainWindow::update_file(optional<std::string> new_file) {
|
|
|
|
playback_file = new_file;
|
|
bool song_loaded = new_file.has_value();
|
|
set_song_loaded(song_loaded);
|
|
if (song_loaded) {
|
|
length = playback->GetLength();
|
|
length_component_count = TimeToComponentCount(length);
|
|
length_label.set_text(TimeToString(length, length_component_count).c_str());
|
|
seek_bar.set_range(0, length);
|
|
} else {
|
|
length = 0;
|
|
length_component_count = 2;
|
|
length_label.set_text("00:00");
|
|
seek_bar.set_value(0.0);
|
|
}
|
|
for (auto row : stream_selection_box.get_children()) {
|
|
row->remove_data(Glib::Quark("id"));
|
|
}
|
|
stream_selection_box.remove_all();
|
|
for (auto &stream : playback->get_streams()) {
|
|
int id = stream.id;
|
|
Glib::ustring name(stream.name.c_str());
|
|
Gtk::Label idLabel;
|
|
idLabel.set_text(Glib::ustring(std::format("{}", id).c_str()));
|
|
Gtk::Label nameLabel;
|
|
nameLabel.set_text(name);
|
|
Gtk::Box parentBox;
|
|
parentBox.append(idLabel);
|
|
parentBox.append(nameLabel);
|
|
parentBox.set_orientation(Gtk::Orientation::HORIZONTAL);
|
|
parentBox.set_hexpand();
|
|
nameLabel.set_hexpand();
|
|
Gtk::ListBoxRow listBoxRow;
|
|
listBoxRow.set_child(parentBox);
|
|
listBoxRow.set_hexpand();
|
|
listBoxRow.set_data(Glib::Quark("id"), new int(id));
|
|
stream_selection_box.append(listBoxRow);
|
|
if (id == this->playback->get_current_stream()) {
|
|
stream_selection_box.select_row(listBoxRow);
|
|
}
|
|
}
|
|
stream_selection_box.signal_row_selected().connect([=,this](auto *row) {
|
|
if (row == nullptr || row->get_data(Glib::Quark("id")) == nullptr) {
|
|
return;
|
|
}
|
|
this->playback->play_stream(*(int*)row->get_data(Glib::Quark("id")));
|
|
});
|
|
std::optional<std::string> title = playback->get_current_title();
|
|
song_name = title.value_or("");
|
|
subTitleLabel.set_visible(title.has_value());
|
|
|
|
if (title.has_value()) {
|
|
set_subtitle(song_name);
|
|
} else {
|
|
set_subtitle({});
|
|
}
|
|
bool length_valid = length > 0;
|
|
seek_bar.set_visible(length_valid);
|
|
seek_indeterminate.set_visible(!length_valid);
|
|
time_label.set_visible(length_valid);
|
|
length_label.set_visible(length_valid);
|
|
}
|
|
struct signals_split_by_expected {
|
|
uint16_t expected;
|
|
uint16_t unexpected;
|
|
inline uint16_t all_signals() {
|
|
return expected|unexpected;
|
|
}
|
|
inline uint16_t filter(uint16_t signal) {
|
|
return all_signals() & signal;
|
|
}
|
|
inline bool emitted(uint16_t signal) {
|
|
return filter(signal) != 0;
|
|
}
|
|
inline uint16_t filter_expected(uint16_t signal) {
|
|
return expected & signal;
|
|
}
|
|
inline uint16_t filter_unexpected(uint16_t signal) {
|
|
return unexpected & signal;
|
|
}
|
|
inline bool signal_expected(uint16_t signal) {
|
|
return filter_expected(signal) == filter(signal);
|
|
}
|
|
inline bool signal_unexpected(uint16_t signal) {
|
|
return filter_unexpected(signal) == filter(signal);
|
|
}
|
|
signals_split_by_expected(uint16_t expected, uint16_t unexpected)
|
|
: expected(expected)
|
|
, unexpected(unexpected)
|
|
{}
|
|
};
|
|
bool MainWindow::update() {
|
|
uint16_t signals_expected_and_emitted = playback->handle_signals(expected_signals);
|
|
uint16_t unexpected_signals = PlaybackSignalNone;
|
|
expected_signals &= ~signals_expected_and_emitted;
|
|
auto handle_signal = [this,signals_expected_and_emitted,&unexpected_signals](uint16_t signals) {
|
|
uint16_t output = signals_expected_and_emitted & signals;
|
|
uint16_t unexpected_output = PlaybackSignalNone;
|
|
uint16_t remaining_signals = signals & ~output;
|
|
if (remaining_signals != PlaybackSignalNone) {
|
|
unexpected_output |= playback->handle_signals(remaining_signals);
|
|
}
|
|
unexpected_signals |= unexpected_output;
|
|
return signals_split_by_expected(output, unexpected_output);
|
|
};
|
|
auto signals_handleable = handle_signal(PlaybackSignalFileChanged|PlaybackSignalErrorOccurred|PlaybackSignalPaused|PlaybackSignalResumed|PlaybackSignalPitchChanged|PlaybackSignalSeeked|PlaybackSignalSpeedChanged|PlaybackSignalStarted|PlaybackSignalStopped|PlaybackSignalTempoChanged);
|
|
if (signals_handleable.emitted(PlaybackSignalFileChanged)) {
|
|
auto new_file = playback->get_current_file();
|
|
if (new_file != playback_file) {
|
|
update_file(new_file);
|
|
}
|
|
}
|
|
bool disambiguate_running = false;
|
|
bool stopped = signals_handleable.emitted(PlaybackSignalStopped);
|
|
bool started = signals_handleable.emitted(PlaybackSignalStarted);
|
|
if (stopped && started) {
|
|
disambiguate_running = true;
|
|
started = playback->get_current_file().has_value();
|
|
stopped = !started;
|
|
}
|
|
if (stopped) {
|
|
update_file({});
|
|
}
|
|
double position = playback->GetPosition();
|
|
if (!signals_handleable.emitted(PlaybackSignalSeeked) || signals_handleable.signal_unexpected(PlaybackSignalSeeked)) {
|
|
seek_bar.set_value(position);
|
|
}
|
|
time_label.set_text(TimeToString(position, length_component_count));
|
|
std::vector<Glib::ustring> classes;
|
|
if (playback->IsPaused()) {
|
|
classes.push_back("paused");
|
|
pause_btn.set_icon_name("media-playback-start");
|
|
} else {
|
|
pause_btn.set_icon_name("media-playback-pause");
|
|
}
|
|
if (length <= 0) {
|
|
classes.push_back("no-length");
|
|
}
|
|
if (playback_file.has_value()) {
|
|
classes.push_back("loaded");
|
|
}
|
|
if (signals_handleable.signal_unexpected(PlaybackSignalPitchChanged)) {
|
|
pitch_slider.set_value(playback->GetPitch());
|
|
}
|
|
if (signals_handleable.signal_unexpected(PlaybackSignalSpeedChanged)) {
|
|
speed_slider.set_value(playback->GetSpeed());
|
|
}
|
|
if (signals_handleable.signal_unexpected(PlaybackSignalTempoChanged)) {
|
|
tempo_slider.set_value(playback->GetTempo());
|
|
}
|
|
if (!running) {
|
|
timer_stopped = true;
|
|
g_mutex_lock(timer_mutex);
|
|
g_cond_signal(timer_stopped_condition);
|
|
g_mutex_unlock(timer_mutex);
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
void MainWindow::expect_signal(uint16_t signal) {
|
|
expected_signals |= signal;
|
|
}
|
|
void MainWindow::update_dark_mode() {
|
|
Gtk::Settings::get_default()->set_property("gtk-application-prefer-dark-theme", Looper::Options::get_option<bool>("ui.gtk.dark_mode", false));
|
|
}
|
|
MainWindow::MainWindow(Playback *playback, Glib::RefPtr<Gtk::Application> app)
|
|
: playback(playback),
|
|
open_dialog("Open some music...")
|
|
{
|
|
set_application(app);
|
|
update_dark_mode();
|
|
open_dialog.set_transient_for(*this);
|
|
open_dialog.set_modal(true);
|
|
open_dialog.signal_response().connect([this](int response) {
|
|
if (response == Gtk::ResponseType::OK) {
|
|
this->expect_signal(PlaybackSignalStarted|PlaybackSignalFileChanged);
|
|
this->playback->Start(this->open_dialog.get_file().get()->get_path());
|
|
}
|
|
open_dialog.set_visible(false);
|
|
});
|
|
open_dialog.add_button("_Cancel", Gtk::ResponseType::CANCEL);
|
|
open_dialog.add_button("_Open", Gtk::ResponseType::OK);
|
|
auto filter_text = Gtk::FileFilter::create();
|
|
filter_text->set_name("Music files");
|
|
filter_text->add_mime_type("audio/*");
|
|
open_dialog.add_filter(filter_text);
|
|
set_title("Looper");
|
|
set_icon_name("looper");
|
|
set_name("main-window");
|
|
add_action("about_window", [this]() {
|
|
aboutWindow.present();
|
|
});
|
|
add_action("options_window", [this]() {
|
|
optionsWindow.present();
|
|
});
|
|
add_action("open_file", [this]() {
|
|
this->open_dialog.present();
|
|
});
|
|
add_action("toggle_pause", [this]() {
|
|
this->expect_signal((this->playback->IsPaused() ? PlaybackSignalResumed : PlaybackSignalPaused));
|
|
this->playback->Pause();
|
|
});
|
|
add_action("stop_playback", [this]() {
|
|
this->playback->Stop();
|
|
});
|
|
add_action("restart_playback", [this]() {
|
|
this->expect_signal(PlaybackSignalSeeked);
|
|
this->playback->Seek(0.0);
|
|
});
|
|
|
|
titleBox.set_orientation(Gtk::Orientation::VERTICAL);
|
|
titleBox.set_valign(Gtk::Align::CENTER);
|
|
titleLabel.set_text(get_title());
|
|
titleLabel.set_css_classes({"title"});
|
|
subTitleLabel.set_visible(false);
|
|
subTitleLabel.set_css_classes({"subtitle"});
|
|
titleLabel.insert_at_end(titleBox);
|
|
subTitleLabel.insert_at_end(titleBox);
|
|
headerBar.set_title_widget(titleBox);
|
|
set_default_size(400, 400);
|
|
openBtn.set_label("Open...");
|
|
openBtn.set_name("open-btn");
|
|
openBtn.set_icon_name("folder-open");
|
|
openBtn.set_action_name("win.open_file");
|
|
headerBar.pack_start(openBtn);
|
|
running = true;
|
|
timer_stopped = false;
|
|
timer_stopped_condition = (GCond*)malloc(sizeof(GCond));
|
|
timer_mutex = (GMutex*)malloc(sizeof(GMutex));
|
|
g_cond_init(timer_stopped_condition);
|
|
g_mutex_init(timer_mutex);
|
|
|
|
top_box.set_orientation(Gtk::Orientation::HORIZONTAL);
|
|
top_box.set_name("top-box");
|
|
bottom_box.set_orientation(Gtk::Orientation::HORIZONTAL);
|
|
bottom_box.set_name("bottom-box");
|
|
controls_box.set_orientation(Gtk::Orientation::VERTICAL);
|
|
controls_box.set_name("controls-box");
|
|
main_box.set_orientation(Gtk::Orientation::VERTICAL);
|
|
main_box.set_name("main-box");
|
|
//main_box.set_valign(Gtk::Align::END);
|
|
main_box.set_vexpand(true);
|
|
controls_box.insert_at_end(main_box);
|
|
top_box.insert_at_end(controls_box);
|
|
bottom_box.insert_at_end(controls_box);
|
|
pause_btn.set_action_name("win.toggle_pause");
|
|
pause_btn.set_name("pause-btn");
|
|
pause_btn.insert_at_end(top_box);
|
|
restart_btn.set_name("restart-btn");
|
|
restart_btn.set_icon_name("view-refresh");
|
|
restart_btn.set_action_name("win.restart_playback");
|
|
restart_btn.insert_at_end(top_box);
|
|
time_label.set_name("time-label");
|
|
time_label.insert_at_end(top_box);
|
|
seek_indeterminate.set_name("seek-placeholder");
|
|
seek_indeterminate.set_text("");
|
|
seek_indeterminate.set_hexpand(true);
|
|
seek_indeterminate.insert_at_end(top_box);
|
|
seek_bar.signal_change_value().connect([this](Gtk::ScrollType scrollType, double value) -> bool {
|
|
this->expect_signal(PlaybackSignalSeeked);
|
|
this->playback->Seek(value);
|
|
return true;
|
|
}, true);
|
|
seek_bar.set_hexpand();
|
|
seek_bar.set_name("seek-bar");
|
|
seek_bar.insert_at_end(top_box);
|
|
length_label.set_name("length-label");
|
|
length_label.insert_at_end(top_box);
|
|
stop_btn.set_action_name("win.stop_playback");
|
|
stop_btn.set_name("stop-btn");
|
|
stop_btn.set_icon_name("media-playback-stop");
|
|
stop_btn.insert_at_end(top_box);
|
|
volume_slider.set_min_value(0.0);
|
|
volume_slider.set_max_value(100.0);
|
|
volume_slider.set_digits_after_decimal(0);
|
|
volume_slider.set_suffix("%");
|
|
volume_slider.set_prefix("Volume: ");
|
|
volume_slider.set_name("volume-slider");
|
|
volume_slider.value_changed.connect([this](double value) {
|
|
this->playback->SetVolume(value);
|
|
});
|
|
volume_slider.set_hexpand(false);
|
|
volume_slider.insert_at_end(top_box);
|
|
speed_slider.set_hexpand();
|
|
speed_slider.set_digits_after_decimal(2);
|
|
speed_slider.set_prefix("Speed: ");
|
|
speed_slider.set_suffix("x");
|
|
speed_slider.set_min_value(0.25);
|
|
speed_slider.set_max_value(4);
|
|
speed_slider.set_logarithmic(true);
|
|
speed_slider.set_value(playback->GetSpeed());
|
|
speed_slider.set_name("speed-slider");
|
|
speed_slider.value_changed.connect([this](double value) {
|
|
this->expect_signal(PlaybackSignalSpeedChanged);
|
|
this->playback->SetSpeed(value);
|
|
});
|
|
speed_slider.insert_at_end(bottom_box);
|
|
tempo_slider.set_hexpand();
|
|
tempo_slider.set_digits_after_decimal(2);
|
|
tempo_slider.set_prefix("Tempo: ");
|
|
tempo_slider.set_suffix("x");
|
|
tempo_slider.set_min_value(0.25);
|
|
tempo_slider.set_max_value(4);
|
|
tempo_slider.set_value(playback->GetTempo());
|
|
tempo_slider.set_name("tempo-slider");
|
|
tempo_slider.value_changed.connect([this](double value) {
|
|
this->expect_signal(PlaybackSignalTempoChanged);
|
|
this->playback->SetTempo(value);
|
|
});
|
|
tempo_slider.insert_at_end(bottom_box);
|
|
pitch_slider.set_hexpand();
|
|
pitch_slider.set_digits_after_decimal(2);
|
|
pitch_slider.set_prefix("Pitch: ");
|
|
pitch_slider.set_suffix("x");
|
|
pitch_slider.set_min_value(0.25);
|
|
pitch_slider.set_max_value(4);
|
|
pitch_slider.set_value(playback->GetPitch());
|
|
pitch_slider.set_name("pitch-slider");
|
|
pitch_slider.value_changed.connect([this](double value) {
|
|
this->expect_signal(PlaybackSignalPitchChanged);
|
|
this->playback->SetPitch(value);
|
|
});
|
|
pitch_slider.insert_at_end(bottom_box);
|
|
/*dropSpaceLabel.set_text("Drop a music file here to play it...");
|
|
dropSpaceLabel.set_can_focus(false);
|
|
dropSpaceLabel.set_can_target(false);
|
|
dropSpaceLabel.set_name("drop-space-label");*/
|
|
dropSpace.set_can_target(true);
|
|
dropTarget = Gtk::DropTarget::create(G_TYPE_FILE, Gdk::DragAction::LINK);
|
|
dropSpace.add_controller(dropTarget);
|
|
//stream_selection_box.set_child(dropSpaceLabel);
|
|
dropSpace.set_expand(true);
|
|
dropSpace.set_vexpand_set(true);
|
|
dropSpace.set_name("drop-space-scrolled-window");
|
|
dropSpace.set_child(stream_selection_box);
|
|
stream_selection_box.set_vexpand(true);
|
|
stream_selection_box.set_hexpand(true);
|
|
stream_selection_box.set_vexpand_set();
|
|
stream_selection_box.set_name("stream-selection-box");
|
|
dropTarget->signal_drop().connect([this](const Glib::ValueBase &value, double a, double b) {
|
|
auto obj = g_value_get_object(value.gobj());
|
|
g_object_ref(obj);
|
|
Gio::File file((GFile*)obj);
|
|
this->expect_signal(PlaybackSignalStarted|PlaybackSignalFileChanged);
|
|
this->playback->Start(file.get_path());
|
|
return false;
|
|
}, false);
|
|
dropSpace.insert_at_start(main_box);
|
|
set_child(main_box);
|
|
update_file(playback->get_current_file());
|
|
pitch_slider.set_value(playback->GetPitch(), true);
|
|
speed_slider.set_value(playback->GetSpeed(), true);
|
|
tempo_slider.set_value(playback->GetTempo(), true);
|
|
volume_slider.set_value(playback->GetVolume(), true);
|
|
optionsWindow.set_visible(false);
|
|
optionsWindow.set_transient_for(*this);
|
|
optionsWindow.set_hide_on_close(true);
|
|
optionsWindow.set_modal();
|
|
aboutWindow.set_visible(false);
|
|
aboutWindow.set_transient_for(*this);
|
|
aboutWindow.set_hide_on_close(true);
|
|
aboutWindow.set_modal();
|
|
main_menu = Gio::Menu::create();
|
|
auto file_menu = Gio::Menu::create();
|
|
file_menu->append("Open...", "win.open_file");
|
|
file_menu->append("About...", "win.about_window");
|
|
file_menu->append("Options...", "win.options_window");
|
|
file_menu->append("Quit", "app.quit");
|
|
auto playback_menu = Gio::Menu::create();
|
|
playback_menu->append("Pause/Resume", "win.toggle_pause");
|
|
playback_menu->append("Restart from Beginning", "win.restart_playback");
|
|
playback_menu->append("Stop", "win.stop_playback");
|
|
main_menu->append_submenu("File", file_menu);
|
|
main_menu->append_submenu("Playback", playback_menu);
|
|
app->set_menubar(main_menu);
|
|
options_menu = Gio::Menu::create();
|
|
options_menu->append("About", "win.about_window");
|
|
options_menu->append("Options", "win.options_window");
|
|
set_show_menubar(false);
|
|
menuPopover.set_menu_model(options_menu);
|
|
menuPopover.set_autohide();
|
|
menuBtn.set_popover(menuPopover);
|
|
menuBtn.set_icon_name("application-menu");
|
|
headerBar.pack_end(menuBtn);
|
|
optionsWindow.optionsSaved.connect(sigc::mem_fun(*this, &MainWindow::update_dark_mode));
|
|
set_titlebar(headerBar);
|
|
Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::update), 100);
|
|
}
|
|
MainWindow::~MainWindow() {
|
|
running = false;
|
|
while (!timer_stopped) {
|
|
g_cond_wait(timer_stopped_condition, timer_mutex);
|
|
}
|
|
free(timer_stopped_condition);
|
|
free(timer_mutex);
|
|
} |