looper/backends/ui/gtk/main_window.cpp

295 lines
12 KiB
C++
Raw Normal View History

#include "main_window.hpp"
#include <gtkmm.h>
#include "my_slider.hpp"
#include <filesystem>
#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);
}
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());
std::filesystem::path file_path(new_file.value());
song_name = file_path.stem().string();
set_title((song_name + std::string(" - Looper")).c_str());
seek_bar.set_range(0, length);
} else {
length = 0;
length_component_count = 2;
length_label.set_text("00:00");
song_name = "";
set_title("Looper");
seek_bar.set_value(0.0);
}
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));
pause_btn.set_icon_name(playback->IsPaused() ? "media-playback-start-symbolic" : "media-playback-pause-symbolic");
if (signals_handleable.signal_unexpected(PlaybackSignalPitchChanged)) {
pitch_slider.set_value(playback->pitch);
}
if (signals_handleable.signal_unexpected(PlaybackSignalSpeedChanged)) {
speed_slider.set_value(playback->speed);
}
if (signals_handleable.signal_unexpected(PlaybackSignalTempoChanged)) {
tempo_slider.set_value(playback->tempo);
}
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;
}
MainWindow::MainWindow(Playback *playback)
: playback(playback),
open_dialog("Open some music...")
{
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_default_size(400, 400);
openBtn.set_label("Open...");
openBtn.set_icon_name("folder-open-symbolic");
openBtn.signal_clicked().connect([this]() {
this->open_dialog.present();
});
topToolbar.set_orientation(Gtk::Orientation::HORIZONTAL);
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);
bottom_box.set_orientation(Gtk::Orientation::HORIZONTAL);
main_box.set_orientation(Gtk::Orientation::VERTICAL);
//main_box.set_valign(Gtk::Align::END);
main_box.set_vexpand(true);
top_box.insert_at_end(main_box);
bottom_box.insert_at_end(main_box);
pause_btn.signal_clicked().connect([this]() {
this->expect_signal((this->playback->IsPaused() ? PlaybackSignalResumed : PlaybackSignalPaused));
this->playback->Pause();
});
pause_btn.insert_at_end(top_box);
restart_btn.set_icon_name("view-refresh-symbolic");
restart_btn.signal_clicked().connect([this]() {
this->expect_signal(PlaybackSignalSeeked);
this->playback->Seek(0.0);
});
restart_btn.insert_at_end(top_box);
time_label.insert_at_end(top_box);
seek_indeterminate.set_text("");
seek_indeterminate.set_hexpand(true);
seek_indeterminate.insert_at_end(top_box);
seek_bar.signal_value_changed().connect([this]() {
this->expect_signal(PlaybackSignalSeeked);
this->playback->Seek(seek_bar.get_value());
});
seek_bar.set_hexpand();
seek_bar.insert_at_end(top_box);
length_label.insert_at_end(top_box);
stop_btn.signal_clicked().connect([this]() {
this->expect_signal(PlaybackSignalStopped);
this->playback->Stop();
});
stop_btn.set_icon_name("media-playback-stop-symbolic");
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.value_changed.connect([this](double value) {
this->playback->volume = value;
this->playback->Update();
});
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_max_value(playback->MaxSpeed);
speed_slider.set_min_value(playback->MinSpeed);
speed_slider.set_value(playback->speed);
speed_slider.value_changed.connect([this](double value) {
this->expect_signal(PlaybackSignalSpeedChanged);
this->playback->speed = value;
this->playback->Update();
});
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_max_value(playback->MaxTempo);
tempo_slider.set_min_value(playback->MinTempo);
tempo_slider.set_value(playback->tempo);
tempo_slider.value_changed.connect([this](double value) {
this->expect_signal(PlaybackSignalTempoChanged);
this->playback->tempo = value;
this->playback->Update();
});
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_max_value(playback->MaxPitch);
pitch_slider.set_min_value(playback->MinPitch);
pitch_slider.set_value(playback->pitch);
pitch_slider.value_changed.connect([this](double value) {
this->expect_signal(PlaybackSignalPitchChanged);
this->playback->pitch = value;
this->playback->Update();
});
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);
dropSpace.set_can_target(true);
dropTarget = Gtk::DropTarget::create(G_TYPE_FILE, Gdk::DragAction::LINK);
dropSpace.add_controller(dropTarget);
dropSpace.set_child(dropSpaceLabel);
dropSpace.set_vexpand(true);
dropSpace.set_hexpand(true);
dropSpace.set_vexpand_set();
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);
topToolbar.insert_at_start(main_box);
set_child(main_box);
update_file(playback->get_current_file());
pitch_slider.set_value(playback->pitch);
speed_slider.set_value(playback->speed);
tempo_slider.set_value(playback->tempo);
volume_slider.set_value(playback->volume);
optionsWindow.set_visible(false);
optionsWindow.set_transient_for(*this);
optionsWindow.set_hide_on_close(true);
optionsWindow.set_modal();
settingsBtn.set_icon_name("preferences-other-symbolic");
settingsBtn.signal_clicked().connect([this]() {
optionsWindow.present();
});
headerBar.pack_start(openBtn);
headerBar.pack_end(settingsBtn);
set_titlebar(headerBar);
Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::update), 20);
}
MainWindow::~MainWindow() {
running = false;
while (!timer_stopped) {
g_cond_wait(timer_stopped_condition, timer_mutex);
}
free(timer_stopped_condition);
free(timer_mutex);
}