// -*- mode: c++; c-basic-offset: 2; -*- #include "common.hh" #include #include #include #include #include "gui_about.hh" #include "gui_main.hh" #include "gui_menu.hh" #include "observers.hh" namespace { template class shared_gobject { public: shared_gobject() : ptr_(nullptr) { } shared_gobject(std::nullptr_t) : ptr_(nullptr) { } explicit shared_gobject(T* ptr) : ptr_(ptr) { } shared_gobject(shared_gobject const& obj) : ptr_(obj.ptr_) { if (ptr_) g_object_ref(ptr_); } shared_gobject(shared_gobject&& obj) : ptr_(obj.ptr_) { obj.ptr = nullptr; } ~shared_gobject() { reset(); } shared_gobject& operator=(shared_gobject const& obj) { reset(obj.ptr_); if (ptr_) g_object_ref(ptr_); return *this; } shared_gobject& operator=(shared_gobject&& obj) { ptr_ = obj.ptr_; obj.ptr_ = nullptr; return *this; } void swap(shared_gobject& obj) { auto x = ptr_; ptr_ = obj.ptr_; obj.ptr_ = x; } void reset() { if (ptr_) { g_object_unref(ptr_); ptr_ = nullptr; } } void reset(T* ptr) { if (ptr_) g_object_unref(ptr_); ptr_ = ptr; } T* get() const { return ptr_; } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } explicit operator bool() const { return ptr_ != nullptr; } private: T* ptr_; }; #define MAIN_APP_TYPE main_app_get_type() G_DECLARE_FINAL_TYPE(MainApp, main_app, MAIN, APP, GtkApplication) #define MAIN_APP_WINDOW_TYPE main_app_window_get_type() G_DECLARE_FINAL_TYPE(MainAppWindow, main_app_window, MAIN, APP_WINDOW, GtkApplicationWindow) class GtkGuiWindow : public GuiWindow { public: GtkWindow* window() const { return reinterpret_cast(impl()); } void set_title(std::string const& title) override; }; class GtkGuiMain : public virtual GuiMain, public GtkGuiWindow { public: GtkGuiMain(std::string const& title, uint32_t width, uint32_t height) : title_(title), width_(width), height_(height) { } void set_menu(GuiMenu* menu) override { assert(menu); menu_ = menu; } GuiMenu* menu() const override { return menu_; } bool run(int argc, char** argv) override; bool exit() override; void set_title(std::string const& title) override; void show(GuiWindow* window) override; uint32_t default_width() const { return width_; } uint32_t default_height() const { return height_; } void add_listener(GuiMain::Listener* listener) override { observers_.insert(listener); } void remove_listener(GuiMain::Listener* listener) override { observers_.erase(listener); } void* impl() const override; private: bool notify_about_to_exit() { auto it = observers_.notify(); while (it.has_next()) { if (!it.next()->about_to_exit()) return false; } return true; } std::string title_; uint32_t width_; uint32_t height_; shared_gobject app_; Observers observers_; GuiMenu* menu_; }; class GtkGuiMenu : public GuiMenu { public: GtkGuiMenu() : parent_(nullptr), menu_(g_menu_new()), map_(nullptr) { } ~GtkGuiMenu() override { } void add_listener(Listener* listener) override { if (parent_) { parent_->add_listener(listener); return; } observers_.insert(listener); } void remove_listener(Listener* listener) override { if (parent_) { parent_->remove_listener(listener); return; } observers_.erase(listener); } GMenuModel* model() const { return G_MENU_MODEL(menu_.get()); } void set_map(GActionMap* map); void activate(GSimpleAction* action); void add_item(std::string const& id, std::string const& label) override; GuiMenu* add_menu(std::string const& label) override; bool enable_item(std::string const& id, bool enable) override; private: explicit GtkGuiMenu(GtkGuiMenu* parent) : parent_(parent), menu_(g_menu_new()), map_(nullptr) { } void notify_item_activated(std::string const& id) { auto it = observers_.notify(); while (it.has_next()) { it.next()->item_activated(id); } } std::string add_action(std::string const& id); std::string escape(std::string const& id); std::string unescape(std::string const& action); GtkGuiMenu* const parent_; shared_gobject menu_; std::unordered_map> action_; GActionMap* map_; std::vector> submenus_; Observers observers_; }; void close_about_dialog(GtkAboutDialog* about) { // TODO: Restore dialog state gtk_widget_hide(GTK_WIDGET(about)); } class GtkGuiAbout : public virtual GuiAbout, public GtkGuiWindow { public: GtkGuiAbout(std::string const& title, std::string const& version, std::vector const& authors) : about_(GTK_ABOUT_DIALOG(gtk_about_dialog_new())) { gtk_about_dialog_set_program_name(about_, title.c_str()); gtk_about_dialog_set_version(about_, version.c_str()); std::vector tmp; tmp.reserve(authors.size() / 2); for (size_t i = 0; i < authors.size(); i += 2) { if (authors[i + 1].empty()) { tmp.push_back(authors[i]); } else { tmp.push_back(authors[i] + " <" + authors[i + 1] + ">"); } } std::vector tmp2; tmp2.reserve(tmp.size() + 1); for (auto const& str : tmp) tmp2.push_back(str.c_str()); tmp2.push_back(nullptr); gtk_about_dialog_set_authors(about_, tmp2.data()); g_signal_connect(about_, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr); g_signal_connect(about_, "response", G_CALLBACK(close_about_dialog), nullptr); } ~GtkGuiAbout() override { gtk_widget_destroy(GTK_WIDGET(about_)); } void* impl() const override { return about_; } void set_title(std::string const& title) override { GtkGuiWindow::set_title(title); } void add_listener(GuiAbout::Listener* listener) override { observers_.insert(listener); } void remove_listener(GuiAbout::Listener* listener) override { observers_.erase(listener); } private: GtkAboutDialog* about_; Observers observers_; }; struct _MainApp { GtkApplication parent; GtkGuiMain* main; }; struct _MainAppWindow { GtkApplicationWindow parent; }; G_DEFINE_TYPE(MainApp, main_app, GTK_TYPE_APPLICATION); G_DEFINE_TYPE(MainAppWindow, main_app_window, GTK_TYPE_APPLICATION_WINDOW); MainAppWindow* main_app_window_new(MainApp *app) { auto ret = MAIN_APP_WINDOW(g_object_new(MAIN_APP_WINDOW_TYPE, "application", app, nullptr)); gtk_window_set_default_size(GTK_WINDOW(ret), app->main->default_width(), app->main->default_height()); return ret; } void main_app_activate(GApplication* g_app) { auto app = MAIN_APP(g_app); auto menu = static_cast(app->main->menu()); if (menu) { menu->set_map(G_ACTION_MAP(app)); gtk_application_set_menubar(GTK_APPLICATION(app), menu->model()); } auto win = main_app_window_new(app); gtk_window_present(GTK_WINDOW(win)); } void main_app_window_open(MainAppWindow* win, GFile* file) { } void main_app_open(GApplication* app, GFile** files, gint n_files, gchar const* hint) { auto windows = gtk_application_get_windows(GTK_APPLICATION(app)); auto win = windows ? MAIN_APP_WINDOW(windows->data) : main_app_window_new(MAIN_APP(app)); for (auto i = 0; i < n_files; i++) { main_app_window_open(win, files[i]); } gtk_window_present(GTK_WINDOW(win)); } void main_app_class_init(MainAppClass* clazz) { G_APPLICATION_CLASS(clazz)->activate = main_app_activate; G_APPLICATION_CLASS(clazz)->open = main_app_open; } void main_app_window_init(MainAppWindow* app) { } void main_app_window_class_init(MainAppWindowClass* clazz) { } MainApp* main_app_new(GtkGuiMain* main) { auto ret = MAIN_APP(g_object_new(MAIN_APP_TYPE, "application-id", "org.jk.tp", "flags", G_APPLICATION_HANDLES_OPEN, nullptr)); ret->main = main; return ret; } void GtkGuiWindow::set_title(std::string const& title) { auto win = window(); if (!win) return; gtk_window_set_title(win, title.c_str()); } bool GtkGuiMain::run(int argc, char** argv) { app_.reset(main_app_new(this)); return g_application_run(G_APPLICATION(app_.get()), argc, argv); } void* GtkGuiMain::impl() const { if (!app_) return nullptr; auto windows = gtk_application_get_windows(GTK_APPLICATION(app_.get())); if (!windows) return nullptr; return windows->data; } void GtkGuiMain::set_title(std::string const& title) { title_ = title; GtkGuiWindow::set_title(title); } bool GtkGuiMain::exit() { if (!app_) { assert(false); return true; } if (!notify_about_to_exit()) return false; g_application_quit(G_APPLICATION(app_.get())); return true; } void GtkGuiMain::show(GuiWindow* window) { auto wnd = reinterpret_cast(window->impl()); if (!wnd) { assert(false); return; } gtk_window_set_transient_for(wnd, this->window()); gtk_window_present(wnd); } void GtkGuiMenu::add_item(std::string const& id, std::string const& label) { auto action = add_action(id); g_menu_append(menu_.get(), label.c_str(), action.c_str()); } GuiMenu* GtkGuiMenu::add_menu(std::string const& label) { auto ret = new GtkGuiMenu(this); g_menu_append_submenu(menu_.get(), label.c_str(), ret->model()); submenus_.emplace_back(ret); return ret; } void GtkGuiMenu::set_map(GActionMap* map) { assert(!parent_); assert(!map_); assert(map); map_ = map; for (auto const& pair : action_) { g_action_map_add_action(map_, G_ACTION(pair.second.get())); } } std::string GtkGuiMenu::escape(std::string const& action) { // TODO return action; } std::string GtkGuiMenu::unescape(std::string const& action) { return action; } void menu_item_activate(GSimpleAction* action, GVariant* parameter, gpointer* data) { auto menu = reinterpret_cast(data); menu->activate(action); } void GtkGuiMenu::activate(GSimpleAction* action) { notify_item_activated(unescape(g_action_get_name(G_ACTION(action)))); } std::string GtkGuiMenu::add_action(std::string const& id) { if (parent_) return parent_->add_action(id); std::string name = escape(id); auto action = g_simple_action_new(name.c_str(), nullptr); action_.emplace(id, action); g_signal_connect(action, "activate", G_CALLBACK(menu_item_activate), this); if (map_) g_action_map_add_action(map_, G_ACTION(action)); return "app." + name; } bool GtkGuiMenu::enable_item(std::string const& id, bool enable) { if (parent_) return parent_->enable_item(id, enable); auto pair = action_.find(id); if (pair == action_.end()) return false; g_simple_action_set_enabled(pair->second.get(), enable); return true; } } // namespace // static GuiMain* GuiMain::create(std::string const& title, uint32_t width, uint32_t height) { return new GtkGuiMain(title, width, height); } // static GuiMenu* GuiMenu::create() { return new GtkGuiMenu(); } // static GuiAbout* GuiAbout::create(std::string const& title, std::string const& version, char const* author_name, char const* author_email, ...) { std::vector authors; authors.push_back(author_name); authors.push_back(author_email); va_list args; va_start(args, author_email); while (true) { auto name = va_arg(args, char const*); if (!name) break; auto email = va_arg(args, char const*); authors.push_back(name); authors.push_back(email); } va_end(args); return new GtkGuiAbout(title, version, authors); }