/* * SPDX-FileCopyrightText: 2025 Felix Morgner * SPDX-License-Identifier: LGPL-2.1-only */ #include "tracker.hpp" #include "messages.hpp" #include "participant_editor.hpp" #include "preferences.hpp" #include "settings.hpp" #include "template_widget.hpp" #include "turn_order_view.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Turns::gui { namespace { auto constexpr static TYPE_NAME = "Tracker"; auto constexpr static TEMPLATE = "/ch/arknet/Turns/tracker.ui"; auto file_filters() { auto filters = Gio::ListStore::create(); auto filter = Gtk::FileFilter::create(); filter->set_name(_("Turns Files")); filter->add_pattern("*.trns"); filters->append(filter); return filters; } } // namespace Tracker::Tracker() : Tracker{{}, gui::get_settings()} { } Tracker::Tracker(Glib::RefPtr const & app, Glib::RefPtr const & settings) : Glib::ObjectBase{TYPE_NAME} , template_widget{TEMPLATE, app} , m_controls{get_widget("controls")} , m_empty{get_widget("empty")} , m_overlay{get_widget("overlay")} , m_stack{get_widget("stack")} , m_start{get_widget("start")} , m_title{get_widget("title")} , m_turn_order{std::make_shared()} , m_turn_order_view{Gtk::make_managed(m_turn_order)} , m_settings{std::move(settings)} , m_subtitle{m_title->property_subtitle()} , m_css{Gtk::CssProvider::create()} { setup_colors(); setup_actions(); m_stack->add(*m_turn_order_view); m_turn_order->property_empty().signal_changed().connect(sigc::mem_fun(*this, &Tracker::update_subtitle)); // m_turn_order->round_number().signal_changed().connect(sigc::mem_fun(*this, &Tracker::update_subtitle)); update_subtitle(); // clang-format off Glib::Binding::bind_property(m_turn_order->property_empty(), m_stack->property_visible_child(), Glib::Binding::Flags::SYNC_CREATE, [this](auto empty) { return empty ? m_empty : m_turn_order_view; }); Glib::Binding::bind_property(m_turn_order->property_running(), m_controls->property_reveal_child(), Glib::Binding::Flags::SYNC_CREATE); // clang-format on // m_settings->bind("skip-defeated", m_turn_order->skip_defeated()); } auto Tracker::load(Glib::RefPtr file) -> void { if (file->query_exists()) { m_file = file; m_file->load_contents_async(sigc::mem_fun(*this, &Tracker::on_load_content_done)); set_sensitive(false); } } auto Tracker::setup_actions() -> void { // win.add_participant // depends-on: turn_order:state == stopped { auto action = add_action("add_participant", sigc::mem_fun(*this, &Tracker::add_participant)); Glib::Binding::bind_property(m_turn_order->property_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } // win.clear // depends-on: turn_order:property_empty == false { auto action = add_action("clear", sigc::mem_fun(*m_turn_order, &TurnOrder::clear)); Glib::Binding::bind_property(m_turn_order->property_empty(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } // // win.next // // depends-on: turn_order:state == running // { // auto action = add_action("next", sigc::mem_fun(*m_turn_order, &TurnOrder::next)); // Glib::Binding::bind_property(m_turn_order->property_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); // } // // win.previous // // depends-on: turn_order:has_previous == true // { // auto action = add_action("previous", sigc::mem_fun(*m_turn_order, &TurnOrder::previous)); // Glib::Binding::bind_property(m_turn_order->has_previous(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); // } // // win.start // // depends-on: turn_order:property_empty == false // { // auto action = add_action("start", sigc::mem_fun(*m_turn_order, &TurnOrder::start)); // Glib::Binding::bind_property(m_turn_order->property_empty(), // action->property_enabled(), // Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); // Glib::Binding::bind_property(m_turn_order->property_running(), // m_start->property_visible(), // Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); // } // win.stop // depends-on: turn_order:running == true { auto action = add_action("stop", sigc::mem_fun(*this, &Tracker::stop)); Glib::Binding::bind_property(m_turn_order->property_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); } // win.delete // win.edit // win.open // win.preferences { add_action_with_parameter("delete", Glib::VARIANT_TYPE_INT32, sigc::mem_fun(*this, &Tracker::delete_participant)); add_action_with_parameter("edit", Glib::VARIANT_TYPE_INT32, sigc::mem_fun(*this, &Tracker::edit_participant)); add_action("open", sigc::mem_fun(*this, &Tracker::open)); add_action("preferences", sigc::mem_fun(*this, &Tracker::preferences)); } // win.save // depends-on: turn_order:property_empty == false { auto action = add_action("save", sigc::bind(sigc::mem_fun(*this, &Tracker::save), false)); Glib::Binding::bind_property(m_turn_order->property_empty(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } // win.save-as // depends-on: turn_order:property_empty == false { auto action = add_action("save-as", sigc::bind(sigc::mem_fun(*this, &Tracker::save), true)); Glib::Binding::bind_property(m_turn_order->property_empty(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } } auto Tracker::setup_colors() -> void { Gtk::CssProvider::add_provider_for_display(get_display(), m_css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); m_settings->signal_changed().connect(sigc::mem_fun(*this, &Tracker::on_settings_changed)); update_colors(); } auto Tracker::add_participant() -> void { auto dialog = Gtk::make_managed(nullptr); dialog->signal_finished().connect([this](auto n, auto p, auto d) { m_turn_order->add(Participant::create(n, p, d)); }); dialog->present(this); } auto Tracker::delete_participant(Glib::VariantBase param) -> void { auto index = Glib::VariantBase::cast_dynamic>(param); m_turn_order->remove_at(index.get()); } auto Tracker::edit_participant(Glib::VariantBase param) -> void { auto index = Glib::VariantBase::cast_dynamic>(param); auto participant = m_turn_order->get_typed_object(index.get()); auto dialog = Gtk::make_managed(participant); dialog->present(this); } auto Tracker::open() -> void { auto dialog = Gtk::FileDialog::create(); dialog->set_filters(file_filters()); dialog->open(sigc::bind(sigc::mem_fun(*this, &Tracker::on_open_response), dialog)); } auto Tracker::preferences() -> void { auto preferences = Gtk::make_managed(m_settings); auto dialog = Gtk::make_managed(); dialog->add(*preferences); dialog->set_visible_page(*preferences); dialog->present(this); } auto Tracker::save(bool force_ask) -> void { if (m_file && !force_ask) { start_replace_content(); } else { auto dialog = Gtk::FileDialog::create(); m_file ? dialog->set_initial_file(m_file) : dialog->set_initial_name(_(message::new_turn_order_file_name)); dialog->set_filters(file_filters()); dialog->save(*this, sigc::bind(sigc::mem_fun(*this, &Tracker::on_save_response), dialog)); } } auto Tracker::stop() -> void { auto dialog = Adwaita::AlertDialog::create(_(message::stop_turn_order), _(message::question_clear_turn_order)); dialog->add_response("stop", _(message::stop)); dialog->set_response_appearance("stop", Adwaita::ResponseAppearance::Suggested); dialog->add_response("clear", _(message::stop_and_clear)); dialog->set_response_appearance("clear", Adwaita::ResponseAppearance::Destructive); dialog->add_response("cancel", _(message::cancel)); dialog->set_response_appearance("cancel", Adwaita::ResponseAppearance::Default); dialog->set_close_response("cancel"); dialog->set_default_response("cancel"); dialog->choose(*this, nullptr, [dialog = std::move(dialog), this](auto const & result) { auto response = dialog->choose_finish(result); if (response == "cancel") { return; } if (response == "clear") { m_turn_order->clear(); } // m_turn_order->stop(); }); } auto Tracker::on_open_response(Glib::RefPtr result, Glib::RefPtr dialog) -> void { try { m_file = dialog->open_finish(result); } catch (std::exception const & e) { return show_error(e); } m_file->load_contents_async(sigc::mem_fun(*this, &Tracker::on_load_content_done)); set_sensitive(false); } auto Tracker::on_save_response(Glib::RefPtr result, Glib::RefPtr dialog) -> void { try { m_file = dialog->save_finish(result); } catch (std::exception const & e) { show_error(e); } start_replace_content(); set_sensitive(false); } auto Tracker::on_load_content_done(Glib::RefPtr result) -> void { static_cast(result); // set_sensitive(); // char * data{}; // auto size = std::size_t{}; // try // { // if (!m_file->load_contents_finish(result, data, size, m_file_etag)) // { // m_file.reset(); // m_file_etag.clear(); // return; // } // m_turn_order->load(nlohmann::json::parse(std::string_view{data, size})); // } // catch (std::exception const & e) // { // return show_error(e); // } // auto name = m_file->get_basename(); // show_toast(std::vformat(_(lang::successfully_opened_format), std::make_format_args(name))); // set_title(std::format("{} - {}", _(lang::turns), name)); } auto Tracker::on_replace_content_done(Glib::RefPtr result) -> void { set_sensitive(); try { m_file->replace_contents_finish(result, m_file_etag); } catch (Gio::Error const & e) { return show_error(e); } auto name = m_file->get_basename(); show_toast(std::vformat(_(message::successfully_saved_format), std::make_format_args(name))); set_title(std::format("{} - {}", _(message::turns), name)); } auto Tracker::on_settings_changed(Glib::ustring) -> void { update_colors(); } auto Tracker::start_replace_content() -> void { // m_file_buffer = m_turn_order->serialize().dump(2); // m_file->replace_contents_async(sigc::mem_fun(*this, &Tracker::on_replace_content_done), m_file_buffer, m_file_etag); } auto Tracker::show_error(std::exception const & e) -> void { auto error = e.what(); show_toast(std::vformat(_(message::saving_failed_format), std::make_format_args(error))); } auto Tracker::show_toast(std::string const & message) -> void { m_overlay->add_toast(*Adwaita::Toast::create(message)); } auto Tracker::update_colors() -> void { auto friendly_color = m_settings->get_string("disposition-color-friendly"); auto hostile_color = m_settings->get_string("disposition-color-hostile"); auto secret_color = m_settings->get_string("disposition-color-secret"); m_css->load_from_string(std::format("@define-color friendly {};\n" "@define-color hostile {};\n" "@define-color secret {};\n", friendly_color.c_str(), hostile_color.c_str(), secret_color.c_str())); } auto Tracker::update_subtitle() -> void { if (m_turn_order->property_empty()) { m_subtitle = _(message::no_active_turn_order); } else { // auto round_number = m_turn_order->round_number() + 1; // m_subtitle = round_number == 0 ? "" : std::vformat(_(message::round_number), std::make_format_args(round_number)); } } } // namespace Turns::gui