GTK docking interfaces and more

By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 panthera-www.cc

View raw Download
text/x-c++ • 49.85 kiB
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
21
using plugin_entrypoint_type = void(*)(void);
22
23
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
29
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
36
// 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
42
return escaped.str();
43
}
44
45
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
49
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
53
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
67
struct HistoryEntry {
68
int id;
69
std::string url, title;
70
int64_t timestamp;
71
};
72
73
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
82
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
99
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
103
std::vector<HistoryEntry> get_history() {
104
return storage.get_all<HistoryEntry>();
105
}
106
107
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
116
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
130
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
142
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
159
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
171
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
177
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
204
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
231
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
239
this->signal_pane_shown.connect([this]() {
240
// Load the current date
241
reload_history();
242
});
243
244
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
254
// TODO: allow deleting entries
255
}
256
257
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
300
class PantheraWindow;
301
302
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
313
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
static void download_callback(WebKitNetworkSession *source, WebKitDownload *download, gpointer user_data);
321
static bool decide_destination_callback(WebKitDownload *download, gchar* suggested_filename, gpointer user_data);
322
323
protected:
324
void on_startup() override;
325
void on_activate() override;
326
void on_shutdown() override;
327
PantheraWindow *make_window();
328
329
public:
330
PantheraWww();
331
~PantheraWww();
332
friend class PantheraWindow;
333
void on_new_tab(gPanthera::ContentStack* stack, const Glib::ustring& url = "", bool focus = true, bool new_window = false);
334
void set_search_engines_from_json(const std::string& json_string);
335
static Glib::RefPtr<PantheraWww> create();
336
std::shared_ptr<gPanthera::ContentManager> get_content_manager();
337
void load_plugin(const std::string& plugin_path);
338
};
339
340
class PantheraWindow : public Gtk::ApplicationWindow {
341
private:
342
std::shared_ptr<gPanthera::LayoutManager> layout_manager;
343
Gtk::Entry *url_bar;
344
HistoryViewer *history_viewer;
345
gPanthera::ContentPage *controlled_page = nullptr;
346
gPanthera::ContentStack *controlled_stack = nullptr;
347
Glib::RefPtr<Gio::SimpleAction> back_action, forward_action, reload_action;
348
Gtk::Button *go_button;
349
350
void enable_controls() {
351
back_action->set_enabled(true);
352
forward_action->set_enabled(true);
353
reload_action->set_enabled(true);
354
url_bar->set_sensitive(true);
355
go_button->set_sensitive(true);
356
if(controlled_page) {
357
g_object_notify(G_OBJECT(controlled_page->get_child()->get_first_child()->gobj()), "uri");
358
}
359
}
360
361
void disable_controls() {
362
back_action->set_enabled(false);
363
forward_action->set_enabled(false);
364
reload_action->set_enabled(false);
365
url_bar->set_sensitive(false);
366
url_bar->set_text("");
367
go_button->set_sensitive(false);
368
}
369
370
sigc::connection moving_connection, moved_connection, closing_connection, closed_connection;
371
public:
372
friend class PantheraWww;
373
374
explicit PantheraWindow(Gtk::Application *application) : Gtk::ApplicationWindow() {
375
// There is a constructor with Glib::RefPtr<Gtk::Application>, but it is not appropriate
376
// because the window will own the application, creating a cycle
377
auto panthera = dynamic_cast<PantheraWww*>(application);
378
if(!panthera) {
379
throw std::runtime_error("Application is not a PantheraWww instance");
380
}
381
panthera->add_window(*this);
382
this->set_default_size(800, 600);
383
layout_manager = std::make_shared<gPanthera::LayoutManager>();
384
385
auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("One"), "one");
386
auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1, Gtk::Orientation::HORIZONTAL);
387
auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("Two"), "two");
388
auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2, Gtk::Orientation::VERTICAL);
389
auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
390
auto debug_button = Gtk::make_managed<Gtk::Button>("Debug");
391
auto print_history_button = Gtk::make_managed<Gtk::Button>("Print history");
392
print_history_button->signal_clicked().connect([this, panthera]() {
393
auto history = panthera->history_manager->get_history();
394
for(const auto &entry : history) {
395
std::cout << "entry " << entry.id << ", url " << entry.url << ", unix time " << entry.timestamp << std::endl;
396
// TODO: exclude redirecting URLs
397
}
398
});
399
pane_1_content->append(*debug_button);
400
pane_1_content->append(*print_history_button);
401
auto pane_1_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("help-about-symbolic"));
402
auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "debug", _("Debugging options"), pane_1_icon);
403
404
dock_stack_1->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
405
dock_stack_1->set_transition_duration(125);
406
dock_stack_1->set_expand(true);
407
dock_stack_2->set_transition_type(Gtk::StackTransitionType::SLIDE_UP_DOWN);
408
dock_stack_2->set_transition_duration(125);
409
dock_stack_2->set_expand(true);
410
411
auto outer_grid = Gtk::make_managed<Gtk::Grid>();
412
outer_grid->attach(*switcher_2, 0, 2, 1, 1);
413
outer_grid->attach(*switcher_1, 1, 3, 1, 1);
414
auto outer_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL);
415
outer_paned->set_start_child(*dock_stack_2);
416
auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
417
auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
418
std::function<bool(gPanthera::ContentPage*)> detach_handler;
419
420
detach_handler = [this](gPanthera::ContentPage *widget) {
421
auto new_stack = Gtk::make_managed<gPanthera::ContentStack>(widget->content_manager, widget->get_stack()->get_detach_handler());
422
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());
423
auto new_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(new_stack, new_switcher);
424
auto window = new gPanthera::ContentWindow(new_notebook);
425
widget->redock(new_stack);
426
window->present();
427
window->signal_close_request().connect([window]() {
428
auto webviews = collect_webviews(*window);
429
for(auto view : webviews) {
430
webkit_web_view_try_close(view);
431
}
432
if(!has_webview(*window)) {
433
// All documents have been closed safely, the window can be closed
434
return false;
435
}
436
return true;
437
}, false);
438
controlled_page = nullptr;
439
controlled_stack = nullptr;
440
disable_controls();
441
return true;
442
};
443
444
auto return_extra_child = [this, panthera](gPanthera::ContentTabBar *switcher) {
445
auto new_tab_button = Gtk::make_managed<Gtk::Button>();
446
new_tab_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("list-add-symbolic")));
447
new_tab_button->set_tooltip_text(_("New Tab"));
448
new_tab_button->signal_clicked().connect([this, switcher, panthera]() {
449
panthera->on_new_tab(switcher->get_stack());
450
});
451
return new_tab_button;
452
};
453
controlled_stack = Gtk::make_managed<gPanthera::ContentStack>(panthera->content_manager, detach_handler);
454
panthera->controlled_stack = controlled_stack;
455
auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(controlled_stack, Gtk::Orientation::HORIZONTAL, return_extra_child);
456
panthera->content_manager->add_stack(controlled_stack);
457
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); // for some reason, this has to be created
458
content->set_name("content_box");
459
auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(controlled_stack, content_stack_switcher, Gtk::PositionType::TOP);
460
content->append(*content_notebook);
461
inner_paned->set_start_child(*content);
462
inner_paned->set_end_child(*dock_stack_1);
463
outer_paned->set_end_child(*inner_paned);
464
outer_grid->attach(*outer_paned, 1, 2, 1, 1);
465
// Create the toolbar
466
auto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
467
// URL bar
468
url_bar = Gtk::make_managed<Gtk::Entry>();
469
url_bar->set_placeholder_text(_("Enter URL"));
470
url_bar->set_hexpand(true);
471
auto load_url_callback = [this, panthera]() {
472
bool has_protocol = url_bar->get_text().find("://") != std::string::npos;
473
if(!has_protocol) {
474
url_bar->set_text("http://" + url_bar->get_text());
475
}
476
if(controlled_page) {
477
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
478
webkit_web_view_load_uri(webview, url_bar->get_text().c_str());
479
}
480
}
481
};
482
// Go
483
go_button = Gtk::make_managed<Gtk::Button>(_("Go"));
484
go_button->signal_clicked().connect(load_url_callback);
485
url_bar->signal_activate().connect(load_url_callback);
486
panthera->content_manager->signal_page_operated.connect([this, panthera](gPanthera::ContentPage *page) {
487
if(!page) {
488
return;
489
}
490
if(!page->get_child()) {
491
return;
492
}
493
if(!page->get_child()->get_first_child()) {
494
return;
495
}
496
if(page->get_root() != this) {
497
// The page is in some other window
498
return;
499
}
500
controlled_page = page;
501
controlled_stack = page->get_stack();
502
panthera->controlled_stack = page->get_stack();
503
if(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
504
if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) {
505
url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
506
}
507
guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(panthera->notify_focused_callback), this);
508
std::shared_ptr<sigc::connection> control_signal_handler = std::make_shared<sigc::connection>();
509
*control_signal_handler = page->signal_control_status_changed.connect([this, page, control_signal_handler, url_update_handler](bool controlled) {
510
if(!controlled) {
511
control_signal_handler->disconnect();
512
if(page->get_child() && page->get_child()->get_first_child() && WEBKIT_IS_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
513
g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler);
514
}
515
}
516
});
517
}
518
519
enable_controls();
520
});
521
history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, panthera->history_manager);
522
history_viewer->signal_open_url.connect([this, panthera](const std::string &url) {
523
panthera->on_new_tab(nullptr, url, true);
524
});
525
// Back, forward, reload
526
auto back_button = Gtk::make_managed<Gtk::Button>();
527
back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
528
back_button->set_tooltip_text(_("Back"));
529
auto forward_button = Gtk::make_managed<Gtk::Button>();
530
forward_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
531
forward_button->set_tooltip_text(_("Forward"));
532
auto reload_button = Gtk::make_managed<Gtk::Button>();
533
reload_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("view-refresh-symbolic")));
534
reload_button->set_tooltip_text(_("Reload"));
535
536
back_action = Gio::SimpleAction::create("go_back");
537
add_action(back_action);
538
back_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
539
if(controlled_page) {
540
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
541
webkit_web_view_go_back(webview);
542
}
543
}
544
});
545
back_button->set_action_name("win.go_back");
546
547
forward_action = Gio::SimpleAction::create("go_forward");
548
add_action(forward_action);
549
forward_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
550
if(controlled_page) {
551
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
552
webkit_web_view_go_forward(webview);
553
}
554
}
555
});
556
forward_button->set_action_name("win.go_forward");
557
558
reload_action = Gio::SimpleAction::create("reload");
559
add_action(reload_action);
560
reload_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
561
if(controlled_page) {
562
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
563
webkit_web_view_reload(webview);
564
}
565
}
566
});
567
reload_button->set_action_name("win.reload");
568
569
// Search bar
570
// TODO: provide history, provide a menu button with the other engines
571
auto search_bar = Gtk::make_managed<Gtk::Entry>();
572
search_bar->set_placeholder_text(_("Search"));
573
search_bar->set_hexpand(true);
574
if(panthera->search_engines.empty()) {
575
search_bar->set_sensitive(false);
576
search_bar->set_placeholder_text(_("No search"));
577
}
578
auto search_callback = [this, search_bar, panthera]() {
579
// Create a new tab with the search results
580
panthera->on_new_tab(controlled_stack);
581
auto page = panthera->content_manager->get_last_operated_page();
582
if(page) {
583
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
584
auto search_url = panthera->default_search_engine->get_search_url(search_bar->get_text());
585
webkit_web_view_load_uri(webview, search_url.c_str());
586
}
587
}
588
};
589
if(panthera->default_search_engine) {
590
search_bar->signal_activate().connect(search_callback);
591
}
592
593
// Assemble the toolbar
594
main_toolbar->append(*back_button);
595
main_toolbar->append(*forward_button);
596
main_toolbar->append(*reload_button);
597
main_toolbar->append(*url_bar);
598
main_toolbar->append(*go_button);
599
main_toolbar->append(*search_bar);
600
if(panthera->default_search_engine) {
601
auto search_button = Gtk::make_managed<Gtk::Button>(panthera->default_search_engine->name);
602
search_button->signal_clicked().connect(search_callback);
603
main_toolbar->append(*search_button);
604
}
605
outer_grid->attach(*main_toolbar, 0, 1, 2, 1);
606
this->set_child(*outer_grid);
607
debug_button->signal_clicked().connect([this, panthera]() {
608
if(panthera->content_manager->get_last_operated_page()) {
609
std::cout << "Last operated page: " << panthera->content_manager->get_last_operated_page()->get_name() << std::endl;
610
} else {
611
std::cout << "No page operated!" << std::endl;
612
}
613
});
614
// TODO: Use the last operated page and allow opening tabs next to the last operated page using certain panes
615
// Load the existing layout, if it exists
616
std::ifstream layout_file_in("layout.json");
617
if(layout_file_in) {
618
std::string layout_json((std::istreambuf_iterator<char>(layout_file_in)), std::istreambuf_iterator<char>());
619
layout_file_in.close();
620
layout_manager->restore_json_layout(layout_json);
621
} else {
622
// Create a new layout if the file doesn't exist
623
layout_file_in.close();
624
625
dock_stack_1->add_pane(*pane_1);
626
dock_stack_1->add_pane(*history_viewer);
627
628
std::ofstream layout_file_out("layout.json");
629
layout_file_out << layout_manager->get_layout_as_json();
630
layout_file_out.close();
631
}
632
// Save the layout when changed
633
layout_manager->signal_pane_moved.connect([this, panthera](gPanthera::DockablePane *pane) {
634
std::ofstream layout_file_out("layout.json");
635
layout_file_out << layout_manager->get_layout_as_json();
636
layout_file_out.close();
637
std::cout << "Layout changed: " << layout_manager->get_layout_as_json() << std::endl;
638
});
639
set_show_menubar(true);
640
641
// As the window is initially empty, disable most actions
642
disable_controls();
643
644
moving_connection = panthera->content_manager->signal_page_moving.connect([this, panthera](gPanthera::ContentPage *page) {
645
if(page->get_root() == this) {
646
controlled_page = nullptr;
647
controlled_stack = nullptr;
648
disable_controls();
649
}
650
});
651
moved_connection = panthera->content_manager->signal_page_moved.connect([this, panthera](gPanthera::ContentPage *page) {
652
if(page->get_root() == this) {
653
panthera->content_manager->set_last_operated_page(page);
654
page->get_stack()->set_visible_child(*page);
655
enable_controls();
656
}
657
});
658
659
signal_close_request().connect([this]() {
660
auto webviews = collect_webviews(*this);
661
for(auto view : webviews) {
662
webkit_web_view_try_close(view);
663
}
664
if(!has_webview(*this)) {
665
// All documents have been closed safely, the window can be closed
666
return false;
667
}
668
return true;
669
}, false);
670
671
closing_connection = panthera->content_manager->signal_page_closing.connect([this](gPanthera::ContentPage *page) {
672
if(page->get_root() != this) {
673
return;
674
}
675
disable_controls();
676
});
677
closed_connection = panthera->content_manager->signal_page_closed.connect([this](gPanthera::ContentPage *page) {
678
if(has_webview(*this)) {
679
enable_controls();
680
}
681
});
682
}
683
~PantheraWindow() override {
684
moving_connection.disconnect();
685
moved_connection.disconnect();
686
closed_connection.disconnect();
687
closing_connection.disconnect();
688
}
689
};
690
691
void PantheraWww::load_change_callback(WebKitWebView *object, WebKitLoadEvent load_event, gpointer data) {
692
if(auto self = static_cast<PantheraWww*>(data)) {
693
if(load_event == WEBKIT_LOAD_COMMITTED) {
694
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) {
695
self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), "");
696
} else {
697
self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)),
698
webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
699
}
700
// TODO: reload visible history viewers
701
}
702
}
703
}
704
705
void PantheraWww::notify_callback(GObject *object, GParamSpec *pspec, gpointer data) {
706
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
707
return;
708
}
709
if(auto self = static_cast<PantheraWww*>(data)) {
710
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
711
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
712
if(g_strcmp0(pspec->name, "title") == 0) {
713
if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) {
714
if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) {
715
label->set_label(_("Untitled"));
716
} else {
717
label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
718
}
719
}
720
} else if(g_strcmp0(pspec->name, "favicon") == 0) {
721
// Update favicons
722
if(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) {
723
image->set_from_icon_name("image-loading-symbolic");
724
if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) {
725
gtk_image_set_from_paintable(image->gobj(), GDK_PAINTABLE(favicon));
726
}
727
}
728
}
729
}
730
}
731
}
732
733
void PantheraWww::notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) {
734
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
735
return;
736
}
737
auto this_ = static_cast<PantheraWww*>(data);
738
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
739
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
740
if(g_strcmp0(pspec->name, "uri") == 0) {
741
if(auto main_window = dynamic_cast<PantheraWindow*>(page->get_root())) {
742
if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object))) {
743
main_window->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));
744
}
745
}
746
} else if(g_strcmp0(pspec->name, "title") == 0) {
747
if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) {
748
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) {
749
window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
750
} else {
751
window->set_title(_("Untitled"));
752
}
753
}
754
}
755
}
756
}
757
758
void PantheraWww::on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
759
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
760
webkit_web_view_go_back(webview);
761
}
762
763
void PantheraWww::on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
764
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
765
webkit_web_view_go_forward(webview);
766
}
767
768
gboolean PantheraWww::on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) {
769
if(auto self = static_cast<PantheraWww*>(user_data)) {
770
if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
771
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
772
Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)));
773
if(!starts_with(url, "about:") && url.find("://") == Glib::ustring::npos) {
774
// It is a special scheme (mailto:, tel: etc.)
775
try {
776
Gio::AppInfo::launch_default_for_uri(url);
777
} catch(const Glib::Error &exception) {
778
std::cerr << "Failed to launch special URI: " << exception.what() << std::endl;
779
}
780
webkit_policy_decision_ignore(decision);
781
return true;
782
}
783
if(webkit_navigation_action_get_mouse_button(action) == 2) {
784
// Middle-click opens in a new window
785
self->on_new_tab(nullptr, url, false);
786
webkit_policy_decision_ignore(decision);
787
return true;
788
}
789
} else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
790
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
791
self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true);
792
return true;
793
}
794
}
795
return false;
796
}
797
798
void PantheraWww::close_callback(WebKitWebView *source, gpointer user_data) {
799
if(auto self = static_cast<PantheraWww*>(user_data)) {
800
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(source)));
801
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
802
if(page->get_next_sibling()) {
803
page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_next_sibling()));
804
page->get_stack()->set_visible_child(*page->get_next_sibling());
805
} else if(page->get_prev_sibling()) {
806
page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_prev_sibling()));
807
page->get_stack()->set_visible_child(*page->get_prev_sibling());
808
}
809
webkit_web_view_terminate_web_process(source);
810
if(page->content_manager->get_last_operated_page() == page) {
811
page->content_manager->set_last_operated_page(nullptr);
812
}
813
page->redock(nullptr);
814
self->content_manager->signal_page_closed.emit(page);
815
}
816
}
817
}
818
819
void PantheraWww::download_callback(WebKitNetworkSession *source, WebKitDownload *download, gpointer user_data) {
820
if(auto self = static_cast<PantheraWww*>(user_data)) {
821
// TODO: free the WebKitDownload
822
g_signal_connect(download, "decide-destination", G_CALLBACK(decide_destination_callback), self);
823
}
824
}
825
826
bool PantheraWww::decide_destination_callback(WebKitDownload *download, gchar *suggested_filename, gpointer user_data) {
827
if(auto self = static_cast<PantheraWww*>(user_data)) {
828
auto file_dialog = Gtk::FileDialog::create();
829
file_dialog->set_accept_label(_("Download"));
830
file_dialog->set_modal(false);
831
file_dialog->set_initial_name(std::string(suggested_filename));
832
if(auto *window = dynamic_cast<Gtk::Window*>(Glib::wrap_auto(G_OBJECT(gtk_widget_get_root(GTK_WIDGET(webkit_download_get_web_view(download))))))) {
833
file_dialog->save(*window, Gio::SlotAsyncReady([file_dialog, download](Glib::RefPtr<Gio::AsyncResult> &result) {
834
try {
835
auto file = file_dialog->save_finish(result);
836
webkit_download_set_destination(download, file->get_path().c_str());
837
} catch(Gtk::DialogError &e) {
838
// Download rejected
839
}
840
}));
841
}
842
//g_free(suggested_filename);
843
return true;
844
}
845
return true;
846
}
847
848
void PantheraWww::on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url, bool focus, bool new_window) {
849
if(!stack) {
850
// Find the current area
851
stack = controlled_stack;
852
}
853
Glib::ustring url_ = url;
854
if(url.empty()) {
855
url_ = new_tab_page;
856
}
857
858
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
859
gtk_widget_set_hexpand(GTK_WIDGET(webview), true);
860
gtk_widget_set_vexpand(GTK_WIDGET(webview), true);
861
auto page_content = Gtk::make_managed<Gtk::Box>();
862
gtk_box_append(page_content->gobj(), GTK_WIDGET(webview));
863
auto page_tab = new Gtk::Box();
864
page_tab->set_orientation(Gtk::Orientation::HORIZONTAL);
865
auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic"));
866
page_tab->append(*initial_icon);
867
page_tab->append(*Gtk::make_managed<Gtk::Label>(_("Untitled")));
868
page_tab->set_spacing(4);
869
auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab);
870
page->signal_close.connect([webview]() {
871
webkit_web_view_try_close(WEBKIT_WEB_VIEW(webview));
872
return true;
873
});
874
g_signal_connect(webview, "close", G_CALLBACK(close_callback), this);
875
g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), this);
876
g_signal_connect(webview, "load-changed", G_CALLBACK(load_change_callback), this);
877
g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this);
878
webkit_web_view_load_uri(webview, url_.data());
879
auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_network_session_get_default());
880
webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
881
webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
882
auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_network_session_get_default());
883
webkit_website_data_manager_set_favicons_enabled(website_data_manager, true);
884
GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
885
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8);
886
g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview);
887
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back);
888
889
GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
890
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9);
891
g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview);
892
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward);
893
894
stack->add_page(*page);
895
if(focus) {
896
stack->set_visible_child(*page);
897
content_manager->set_last_operated_page(page);
898
}
899
if(new_window) {
900
bool result = stack->signal_detach.emit(page);
901
}
902
}
903
904
PantheraWindow *PantheraWww::make_window() {
905
auto window = Gtk::make_managed<PantheraWindow>(this);
906
return window;
907
}
908
909
void PantheraWww::on_startup() {
910
bindtextdomain("panthera-www", "./locales");
911
textdomain("panthera-www");
912
content_manager = std::make_shared<gPanthera::ContentManager>();
913
Gtk::Application::on_startup();
914
// Get search engines
915
std::ifstream search_engines_file_in("search_engines.json");
916
if(search_engines_file_in) {
917
std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>());
918
search_engines_file_in.close();
919
set_search_engines_from_json(search_engines_json);
920
} else {
921
search_engines_file_in.close();
922
auto empty_json = nlohmann::json::array();
923
std::ofstream search_engines_file_out("search_engines.json");
924
search_engines_file_out << empty_json.dump(4);
925
}
926
927
// Set window titles on tab switch
928
content_manager->signal_page_operated.connect([this](gPanthera::ContentPage *page) {
929
if(!page) {
930
return;
931
}
932
if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) {
933
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) {
934
window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
935
} else {
936
window->set_title(_("Untitled"));
937
}
938
}
939
});
940
941
// Load settings
942
new_tab_page = "about:blank";
943
std::ifstream settings_file_in("settings.json");
944
if(settings_file_in) {
945
std::string settings_json((std::istreambuf_iterator<char>(settings_file_in)), std::istreambuf_iterator<char>());
946
settings_file_in.close();
947
auto json = nlohmann::json::parse(settings_json);
948
if(json.contains("ntp")) {
949
new_tab_page = json["ntp"].get<std::string>();
950
}
951
} else {
952
settings_file_in.close();
953
auto empty_json = nlohmann::json::object();
954
empty_json["ntp"] = "about:blank";
955
std::ofstream settings_file_out("settings.json");
956
settings_file_out << empty_json.dump(4);
957
settings_file_out.close();
958
}
959
960
// Load plugins
961
std::ifstream plugin_file_in("plugins.json");
962
if(plugin_file_in) {
963
std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>());
964
plugin_file_in.close();
965
auto json = nlohmann::json::parse(plugin_json);
966
for(auto &plugin : json) {
967
auto plugin_path = plugin["path"].get<std::string>();
968
//load_plugin(plugin_path);
969
}
970
} else {
971
plugin_file_in.close();
972
auto empty_json = nlohmann::json::array();
973
std::ofstream plugin_file_out("plugins.json");
974
plugin_file_out << empty_json.dump(4);
975
plugin_file_out.close();
976
}
977
history_manager = std::make_shared<HistoryManager>("history.db");
978
979
// Known bug <https://gitlab.gnome.org/GNOME/epiphany/-/issues/2714>:
980
// JS can't veto the application's accelerators using `preventDefault()`. This is
981
// not possible in WebKitGTK, or at least I can't find a way to do it.
982
983
main_menu = Gio::Menu::create();
984
// File
985
auto file_menu = Gio::Menu::create();
986
// New tab
987
auto new_tab_action = Gio::SimpleAction::create("new_tab");
988
new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
989
on_new_tab(nullptr);
990
});
991
add_action(new_tab_action);
992
set_accels_for_action("app.new_tab", {"<Primary>T"});
993
file_menu->append(_("New _Tab"), "app.new_tab");
994
// Close tab
995
auto close_tab_action = Gio::SimpleAction::create("close_tab");
996
close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
997
auto page = content_manager->get_last_operated_page();
998
if(page) {
999
page->close();
1000
}
1001
});
1002
add_action(close_tab_action);
1003
set_accels_for_action("app.close_tab", {"<Primary>W"});
1004
file_menu->append(_("_Close Tab"), "app.close_tab");
1005
// New window
1006
auto new_window_action = Gio::SimpleAction::create("new_window");
1007
new_window_action->signal_activate().connect([this](const Glib::VariantBase&) {
1008
on_activate();
1009
});
1010
add_action(new_window_action);
1011
set_accels_for_action("app.new_window", {"<Primary>N"});
1012
file_menu->append(_("_New Window"), "app.new_window");
1013
// Quit
1014
auto quit_action = Gio::SimpleAction::create("quit");
1015
quit_action->signal_activate().connect([this](const Glib::VariantBase&) {
1016
quit();
1017
});
1018
add_action(quit_action);
1019
set_accels_for_action("app.quit", {"<Primary>Q"});
1020
file_menu->append(_("_Quit"), "app.quit");
1021
1022
main_menu->append_submenu(_("_File"), file_menu);
1023
1024
// View
1025
auto view_menu = Gio::Menu::create();
1026
// Reload
1027
set_accels_for_action("win.reload", {"F5", "<Primary>R"});
1028
view_menu->append(_("_Reload"), "win.reload");
1029
1030
main_menu->append_submenu(_("_View"), view_menu);
1031
1032
// Go
1033
auto go_menu = Gio::Menu::create();
1034
// Back
1035
set_accels_for_action("win.go_back", {"<Alt>Left"});
1036
go_menu->append(_("_Back"), "win.go_back");
1037
// Forward
1038
set_accels_for_action("win.go_forward", {"<Alt>Right"});
1039
go_menu->append(_("_Forward"), "win.go_forward");
1040
1041
main_menu->append_submenu(_("_Go"), go_menu);
1042
1043
set_menubar(main_menu);
1044
1045
// Download handling
1046
g_signal_connect(webkit_network_session_get_default(), "download-started", G_CALLBACK(download_callback), this);
1047
}
1048
1049
void PantheraWww::on_activate() {
1050
auto window = make_window();
1051
window->present();
1052
}
1053
1054
void PantheraWww::on_shutdown() {
1055
Gtk::Application::on_shutdown();
1056
}
1057
1058
void PantheraWww::set_search_engines_from_json(const std::string &json_string) {
1059
auto json = nlohmann::json::parse(json_string);
1060
Glib::ustring default_search_engine_name;
1061
for(auto &engine : json) {
1062
SearchEngine search_engine;
1063
search_engine.name = engine["name"].get<std::string>();
1064
search_engine.url = engine["url"].get<std::string>();
1065
search_engines.push_back(search_engine);
1066
if(engine.contains("default") && engine["default"].get<bool>()) {
1067
default_search_engine_name = engine["name"].get<std::string>();
1068
}
1069
}
1070
for(auto &search_engine : search_engines) {
1071
if(search_engine.name == default_search_engine_name) {
1072
default_search_engine = &search_engine;
1073
break;
1074
}
1075
}
1076
}
1077
1078
PantheraWww::PantheraWww() : Gtk::Application("com.roundabout_host.roundabout.PantheraWww", Gio::Application::Flags::NONE) {
1079
}
1080
1081
Glib::RefPtr<PantheraWww> PantheraWww::create() {
1082
return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww());
1083
}
1084
1085
void PantheraWww::load_plugin(const std::string &plugin_path) {
1086
// TODO: needs to be reworked, to work with multi-window
1087
void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
1088
if(!handle) {
1089
std::cerr << "Failed to load plugin: " << dlerror() << std::endl;
1090
return;
1091
}
1092
auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init");
1093
if(!entrypoint) {
1094
std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl;
1095
dlclose(handle);
1096
return;
1097
}
1098
entrypoint();
1099
}
1100
1101
PantheraWww::~PantheraWww() = default;
1102
1103
/*
1104
extern "C" {
1105
void panthera_log(const char *message) {
1106
std::cerr << message << std::endl;
1107
}
1108
1109
void panthera_add_pane(const char *id, const char *label, GtkImage *icon, GtkWidget *pane) {
1110
auto pane_child = Glib::wrap(pane);
1111
auto icon_cc = Glib::wrap(icon);
1112
auto label_cc = Glib::ustring(label);
1113
auto id_cc = Glib::ustring(id);
1114
//auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc);
1115
// TODO: Port to multi-window
1116
//app->get_layout_manager()->add_pane(new_pane);
1117
}
1118
}
1119
*/
1120
1121
int main(int argc, char *argv[]) {
1122
gPanthera::init();
1123
1124
auto app = PantheraWww::create();
1125
1126
return app->run(argc, argv);
1127
}
1128