C++ source, ASCII text, with very long lines (382)
        
            1
            #include "gpanthera.hh" 
        
            2
            #include <gtkmm.h> 
        
            3
            #include <glibmm.h> 
        
            4
            #include <glibmm/ustring.h> 
        
            5
            #include <iostream> 
        
            6
            #include <memory> 
        
            7
            #include <libintl.h> 
        
            8
            #include <locale.h> 
        
            9
            #include <gtk/gtk.h> 
        
            10
            #include <gdk/gdk.h> 
        
            11
            #include <webkit/webkit.h> 
        
            12
            #include <fstream> 
        
            13
            #include <nlohmann/json.hpp> 
        
            14
            #include <iomanip> 
        
            15
            #include <sstream> 
        
            16
            #include <dlfcn.h> 
        
            17
            #include "external/sqlite_orm/sqlite_orm.h" 
        
            18
            //#include "plugin_api.h" 
        
            19
            #define _(STRING) gettext(STRING) 
        
            20
            using plugin_entrypoint_type = void(*)(void); 
        
            22
            std::string url_encode(const std::string &value) { 
        
            24
                // Thanks https://stackoverflow.com/a/17708801 
        
            25
                std::ostringstream escaped; 
        
            26
                escaped.fill('0'); 
        
            27
                escaped << std::hex; 
        
            28
                for(char c : value) { 
        
            30
                    // Keep alphanumeric and other accepted characters intact 
        
            31
                    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { 
        
            32
                        escaped << c; 
        
            33
                        continue; 
        
            34
                    } 
        
            35
                    // Any other characters are percent-encoded 
        
            37
                    escaped << std::uppercase; 
        
            38
                    escaped << '%' << std::setw(2) << int((unsigned char) c); 
        
            39
                    escaped << std::nouppercase; 
        
            40
                } 
        
            41
                return escaped.str(); 
        
            43
            } 
        
            44
            bool starts_with(const Glib::ustring &str, const Glib::ustring &prefix) { 
        
            46
                return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; 
        
            47
            } 
        
            48
            bool ends_with(const Glib::ustring &str, const Glib::ustring &suffix) { 
        
            50
                return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; 
        
            51
            } 
        
            52
            struct SearchEngine { 
        
            54
                Glib::ustring name; 
        
            55
                Glib::ustring url; 
        
            56
                Glib::ustring get_search_url(const std::string &query) { 
        
            57
                    auto pos = url.find("%s"); 
        
            58
                    if(pos == Glib::ustring::npos) { 
        
            59
                        throw std::runtime_error("Invalid search engine URL: missing '%s' placeholder"); 
        
            60
                    } 
        
            61
                    auto new_url = url; 
        
            62
                    new_url.replace(pos, 2, url_encode(query)); 
        
            63
                    return new_url; 
        
            64
                } 
        
            65
            }; 
        
            66
            struct HistoryEntry { 
        
            68
                int id; 
        
            69
                std::string url, title; 
        
            70
                int64_t timestamp; 
        
            71
            }; 
        
            72
            using StorageType = decltype(sqlite_orm::make_storage("", 
        
            74
                sqlite_orm::make_table("history", 
        
            75
                    sqlite_orm::make_column("id", &HistoryEntry::id, sqlite_orm::primary_key()), 
        
            76
                    sqlite_orm::make_column("url", &HistoryEntry::url), 
        
            77
                    sqlite_orm::make_column("title", &HistoryEntry::title), 
        
            78
                    sqlite_orm::make_column("timestamp", &HistoryEntry::timestamp) 
        
            79
                ) 
        
            80
            )); 
        
            81
            class HistoryManager { 
        
            83
            public: 
        
            84
                static auto make_storage(const std::string& path) { 
        
            85
                    return sqlite_orm::make_storage(path, 
        
            86
                        sqlite_orm::make_table("history", 
        
            87
                            sqlite_orm::make_column("id", &HistoryEntry::id, sqlite_orm::primary_key()), 
        
            88
                            sqlite_orm::make_column("url", &HistoryEntry::url), 
        
            89
                            sqlite_orm::make_column("title", &HistoryEntry::title), 
        
            90
                            sqlite_orm::make_column("timestamp", &HistoryEntry::timestamp) 
        
            91
                        ) 
        
            92
                    ); 
        
            93
                } 
        
            94
                StorageType storage; 
        
            95
                explicit HistoryManager(const std::string &db_path) : storage(make_storage(db_path)) { 
        
            96
                    storage.sync_schema(); 
        
            97
                } 
        
            98
                void log_url(const std::string &url, const std::string title, const int64_t timestamp = Glib::DateTime::create_now_local().to_unix()) { 
        
            100
                    storage.insert(HistoryEntry{-1, url, title, timestamp}); 
        
            101
                } 
        
            102
                std::vector<HistoryEntry> get_history() { 
        
            104
                    return storage.get_all<HistoryEntry>(); 
        
            105
                } 
        
            106
                std::vector<HistoryEntry> get_history_between(int64_t start_time, int64_t end_time) { 
        
            108
                    using namespace sqlite_orm; 
        
            109
                    return storage.get_all<HistoryEntry>( 
        
            110
                        where(c(&HistoryEntry::timestamp) >= start_time and c(&HistoryEntry::timestamp) < end_time), 
        
            111
                        order_by(&HistoryEntry::timestamp).desc() 
        
            112
                    ); 
        
            113
                } 
        
            114
            }; 
        
            115
            std::vector<WebKitWebView*> collect_webviews(Gtk::Widget const &root) { 
        
            117
                std::vector<WebKitWebView*> result; 
        
            118
                std::function<void(const Gtk::Widget&)> recurse = [&](const Gtk::Widget &widget) { 
        
            119
                    gpointer gobj = G_OBJECT(widget.gobj()); 
        
            120
                    if(WEBKIT_IS_WEB_VIEW(gobj)) { 
        
            121
                        result.push_back(WEBKIT_WEB_VIEW(gobj)); 
        
            122
                    } else for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) { 
        
            123
                        recurse(*child); 
        
            124
                    } 
        
            125
                }; 
        
            126
                recurse(root); 
        
            127
                return result; 
        
            128
            } 
        
            129
            bool has_webview(Gtk::Widget const &root) { 
        
            131
                if(WEBKIT_IS_WEB_VIEW(G_OBJECT(root.gobj()))) { 
        
            132
                    return true; 
        
            133
                } 
        
            134
                for(auto *child = root.get_first_child(); child; child = child->get_next_sibling()) { 
        
            135
                    if(has_webview(*child)) { 
        
            136
                        return true; 
        
            137
                    } 
        
            138
                } 
        
            139
                return false; 
        
            140
            } 
        
            141
            class HistoryViewer : public gPanthera::DockablePane { 
        
            143
            protected: 
        
            144
                std::shared_ptr<HistoryManager> history_manager; 
        
            145
                Gtk::Calendar *calendar; 
        
            146
                Gtk::Label *date_label; 
        
            147
                Gtk::Box *box_place; 
        
            148
            public: 
        
            149
                sigc::signal<void(const std::string &url)> signal_open_url; 
        
            150
                HistoryViewer(std::shared_ptr<gPanthera::LayoutManager> layout_manager, std::shared_ptr<HistoryManager> history_manager) : gPanthera::DockablePane(layout_manager, *Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0), "history", _("History"), Gtk::make_managed<Gtk::Image>(Gio::Icon::create("document-open-recent-symbolic"))), history_manager(std::move(history_manager)) { 
        
            151
                    auto history_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0); 
        
            152
                    auto history_button_previous = Gtk::make_managed<Gtk::Button>(); 
        
            153
                    history_button_previous->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic"))); 
        
            154
                    history_button_previous->set_tooltip_text(_("Previous day")); 
        
            155
                    auto history_button_next = Gtk::make_managed<Gtk::Button>(); 
        
            156
                    history_button_next->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic"))); 
        
            157
                    history_button_next->set_tooltip_text(_("Next day")); 
        
            158
                    date_label = Gtk::make_managed<Gtk::Label>(""); 
        
            160
                    auto date_button = Gtk::make_managed<Gtk::MenuButton>(); 
        
            161
                    date_button->set_child(*date_label); 
        
            162
                    date_button->set_tooltip_text(_("Select date")); 
        
            163
                    date_button->set_direction(Gtk::ArrowType::NONE); 
        
            164
                    auto date_popover = Gtk::make_managed<Gtk::Popover>(); 
        
            165
                    calendar = Gtk::make_managed<Gtk::Calendar>(); 
        
            166
                    date_popover->set_child(*calendar); 
        
            167
                    date_button->set_popover(*date_popover); 
        
            168
                    date_button->set_has_frame(false); 
        
            169
                    date_button->set_hexpand(true); 
        
            170
                    history_toolbar->append(*history_button_previous); 
        
            172
                    history_toolbar->append(*date_button); 
        
            173
                    history_toolbar->append(*history_button_next); 
        
            174
                    auto box = dynamic_cast<Gtk::Box*>(this->child); 
        
            175
                    box->append(*history_toolbar); 
        
            176
                    history_button_next->signal_clicked().connect([this]() { 
        
            178
                        Glib::DateTime calendar_day = calendar->get_date(); 
        
            179
                        Glib::DateTime datetime = Glib::DateTime::create_local( 
        
            180
                            calendar_day.get_year(), 
        
            181
                            calendar_day.get_month(), 
        
            182
                            calendar_day.get_day_of_month(), 
        
            183
                            0, 0, 0 
        
            184
                        ); 
        
            185
                        auto next_entries = this->history_manager->storage.get_all<HistoryEntry>( 
        
            186
                            sqlite_orm::where(sqlite_orm::c(&HistoryEntry::timestamp) >= datetime.to_unix() + 86400), 
        
            187
                            sqlite_orm::order_by(&HistoryEntry::timestamp).asc() 
        
            188
                        ); 
        
            189
                        if(next_entries.empty()) { 
        
            190
                            return; 
        
            191
                        } 
        
            192
                        auto last_entry = next_entries.front(); 
        
            193
                        Glib::DateTime next_day = Glib::DateTime::create_now_local(last_entry.timestamp); 
        
            194
                        Glib::DateTime new_date = Glib::DateTime::create_local( 
        
            195
                            next_day.get_year(), 
        
            196
                            next_day.get_month(), 
        
            197
                            next_day.get_day_of_month(), 
        
            198
                            0, 0, 0 
        
            199
                        ); 
        
            200
                        calendar->select_day(new_date); 
        
            201
                        reload_history(); 
        
            202
                    }); 
        
            203
                    history_button_previous->signal_clicked().connect([this]() { 
        
            205
                        Glib::DateTime calendar_day = calendar->get_date(); 
        
            206
                        Glib::DateTime datetime = Glib::DateTime::create_local( 
        
            207
                            calendar_day.get_year(), 
        
            208
                            calendar_day.get_month(), 
        
            209
                            calendar_day.get_day_of_month(), 
        
            210
                            0, 0, 0 
        
            211
                        ); 
        
            212
                        auto previous_entries = this->history_manager->storage.get_all<HistoryEntry>( 
        
            213
                            sqlite_orm::where(sqlite_orm::c(&HistoryEntry::timestamp) < datetime.to_unix()), 
        
            214
                            sqlite_orm::order_by(&HistoryEntry::timestamp).desc() 
        
            215
                        ); 
        
            216
                        if(previous_entries.empty()) { 
        
            217
                            return; 
        
            218
                        } 
        
            219
                        auto last_entry = previous_entries.front(); 
        
            220
                        Glib::DateTime previous_day = Glib::DateTime::create_now_local(last_entry.timestamp); 
        
            221
                        Glib::DateTime new_date = Glib::DateTime::create_local( 
        
            222
                            previous_day.get_year(), 
        
            223
                            previous_day.get_month(), 
        
            224
                            previous_day.get_day_of_month(), 
        
            225
                            0, 0, 0 
        
            226
                        ); 
        
            227
                        calendar->select_day(new_date); 
        
            228
                        reload_history(); 
        
            229
                    }); 
        
            230
                    calendar->signal_day_selected().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history)); 
        
            232
                    calendar->signal_prev_month().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history)); 
        
            233
                    calendar->signal_next_month().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history)); 
        
            234
                    calendar->signal_prev_year().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history)); 
        
            235
                    calendar->signal_next_year().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history)); 
        
            236
                    Glib::DateTime datetime = Glib::DateTime::create_now_local(); 
        
            237
                    calendar->select_day(datetime); 
        
            238
                    this->signal_pane_shown.connect([this]() { 
        
            240
                        // Load the current date 
        
            241
                        reload_history(); 
        
            242
                    }); 
        
            243
                    auto scrolled_window = Gtk::make_managed<Gtk::ScrolledWindow>(); 
        
            245
                    box_place = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); 
        
            246
                    auto viewport = Gtk::make_managed<Gtk::Viewport>(nullptr, nullptr); 
        
            247
                    viewport->set_child(*box_place); 
        
            248
                    scrolled_window->set_child(*viewport); 
        
            249
                    scrolled_window->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC); 
        
            250
                    scrolled_window->set_vexpand(true); 
        
            251
                    scrolled_window->set_hexpand(true); 
        
            252
                    box->append(*scrolled_window); 
        
            253
                    // TODO: allow deleting entries 
        
            255
                } 
        
            256
                void reload_history() { 
        
            258
                    Glib::DateTime calendar_day = calendar->get_date(); 
        
            259
                    Glib::DateTime datetime = Glib::DateTime::create_local( 
        
            260
                        calendar_day.get_year(), 
        
            261
                        calendar_day.get_month(), 
        
            262
                        calendar_day.get_day_of_month(), 
        
            263
                        0, 0, 0 
        
            264
                    ); 
        
            265
                    date_label->set_text(datetime.format("%x")); 
        
            266
                    auto box = Gtk::make_managed<Gtk::ListBox>(); 
        
            267
                    auto history_entries = history_manager->get_history_between(datetime.to_unix(), datetime.to_unix() + 86400); 
        
            268
                    for(const auto &entry : history_entries) { 
        
            269
                        auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4); 
        
            270
                        auto title = Gtk::make_managed<Gtk::Label>(entry.title.empty() ? "Untitled" : entry.title); 
        
            271
                        title->set_halign(Gtk::Align::START); 
        
            272
                        title->set_ellipsize(Pango::EllipsizeMode::MIDDLE); 
        
            273
                        auto label = Gtk::make_managed<Gtk::Label>(entry.url); 
        
            274
                        label->set_halign(Gtk::Align::START); 
        
            275
                        label->set_ellipsize(Pango::EllipsizeMode::MIDDLE); 
        
            276
                        row->append(*title); 
        
            277
                        row->append(*label); 
        
            278
                        if(auto time = Glib::DateTime::create_now_local(entry.timestamp)) { 
        
            279
                            label->set_tooltip_text(time.format("%c")); 
        
            280
                            box->append(*row); 
        
            281
                        } 
        
            282
                    } 
        
            283
                    if(box_place->get_first_child()) { 
        
            284
                        box_place->remove(*box_place->get_first_child()); 
        
            285
                    } 
        
            286
                    box->signal_row_activated().connect([this](Gtk::ListBoxRow *row) { 
        
            287
                        if(auto box_row = dynamic_cast<Gtk::Box*>(row->get_child())) { 
        
            288
                            if(auto label = dynamic_cast<Gtk::Label*>(box_row->get_last_child())) { 
        
            289
                                auto url = label->get_text(); 
        
            290
                                if(!url.empty()) { 
        
            291
                                    signal_open_url.emit(url); 
        
            292
                                } 
        
            293
                            } 
        
            294
                        } 
        
            295
                    }); 
        
            296
                    box_place->append(*box); 
        
            297
                } 
        
            298
            }; 
        
            299
            class PantheraWindow; 
        
            301
            class PantheraWww : public Gtk::Application { 
        
            303
            private: 
        
            304
                std::shared_ptr<gPanthera::ContentManager> content_manager; 
        
            305
                std::string cookie_file; 
        
            306
                std::vector<SearchEngine> search_engines; 
        
            307
                SearchEngine *default_search_engine; 
        
            308
                std::shared_ptr<HistoryManager> history_manager; 
        
            309
                Glib::RefPtr<Gio::Menu> main_menu; 
        
            310
                std::string new_tab_page; 
        
            311
                gPanthera::ContentStack *controlled_stack = nullptr; 
        
            312
                static void load_change_callback(WebKitWebView* object, WebKitLoadEvent load_event, gpointer data); 
        
            314
                static void notify_callback(GObject* object, GParamSpec* pspec, gpointer data); 
        
            315
                static void notify_focused_callback(GObject* object, GParamSpec* pspec, gpointer data); 
        
            316
                static void on_back_pressed(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data); 
        
            317
                static void on_forward_pressed(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data); 
        
            318
                static gboolean on_decide_policy(WebKitWebView* source, WebKitPolicyDecision* decision, WebKitPolicyDecisionType type, gpointer user_data); 
        
            319
                static void close_callback(WebKitWebView *source, gpointer user_data); 
        
            320
            protected: 
        
            322
                void on_startup() override; 
        
            323
                void on_activate() override; 
        
            324
                void on_shutdown() override; 
        
            325
                PantheraWindow *make_window(); 
        
            326
            public: 
        
            328
                PantheraWww(); 
        
            329
                ~PantheraWww(); 
        
            330
                friend class PantheraWindow; 
        
            331
                void on_new_tab(gPanthera::ContentStack* stack, const Glib::ustring& url = "", bool focus = true, bool new_window = false); 
        
            332
                void set_search_engines_from_json(const std::string& json_string); 
        
            333
                static Glib::RefPtr<PantheraWww> create(); 
        
            334
                std::shared_ptr<gPanthera::ContentManager> get_content_manager(); 
        
            335
                void load_plugin(const std::string& plugin_path); 
        
            336
            }; 
        
            337
            class PantheraWindow : public Gtk::ApplicationWindow { 
        
            339
            private: 
        
            340
                std::shared_ptr<gPanthera::LayoutManager> layout_manager; 
        
            341
                Gtk::Entry *url_bar; 
        
            342
                HistoryViewer *history_viewer; 
        
            343
                gPanthera::ContentPage *controlled_page = nullptr; 
        
            344
                gPanthera::ContentStack *controlled_stack = nullptr; 
        
            345
                Glib::RefPtr<Gio::SimpleAction> back_action, forward_action, reload_action; 
        
            346
                Gtk::Button *go_button; 
        
            347
                void enable_controls() { 
        
            349
                    back_action->set_enabled(true); 
        
            350
                    forward_action->set_enabled(true); 
        
            351
                    reload_action->set_enabled(true); 
        
            352
                    url_bar->set_sensitive(true); 
        
            353
                    go_button->set_sensitive(true); 
        
            354
                    if(controlled_page) { 
        
            355
                        g_object_notify(G_OBJECT(controlled_page->get_child()->get_first_child()->gobj()), "uri"); 
        
            356
                    } 
        
            357
                } 
        
            358
                void disable_controls() { 
        
            360
                    back_action->set_enabled(false); 
        
            361
                    forward_action->set_enabled(false); 
        
            362
                    reload_action->set_enabled(false); 
        
            363
                    url_bar->set_sensitive(false); 
        
            364
                    url_bar->set_text(""); 
        
            365
                    go_button->set_sensitive(false); 
        
            366
                } 
        
            367
                sigc::connection moving_connection, moved_connection, closing_connection, closed_connection; 
        
            369
            public: 
        
            370
                friend class PantheraWww; 
        
            371
                explicit PantheraWindow(Gtk::Application *application) : Gtk::ApplicationWindow() { 
        
            373
                    // There is a constructor with Glib::RefPtr<Gtk::Application>, but it is not appropriate 
        
            374
                    // because the window will own the application, creating a cycle 
        
            375
                    auto panthera = dynamic_cast<PantheraWww*>(application); 
        
            376
                    if(!panthera) { 
        
            377
                        throw std::runtime_error("Application is not a PantheraWww instance"); 
        
            378
                    } 
        
            379
                    panthera->add_window(*this); 
        
            380
                    this->set_default_size(800, 600); 
        
            381
                    layout_manager = std::make_shared<gPanthera::LayoutManager>(); 
        
            382
                    auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("One"), "one"); 
        
            384
                    auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1, Gtk::Orientation::HORIZONTAL); 
        
            385
                    auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("Two"), "two"); 
        
            386
                    auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2, Gtk::Orientation::VERTICAL); 
        
            387
                    auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); 
        
            388
                    auto debug_button = Gtk::make_managed<Gtk::Button>("Debug"); 
        
            389
                    auto print_history_button = Gtk::make_managed<Gtk::Button>("Print history"); 
        
            390
                    print_history_button->signal_clicked().connect([this, panthera]() { 
        
            391
                        auto history = panthera->history_manager->get_history(); 
        
            392
                        for(const auto &entry : history) { 
        
            393
                            std::cout << "entry " << entry.id << ", url " << entry.url << ", unix time " << entry.timestamp << std::endl; 
        
            394
                            // TODO: exclude redirecting URLs 
        
            395
                        } 
        
            396
                    }); 
        
            397
                    pane_1_content->append(*debug_button); 
        
            398
                    pane_1_content->append(*print_history_button); 
        
            399
                    auto pane_1_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("help-about-symbolic")); 
        
            400
                    auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "debug", _("Debugging options"), pane_1_icon); 
        
            401
                    dock_stack_1->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT); 
        
            403
                    dock_stack_1->set_transition_duration(125); 
        
            404
                    dock_stack_1->set_expand(true); 
        
            405
                    dock_stack_2->set_transition_type(Gtk::StackTransitionType::SLIDE_UP_DOWN); 
        
            406
                    dock_stack_2->set_transition_duration(125); 
        
            407
                    dock_stack_2->set_expand(true); 
        
            408
                    auto outer_grid = Gtk::make_managed<Gtk::Grid>(); 
        
            410
                    outer_grid->attach(*switcher_2, 0, 2, 1, 1); 
        
            411
                    outer_grid->attach(*switcher_1, 1, 3, 1, 1); 
        
            412
                    auto outer_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL); 
        
            413
                    outer_paned->set_start_child(*dock_stack_2); 
        
            414
                    auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL); 
        
            415
                    auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); 
        
            416
                    std::function<bool(gPanthera::ContentPage*)> detach_handler; 
        
            417
                    detach_handler = [this](gPanthera::ContentPage *widget) { 
        
            419
                        auto new_stack = Gtk::make_managed<gPanthera::ContentStack>(widget->content_manager, widget->get_stack()->get_detach_handler()); 
        
            420
                        auto new_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(new_stack, Gtk::Orientation::HORIZONTAL, dynamic_cast<gPanthera::ContentTabBar*>(widget->get_stack()->get_parent()->get_first_child())->get_extra_child_function()); 
        
            421
                        auto new_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(new_stack, new_switcher); 
        
            422
                        auto window = new gPanthera::ContentWindow(new_notebook); 
        
            423
                        widget->redock(new_stack); 
        
            424
                        window->present(); 
        
            425
                        window->signal_close_request().connect([window]() { 
        
            426
                            auto webviews = collect_webviews(*window); 
        
            427
                            for(auto view : webviews) { 
        
            428
                                webkit_web_view_try_close(view); 
        
            429
                            } 
        
            430
                            if(!has_webview(*window)) { 
        
            431
                                // All documents have been closed safely, the window can be closed 
        
            432
                                return false; 
        
            433
                            } 
        
            434
                            return true; 
        
            435
                        }, false); 
        
            436
                        controlled_page = nullptr; 
        
            437
                        controlled_stack = nullptr; 
        
            438
                        disable_controls(); 
        
            439
                        return true; 
        
            440
                    }; 
        
            441
                    auto return_extra_child = [this, panthera](gPanthera::ContentTabBar *switcher) { 
        
            443
                        auto new_tab_button = Gtk::make_managed<Gtk::Button>(); 
        
            444
                        new_tab_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("list-add-symbolic"))); 
        
            445
                        new_tab_button->set_tooltip_text(_("New Tab")); 
        
            446
                        new_tab_button->signal_clicked().connect([this, switcher, panthera]() { 
        
            447
                            panthera->on_new_tab(switcher->get_stack()); 
        
            448
                        }); 
        
            449
                        return new_tab_button; 
        
            450
                    }; 
        
            451
                    controlled_stack = Gtk::make_managed<gPanthera::ContentStack>(panthera->content_manager, detach_handler); 
        
            452
                    panthera->controlled_stack = controlled_stack; 
        
            453
                    auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(controlled_stack, Gtk::Orientation::HORIZONTAL, return_extra_child); 
        
            454
                    panthera->content_manager->add_stack(controlled_stack); 
        
            455
                    WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());   // for some reason, this has to be created 
        
            456
                    content->set_name("content_box"); 
        
            457
                    auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(controlled_stack, content_stack_switcher, Gtk::PositionType::TOP); 
        
            458
                    content->append(*content_notebook); 
        
            459
                    inner_paned->set_start_child(*content); 
        
            460
                    inner_paned->set_end_child(*dock_stack_1); 
        
            461
                    outer_paned->set_end_child(*inner_paned); 
        
            462
                    outer_grid->attach(*outer_paned, 1, 2, 1, 1); 
        
            463
                    // Create the toolbar 
        
            464
                    auto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8); 
        
            465
                    // URL bar 
        
            466
                    url_bar = Gtk::make_managed<Gtk::Entry>(); 
        
            467
                    url_bar->set_placeholder_text(_("Enter URL")); 
        
            468
                    url_bar->set_hexpand(true); 
        
            469
                    auto load_url_callback = [this, panthera]() { 
        
            470
                        bool has_protocol = url_bar->get_text().find("://") != std::string::npos; 
        
            471
                        if(!has_protocol) { 
        
            472
                            url_bar->set_text("http://" + url_bar->get_text()); 
        
            473
                        } 
        
            474
                        if(controlled_page) { 
        
            475
                            if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) { 
        
            476
                                webkit_web_view_load_uri(webview, url_bar->get_text().c_str()); 
        
            477
                            } 
        
            478
                        } 
        
            479
                    }; 
        
            480
                    // Go 
        
            481
                    go_button = Gtk::make_managed<Gtk::Button>(_("Go")); 
        
            482
                    go_button->signal_clicked().connect(load_url_callback); 
        
            483
                    url_bar->signal_activate().connect(load_url_callback); 
        
            484
                    panthera->content_manager->signal_page_operated.connect([this, panthera](gPanthera::ContentPage *page) { 
        
            485
                        if(!page) { 
        
            486
                            return; 
        
            487
                        } 
        
            488
                        if(!page->get_child()) { 
        
            489
                            return; 
        
            490
                        } 
        
            491
                        if(!page->get_child()->get_first_child()) { 
        
            492
                            return; 
        
            493
                        } 
        
            494
                        if(page->get_root() != this) { 
        
            495
                            // The page is in some other window 
        
            496
                            return; 
        
            497
                        } 
        
            498
                        controlled_page = page; 
        
            499
                        controlled_stack = page->get_stack(); 
        
            500
                        panthera->controlled_stack = page->get_stack(); 
        
            501
                        if(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { 
        
            502
                            if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) { 
        
            503
                                url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))); 
        
            504
                            } 
        
            505
                            guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(panthera->notify_focused_callback), this); 
        
            506
                            std::shared_ptr<sigc::connection> control_signal_handler = std::make_shared<sigc::connection>(); 
        
            507
                            *control_signal_handler = page->signal_control_status_changed.connect([this, page, control_signal_handler, url_update_handler](bool controlled) { 
        
            508
                                if(!controlled) { 
        
            509
                                    control_signal_handler->disconnect(); 
        
            510
                                    if(page->get_child() && page->get_child()->get_first_child() && WEBKIT_IS_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { 
        
            511
                                        g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler); 
        
            512
                                    } 
        
            513
                                } 
        
            514
                            }); 
        
            515
                        } 
        
            516
                        enable_controls(); 
        
            518
                    }); 
        
            519
                    history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, panthera->history_manager); 
        
            520
                    history_viewer->signal_open_url.connect([this, panthera](const std::string &url) { 
        
            521
                        panthera->on_new_tab(nullptr, url, true); 
        
            522
                    }); 
        
            523
                    // Back, forward, reload 
        
            524
                    auto back_button = Gtk::make_managed<Gtk::Button>(); 
        
            525
                    back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic"))); 
        
            526
                    back_button->set_tooltip_text(_("Back")); 
        
            527
                    auto forward_button = Gtk::make_managed<Gtk::Button>(); 
        
            528
                    forward_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic"))); 
        
            529
                    forward_button->set_tooltip_text(_("Forward")); 
        
            530
                    auto reload_button = Gtk::make_managed<Gtk::Button>(); 
        
            531
                    reload_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("view-refresh-symbolic"))); 
        
            532
                    reload_button->set_tooltip_text(_("Reload")); 
        
            533
                    back_action = Gio::SimpleAction::create("go_back"); 
        
            535
                    add_action(back_action); 
        
            536
                    back_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) { 
        
            537
                        if(controlled_page) { 
        
            538
                            if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) { 
        
            539
                                webkit_web_view_go_back(webview); 
        
            540
                            } 
        
            541
                        } 
        
            542
                    }); 
        
            543
                    back_button->set_action_name("win.go_back"); 
        
            544
                    forward_action = Gio::SimpleAction::create("go_forward"); 
        
            546
                    add_action(forward_action); 
        
            547
                    forward_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) { 
        
            548
                        if(controlled_page) { 
        
            549
                            if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) { 
        
            550
                                webkit_web_view_go_forward(webview); 
        
            551
                            } 
        
            552
                        } 
        
            553
                    }); 
        
            554
                    forward_button->set_action_name("win.go_forward"); 
        
            555
                    reload_action = Gio::SimpleAction::create("reload"); 
        
            557
                    add_action(reload_action); 
        
            558
                    reload_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) { 
        
            559
                        if(controlled_page) { 
        
            560
                            if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) { 
        
            561
                                webkit_web_view_reload(webview); 
        
            562
                            } 
        
            563
                        } 
        
            564
                    }); 
        
            565
                    reload_button->set_action_name("win.reload"); 
        
            566
                    // Search bar 
        
            568
                    // TODO: provide history, provide a menu button with the other engines 
        
            569
                    auto search_bar = Gtk::make_managed<Gtk::Entry>(); 
        
            570
                    search_bar->set_placeholder_text(_("Search")); 
        
            571
                    search_bar->set_hexpand(true); 
        
            572
                    if(panthera->search_engines.empty()) { 
        
            573
                        search_bar->set_sensitive(false); 
        
            574
                        search_bar->set_placeholder_text(_("No search")); 
        
            575
                    } 
        
            576
                    auto search_callback = [this, search_bar, panthera]() { 
        
            577
                        // Create a new tab with the search results 
        
            578
                        panthera->on_new_tab(controlled_stack); 
        
            579
                        auto page = panthera->content_manager->get_last_operated_page(); 
        
            580
                        if(page) { 
        
            581
                            if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { 
        
            582
                                auto search_url = panthera->default_search_engine->get_search_url(search_bar->get_text()); 
        
            583
                                webkit_web_view_load_uri(webview, search_url.c_str()); 
        
            584
                            } 
        
            585
                        } 
        
            586
                    }; 
        
            587
                    if(panthera->default_search_engine) { 
        
            588
                        search_bar->signal_activate().connect(search_callback); 
        
            589
                    } 
        
            590
                    // Assemble the toolbar 
        
            592
                    main_toolbar->append(*back_button); 
        
            593
                    main_toolbar->append(*forward_button); 
        
            594
                    main_toolbar->append(*reload_button); 
        
            595
                    main_toolbar->append(*url_bar); 
        
            596
                    main_toolbar->append(*go_button); 
        
            597
                    main_toolbar->append(*search_bar); 
        
            598
                    if(panthera->default_search_engine) { 
        
            599
                        auto search_button = Gtk::make_managed<Gtk::Button>(panthera->default_search_engine->name); 
        
            600
                        search_button->signal_clicked().connect(search_callback); 
        
            601
                        main_toolbar->append(*search_button); 
        
            602
                    } 
        
            603
                    outer_grid->attach(*main_toolbar, 0, 1, 2, 1); 
        
            604
                    this->set_child(*outer_grid); 
        
            605
                    debug_button->signal_clicked().connect([this, panthera]() { 
        
            606
                        if(panthera->content_manager->get_last_operated_page()) { 
        
            607
                            std::cout << "Last operated page: " << panthera->content_manager->get_last_operated_page()->get_name() << std::endl; 
        
            608
                        } else { 
        
            609
                            std::cout << "No page operated!" << std::endl; 
        
            610
                        } 
        
            611
                    }); 
        
            612
                    // TODO: Use the last operated page and allow opening tabs next to the last operated page using certain panes 
        
            613
                    // Load the existing layout, if it exists 
        
            614
                    std::ifstream layout_file_in("layout.json"); 
        
            615
                    if(layout_file_in) { 
        
            616
                        std::string layout_json((std::istreambuf_iterator<char>(layout_file_in)), std::istreambuf_iterator<char>()); 
        
            617
                        layout_file_in.close(); 
        
            618
                        layout_manager->restore_json_layout(layout_json); 
        
            619
                    } else { 
        
            620
                        // Create a new layout if the file doesn't exist 
        
            621
                        layout_file_in.close(); 
        
            622
                        dock_stack_1->add_pane(*pane_1); 
        
            624
                        dock_stack_1->add_pane(*history_viewer); 
        
            625
                        std::ofstream layout_file_out("layout.json"); 
        
            627
                        layout_file_out << layout_manager->get_layout_as_json(); 
        
            628
                        layout_file_out.close(); 
        
            629
                    } 
        
            630
                    // Save the layout when changed 
        
            631
                    layout_manager->signal_pane_moved.connect([this, panthera](gPanthera::DockablePane *pane) { 
        
            632
                        std::ofstream layout_file_out("layout.json"); 
        
            633
                        layout_file_out << layout_manager->get_layout_as_json(); 
        
            634
                        layout_file_out.close(); 
        
            635
                        std::cout << "Layout changed: " << layout_manager->get_layout_as_json() << std::endl; 
        
            636
                    }); 
        
            637
                    set_show_menubar(true); 
        
            638
                    // As the window is initially empty, disable most actions 
        
            640
                    disable_controls(); 
        
            641
                    moving_connection = panthera->content_manager->signal_page_moving.connect([this, panthera](gPanthera::ContentPage *page) { 
        
            643
                        if(page->get_root() == this) { 
        
            644
                            controlled_page = nullptr; 
        
            645
                            controlled_stack = nullptr; 
        
            646
                            disable_controls(); 
        
            647
                        } 
        
            648
                    }); 
        
            649
                    moved_connection = panthera->content_manager->signal_page_moved.connect([this, panthera](gPanthera::ContentPage *page) { 
        
            650
                        if(page->get_root() == this) { 
        
            651
                            panthera->content_manager->set_last_operated_page(page); 
        
            652
                            page->get_stack()->set_visible_child(*page); 
        
            653
                            enable_controls(); 
        
            654
                        } 
        
            655
                    }); 
        
            656
                    signal_close_request().connect([this]() { 
        
            658
                        auto webviews = collect_webviews(*this); 
        
            659
                        for(auto view : webviews) { 
        
            660
                            webkit_web_view_try_close(view); 
        
            661
                        } 
        
            662
                        if(!has_webview(*this)) { 
        
            663
                            // All documents have been closed safely, the window can be closed 
        
            664
                            return false; 
        
            665
                        } 
        
            666
                        return true; 
        
            667
                    }, false); 
        
            668
                    closing_connection = panthera->content_manager->signal_page_closing.connect([this](gPanthera::ContentPage *page) { 
        
            670
                        if(page->get_root() != this) { 
        
            671
                            return; 
        
            672
                        } 
        
            673
                        disable_controls(); 
        
            674
                    }); 
        
            675
                    closed_connection = panthera->content_manager->signal_page_closed.connect([this](gPanthera::ContentPage *page) { 
        
            676
                        if(has_webview(*this)) { 
        
            677
                            enable_controls(); 
        
            678
                        } 
        
            679
                    }); 
        
            680
                } 
        
            681
                ~PantheraWindow() override { 
        
            682
                    moving_connection.disconnect(); 
        
            683
                    moved_connection.disconnect(); 
        
            684
                    closed_connection.disconnect(); 
        
            685
                    closing_connection.disconnect(); 
        
            686
                } 
        
            687
            }; 
        
            688
            void PantheraWww::load_change_callback(WebKitWebView *object, WebKitLoadEvent load_event, gpointer data) { 
        
            690
                if(auto self = static_cast<PantheraWww*>(data)) { 
        
            691
                    if(load_event == WEBKIT_LOAD_COMMITTED) { 
        
            692
                        if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) { 
        
            693
                            self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), ""); 
        
            694
                        } else { 
        
            695
                            self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), 
        
            696
                                                           webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))); 
        
            697
                        } 
        
            698
                        // TODO: reload visible history viewers 
        
            699
                    } 
        
            700
                } 
        
            701
            } 
        
            702
            void PantheraWww::notify_callback(GObject *object, GParamSpec *pspec, gpointer data) { 
        
            704
                if(!gtk_widget_get_parent(GTK_WIDGET(object))) { 
        
            705
                    return; 
        
            706
                } 
        
            707
                if(auto self = static_cast<PantheraWww*>(data)) { 
        
            708
                    auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object))); 
        
            709
                    if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) { 
        
            710
                        if(g_strcmp0(pspec->name, "title") == 0) { 
        
            711
                            if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) { 
        
            712
                                if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) { 
        
            713
                                    label->set_label(_("Untitled")); 
        
            714
                                } else { 
        
            715
                                    label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))); 
        
            716
                                } 
        
            717
                            } 
        
            718
                        } else if(g_strcmp0(pspec->name, "favicon") == 0) { 
        
            719
                            // Update favicons 
        
            720
                            if(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) { 
        
            721
                                image->set_from_icon_name("image-loading-symbolic"); 
        
            722
                                if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) { 
        
            723
                                    gtk_image_set_from_paintable(image->gobj(), GDK_PAINTABLE(favicon)); 
        
            724
                                } 
        
            725
                            } 
        
            726
                        } 
        
            727
                    } 
        
            728
                } 
        
            729
            } 
        
            730
            void PantheraWww::notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) { 
        
            732
                if(!gtk_widget_get_parent(GTK_WIDGET(object))) { 
        
            733
                    return; 
        
            734
                } 
        
            735
                auto this_ = static_cast<PantheraWww*>(data); 
        
            736
                auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object))); 
        
            737
                if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) { 
        
            738
                    if(g_strcmp0(pspec->name, "uri") == 0) { 
        
            739
                        if(auto main_window = dynamic_cast<PantheraWindow*>(page->get_root())) { 
        
            740
                            if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object))) { 
        
            741
                                main_window->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object))); 
        
            742
                            } 
        
            743
                        } 
        
            744
                    } else if(g_strcmp0(pspec->name, "title") == 0) { 
        
            745
                        if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) { 
        
            746
                            if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) { 
        
            747
                                window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))); 
        
            748
                            } else { 
        
            749
                                window->set_title(_("Untitled")); 
        
            750
                            } 
        
            751
                        } 
        
            752
                    } 
        
            753
                } 
        
            754
            } 
        
            755
            void PantheraWww::on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) { 
        
            757
                WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data); 
        
            758
                webkit_web_view_go_back(webview); 
        
            759
            } 
        
            760
            void PantheraWww::on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) { 
        
            762
                WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data); 
        
            763
                webkit_web_view_go_forward(webview); 
        
            764
            } 
        
            765
            gboolean PantheraWww::on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) { 
        
            767
                if(auto self = static_cast<PantheraWww*>(user_data)) { 
        
            768
                    if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { 
        
            769
                        auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); 
        
            770
                        Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action))); 
        
            771
                        if(!starts_with(url, "about:") && url.find("://") == Glib::ustring::npos) { 
        
            772
                            // It is a special scheme (mailto:, tel: etc.) 
        
            773
                            try { 
        
            774
                                Gio::AppInfo::launch_default_for_uri(url); 
        
            775
                            } catch(const Glib::Error &exception) { 
        
            776
                                std::cerr << "Failed to launch special URI: " << exception.what() << std::endl; 
        
            777
                            } 
        
            778
                            webkit_policy_decision_ignore(decision); 
        
            779
                            return true; 
        
            780
                        } 
        
            781
                        if(webkit_navigation_action_get_mouse_button(action) == 2) { 
        
            782
                            // Middle-click opens in a new window 
        
            783
                            self->on_new_tab(nullptr, url, false); 
        
            784
                            webkit_policy_decision_ignore(decision); 
        
            785
                            return true; 
        
            786
                        } 
        
            787
                    } else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) { 
        
            788
                        auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); 
        
            789
                        self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true); 
        
            790
                        return true; 
        
            791
                    } 
        
            792
                } 
        
            793
                return false; 
        
            794
            } 
        
            795
            void PantheraWww::close_callback(WebKitWebView *source, gpointer user_data) { 
        
            797
                if(auto self = static_cast<PantheraWww*>(user_data)) { 
        
            798
                    auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(source))); 
        
            799
                    if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) { 
        
            800
                        if(page->get_next_sibling()) { 
        
            801
                            page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_next_sibling())); 
        
            802
                            page->get_stack()->set_visible_child(*page->get_next_sibling()); 
        
            803
                        } else if(page->get_prev_sibling()) { 
        
            804
                            page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_prev_sibling())); 
        
            805
                            page->get_stack()->set_visible_child(*page->get_prev_sibling()); 
        
            806
                        } 
        
            807
                        webkit_web_view_terminate_web_process(source); 
        
            808
                        if(page->content_manager->get_last_operated_page() == page) { 
        
            809
                            page->content_manager->set_last_operated_page(nullptr); 
        
            810
                        } 
        
            811
                        page->redock(nullptr); 
        
            812
                        self->content_manager->signal_page_closed.emit(page); 
        
            813
                    } 
        
            814
                } 
        
            815
            } 
        
            816
            void PantheraWww::on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url, bool focus, bool new_window) { 
        
            818
                if(!stack) { 
        
            819
                    // Find the current area 
        
            820
                    stack = controlled_stack; 
        
            821
                } 
        
            822
                Glib::ustring url_ = url; 
        
            823
                if(url.empty()) { 
        
            824
                    url_ = new_tab_page; 
        
            825
                } 
        
            826
                WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); 
        
            828
                gtk_widget_set_hexpand(GTK_WIDGET(webview), true); 
        
            829
                gtk_widget_set_vexpand(GTK_WIDGET(webview), true); 
        
            830
                auto page_content = Gtk::make_managed<Gtk::Box>(); 
        
            831
                gtk_box_append(page_content->gobj(), GTK_WIDGET(webview)); 
        
            832
                auto page_tab = new Gtk::Box(); 
        
            833
                page_tab->set_orientation(Gtk::Orientation::HORIZONTAL); 
        
            834
                auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic")); 
        
            835
                page_tab->append(*initial_icon); 
        
            836
                page_tab->append(*Gtk::make_managed<Gtk::Label>(_("Untitled"))); 
        
            837
                page_tab->set_spacing(4); 
        
            838
                auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab); 
        
            839
                page->signal_close.connect([webview]() { 
        
            840
                    webkit_web_view_try_close(WEBKIT_WEB_VIEW(webview)); 
        
            841
                    return true; 
        
            842
                }); 
        
            843
                g_signal_connect(webview, "close", G_CALLBACK(close_callback), this); 
        
            844
                g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), this); 
        
            845
                g_signal_connect(webview, "load-changed", G_CALLBACK(load_change_callback), this); 
        
            846
                g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this); 
        
            847
                webkit_web_view_load_uri(webview, url_.data()); 
        
            848
                auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_web_view_get_network_session(webview)); 
        
            849
                webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT); 
        
            850
                webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); 
        
            851
                auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_web_view_get_network_session(webview)); 
        
            852
                webkit_website_data_manager_set_favicons_enabled(website_data_manager, true); 
        
            853
                GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); 
        
            854
                gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8); 
        
            855
                g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview); 
        
            856
                gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back); 
        
            857
                GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); 
        
            859
                gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9); 
        
            860
                g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview); 
        
            861
                gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward); 
        
            862
                stack->add_page(*page); 
        
            864
                if(focus) { 
        
            865
                    stack->set_visible_child(*page); 
        
            866
                    content_manager->set_last_operated_page(page); 
        
            867
                } 
        
            868
                if(new_window) { 
        
            869
                    bool result = stack->signal_detach.emit(page); 
        
            870
                } 
        
            871
            } 
        
            872
            PantheraWindow *PantheraWww::make_window() { 
        
            874
                auto window = Gtk::make_managed<PantheraWindow>(this); 
        
            875
                return window; 
        
            876
            } 
        
            877
            void PantheraWww::on_startup() { 
        
            879
                bindtextdomain("panthera-www", "./locales"); 
        
            880
                textdomain("panthera-www"); 
        
            881
                content_manager = std::make_shared<gPanthera::ContentManager>(); 
        
            882
                Gtk::Application::on_startup(); 
        
            883
                // Get search engines 
        
            884
                std::ifstream search_engines_file_in("search_engines.json"); 
        
            885
                if(search_engines_file_in) { 
        
            886
                    std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>()); 
        
            887
                    search_engines_file_in.close(); 
        
            888
                    set_search_engines_from_json(search_engines_json); 
        
            889
                } else { 
        
            890
                    search_engines_file_in.close(); 
        
            891
                    auto empty_json = nlohmann::json::array(); 
        
            892
                    std::ofstream search_engines_file_out("search_engines.json"); 
        
            893
                    search_engines_file_out << empty_json.dump(4); 
        
            894
                } 
        
            895
                // Set window titles on tab switch 
        
            897
                content_manager->signal_page_operated.connect([this](gPanthera::ContentPage *page) { 
        
            898
                    if(!page) { 
        
            899
                        return; 
        
            900
                    } 
        
            901
                    if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) { 
        
            902
                        if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) { 
        
            903
                            window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))); 
        
            904
                        } else { 
        
            905
                            window->set_title(_("Untitled")); 
        
            906
                        } 
        
            907
                    } 
        
            908
                }); 
        
            909
                // Load settings 
        
            911
                new_tab_page = "about:blank"; 
        
            912
                std::ifstream settings_file_in("settings.json"); 
        
            913
                if(settings_file_in) { 
        
            914
                    std::string settings_json((std::istreambuf_iterator<char>(settings_file_in)), std::istreambuf_iterator<char>()); 
        
            915
                    settings_file_in.close(); 
        
            916
                    auto json = nlohmann::json::parse(settings_json); 
        
            917
                    if(json.contains("ntp")) { 
        
            918
                        new_tab_page = json["ntp"].get<std::string>(); 
        
            919
                    } 
        
            920
                } else { 
        
            921
                    settings_file_in.close(); 
        
            922
                    auto empty_json = nlohmann::json::object(); 
        
            923
                    empty_json["ntp"] = "about:blank"; 
        
            924
                    std::ofstream settings_file_out("settings.json"); 
        
            925
                    settings_file_out << empty_json.dump(4); 
        
            926
                    settings_file_out.close(); 
        
            927
                } 
        
            928
                // Load plugins 
        
            930
                std::ifstream plugin_file_in("plugins.json"); 
        
            931
                if(plugin_file_in) { 
        
            932
                    std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>()); 
        
            933
                    plugin_file_in.close(); 
        
            934
                    auto json = nlohmann::json::parse(plugin_json); 
        
            935
                    for(auto &plugin : json) { 
        
            936
                        auto plugin_path = plugin["path"].get<std::string>(); 
        
            937
                        //load_plugin(plugin_path); 
        
            938
                    } 
        
            939
                } else { 
        
            940
                    plugin_file_in.close(); 
        
            941
                    auto empty_json = nlohmann::json::array(); 
        
            942
                    std::ofstream plugin_file_out("plugins.json"); 
        
            943
                    plugin_file_out << empty_json.dump(4); 
        
            944
                    plugin_file_out.close(); 
        
            945
                } 
        
            946
                history_manager = std::make_shared<HistoryManager>("history.db"); 
        
            947
                // Known bug <https://gitlab.gnome.org/GNOME/epiphany/-/issues/2714>: 
        
            949
                // JS can't veto the application's accelerators using `preventDefault()`. This is 
        
            950
                // not possible in WebKitGTK, or at least I can't find a way to do it. 
        
            951
                main_menu = Gio::Menu::create(); 
        
            953
                // File 
        
            954
                auto file_menu = Gio::Menu::create(); 
        
            955
                // New tab 
        
            956
                auto new_tab_action = Gio::SimpleAction::create("new_tab"); 
        
            957
                new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            958
                    on_new_tab(nullptr); 
        
            959
                }); 
        
            960
                add_action(new_tab_action); 
        
            961
                set_accels_for_action("app.new_tab", {"<Primary>T"}); 
        
            962
                file_menu->append(_("New _Tab"), "app.new_tab"); 
        
            963
                // Close tab 
        
            964
                auto close_tab_action = Gio::SimpleAction::create("close_tab"); 
        
            965
                close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            966
                    auto page = content_manager->get_last_operated_page(); 
        
            967
                    if(page) { 
        
            968
                        page->close(); 
        
            969
                    } 
        
            970
                }); 
        
            971
                add_action(close_tab_action); 
        
            972
                set_accels_for_action("app.close_tab", {"<Primary>W"}); 
        
            973
                file_menu->append(_("_Close Tab"), "app.close_tab"); 
        
            974
                // New window 
        
            975
                auto new_window_action = Gio::SimpleAction::create("new_window"); 
        
            976
                new_window_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            977
                    on_activate(); 
        
            978
                }); 
        
            979
                add_action(new_window_action); 
        
            980
                set_accels_for_action("app.new_window", {"<Primary>N"}); 
        
            981
                file_menu->append(_("_New Window"), "app.new_window"); 
        
            982
                // Quit 
        
            983
                auto quit_action = Gio::SimpleAction::create("quit"); 
        
            984
                quit_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            985
                    quit(); 
        
            986
                }); 
        
            987
                add_action(quit_action); 
        
            988
                set_accels_for_action("app.quit", {"<Primary>Q"}); 
        
            989
                file_menu->append(_("_Quit"), "app.quit"); 
        
            990
                main_menu->append_submenu(_("_File"), file_menu); 
        
            992
                // View 
        
            994
                auto view_menu = Gio::Menu::create(); 
        
            995
                // Reload 
        
            996
                set_accels_for_action("win.reload", {"F5", "<Primary>R"}); 
        
            997
                view_menu->append(_("_Reload"), "win.reload"); 
        
            998
                main_menu->append_submenu(_("_View"), view_menu); 
        
            1000
                // Go 
        
            1002
                auto go_menu = Gio::Menu::create(); 
        
            1003
                // Back 
        
            1004
                set_accels_for_action("win.go_back", {"<Alt>Left"}); 
        
            1005
                go_menu->append(_("_Back"), "win.go_back"); 
        
            1006
                // Forward 
        
            1007
                set_accels_for_action("win.go_forward", {"<Alt>Right"}); 
        
            1008
                go_menu->append(_("_Forward"), "win.go_forward"); 
        
            1009
                main_menu->append_submenu(_("_Go"), go_menu); 
        
            1011
                set_menubar(main_menu); 
        
            1013
            } 
        
            1014
            void PantheraWww::on_activate() { 
        
            1016
                auto window = make_window(); 
        
            1017
                window->present(); 
        
            1018
            } 
        
            1019
            void PantheraWww::on_shutdown() { 
        
            1021
                Gtk::Application::on_shutdown(); 
        
            1022
            } 
        
            1023
            void PantheraWww::set_search_engines_from_json(const std::string &json_string) { 
        
            1025
                auto json = nlohmann::json::parse(json_string); 
        
            1026
                Glib::ustring default_search_engine_name; 
        
            1027
                for(auto &engine : json) { 
        
            1028
                    SearchEngine search_engine; 
        
            1029
                    search_engine.name = engine["name"].get<std::string>(); 
        
            1030
                    search_engine.url = engine["url"].get<std::string>(); 
        
            1031
                    search_engines.push_back(search_engine); 
        
            1032
                    if(engine.contains("default") && engine["default"].get<bool>()) { 
        
            1033
                        default_search_engine_name = engine["name"].get<std::string>(); 
        
            1034
                    } 
        
            1035
                } 
        
            1036
                for(auto &search_engine : search_engines) { 
        
            1037
                    if(search_engine.name == default_search_engine_name) { 
        
            1038
                        default_search_engine = &search_engine; 
        
            1039
                        break; 
        
            1040
                    } 
        
            1041
                } 
        
            1042
            } 
        
            1043
            PantheraWww::PantheraWww() : Gtk::Application("com.roundabout_host.roundabout.PantheraWww", Gio::Application::Flags::NONE) { 
        
            1045
            } 
        
            1046
            Glib::RefPtr<PantheraWww> PantheraWww::create() { 
        
            1048
                return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww()); 
        
            1049
            } 
        
            1050
            void PantheraWww::load_plugin(const std::string &plugin_path) { 
        
            1052
                // TODO: needs to be reworked, to work with multi-window 
        
            1053
                void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL); 
        
            1054
                if(!handle) { 
        
            1055
                    std::cerr << "Failed to load plugin: " << dlerror() << std::endl; 
        
            1056
                    return; 
        
            1057
                } 
        
            1058
                auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init"); 
        
            1059
                if(!entrypoint) { 
        
            1060
                    std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl; 
        
            1061
                    dlclose(handle); 
        
            1062
                    return; 
        
            1063
                } 
        
            1064
                entrypoint(); 
        
            1065
            } 
        
            1066
            PantheraWww::~PantheraWww() = default; 
        
            1068
            /* 
        
            1070
            extern "C" { 
        
            1071
                void panthera_log(const char *message) { 
        
            1072
                    std::cerr << message << std::endl; 
        
            1073
                } 
        
            1074
                void panthera_add_pane(const char *id, const char *label, GtkImage *icon, GtkWidget *pane) { 
        
            1076
                    auto pane_child = Glib::wrap(pane); 
        
            1077
                    auto icon_cc = Glib::wrap(icon); 
        
            1078
                    auto label_cc = Glib::ustring(label); 
        
            1079
                    auto id_cc = Glib::ustring(id); 
        
            1080
                    //auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc); 
        
            1081
                    // TODO: Port to multi-window 
        
            1082
                    //app->get_layout_manager()->add_pane(new_pane); 
        
            1083
                } 
        
            1084
            } 
        
            1085
            */ 
        
            1086
            int main(int argc, char *argv[]) { 
        
            1088
                gPanthera::init(); 
        
            1089
                auto app = PantheraWww::create(); 
        
            1091
                return app->run(argc, argv); 
        
            1093
            } 
        
            1094