roundabout,
created on Wednesday, 28 May 2025, 19:32:54 (1748460774),
received on Wednesday, 28 May 2025, 19:32:57 (1748460777)
Author identity: vlad <vlad.muntoiu@gmail.com>
c0e8b6372cce674f16237e61cf4b05cc4f5671a2
gpanthera.cc
@@ -59,6 +59,9 @@ namespace gPanthera {
auto new_image = Gtk::make_managed<Gtk::Image>(); new_image->set_from_icon_name(image->get_icon_name()); return new_image; } else if(image->get_storage_type() == Gtk::Image::Type::GICON) { auto new_image = Gtk::make_managed<Gtk::Image>(image->get_gicon()); return new_image;} else { return nullptr; }
@@ -197,6 +200,7 @@ namespace gPanthera {
this->header->get_style_context()->add_class("gpanthera-dock-titlebar-popout"); } this->window->present(); this->signal_pane_shown.emit();layout->signal_pane_moved.emit(this); }
@@ -246,16 +250,24 @@ namespace gPanthera {
// Hide the stack when no child is visible this->property_visible_child_name().signal_changed().connect([this]() { if(last_pane) { last_pane->signal_pane_hidden.emit(); }if(this->get_visible_child_name() == "") { this->hide(); } else { this->show(); } last_pane = dynamic_cast<DockablePane*>(this->get_visible_child()); if(last_pane) { last_pane->signal_pane_shown.emit(); }}); // Also hide when the visible child is removed this->signal_child_removed.connect([this](Gtk::Widget* const &child) { if(this->get_visible_child_name() == "") { last_pane = nullptr;this->hide(); } });
@@ -309,7 +321,9 @@ namespace gPanthera {
DockButton::DockButton(DockablePane *pane) : Gtk::ToggleButton(), pane(pane) { if(pane->get_icon()) { this->set_child(*copy_image(pane->get_icon()));if(auto icon_copy = copy_image(pane->get_icon())) { this->set_child(*icon_copy); }} this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB); this->set_tooltip_text(pane->get_label()->get_text());
gpanthera.hh
@@ -12,20 +12,22 @@ namespace gPanthera {
void init(); // Utility function to create a signal that fires when a context menu is requested sigc::signal<void(double, double)> add_context_menu(Gtk::Widget &widget); std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget); Gtk::Image *copy_image(Gtk::Image *image);class DockStack; class DockWindow; class LayoutManager; class DockablePane : public Gtk::Box { private:protected: Gtk::Widget *child;Gtk::Label label; Glib::ustring name; Gtk::Image *icon; DockStack *stack = nullptr; DockWindow *window = nullptr; std::unique_ptr<Gtk::HeaderBar> header; Gtk::Widget *child;Glib::RefPtr<Gio::SimpleActionGroup> action_group; public: DockWindow *get_window() const;
@@ -40,6 +42,8 @@ namespace gPanthera {
void redock(DockStack *stack); void pop_out(); Gtk::Widget *get_child() const; sigc::signal<void()> signal_pane_shown; sigc::signal<void()> signal_pane_hidden;}; class DockWindow : public Gtk::Window {
@@ -84,6 +88,7 @@ namespace gPanthera {
std::shared_ptr<LayoutManager> layout; Glib::ustring name; std::string id; DockablePane *last_pane = nullptr;explicit DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name, const std::string &id); void add_pane(DockablePane &child);
panthera-www.cc
@@ -57,7 +57,7 @@ struct SearchEngine {
struct HistoryEntry { int id; std::string url;std::string url, title;int64_t timestamp; };
@@ -65,33 +65,137 @@ using StorageType = decltype(sqlite_orm::make_storage("",
sqlite_orm::make_table("history", sqlite_orm::make_column("id", &HistoryEntry::id, sqlite_orm::primary_key()), sqlite_orm::make_column("url", &HistoryEntry::url), sqlite_orm::make_column("title", &HistoryEntry::title),sqlite_orm::make_column("timestamp", &HistoryEntry::timestamp) ) )); class HistoryManager { public:static auto make_storage(const std::string& path) { return sqlite_orm::make_storage(path, sqlite_orm::make_table("history", sqlite_orm::make_column("id", &HistoryEntry::id, sqlite_orm::primary_key()), sqlite_orm::make_column("url", &HistoryEntry::url), sqlite_orm::make_column("title", &HistoryEntry::title),sqlite_orm::make_column("timestamp", &HistoryEntry::timestamp) ) ); } StorageType storage; public:explicit HistoryManager(const std::string &db_path) : storage(make_storage(db_path)) { storage.sync_schema(); } void log_url(const std::string &url, const int64_t timestamp = std::chrono::system_clock::now().time_since_epoch().count()) {storage.insert(HistoryEntry{-1, url, timestamp});void log_url(const std::string &url, const std::string title, const int64_t timestamp = Glib::DateTime::create_now_local().to_unix()) { storage.insert(HistoryEntry{-1, url, title, timestamp});} std::vector<HistoryEntry> get_history() { return storage.get_all<HistoryEntry>(); } std::vector<HistoryEntry> get_history_between(int64_t start_time, int64_t end_time) { using namespace sqlite_orm; return storage.get_all<HistoryEntry>( where(c(&HistoryEntry::timestamp) >= start_time and c(&HistoryEntry::timestamp) < end_time), order_by(&HistoryEntry::timestamp).desc() ); } }; class HistoryViewer : public gPanthera::DockablePane { protected: std::shared_ptr<HistoryManager> history_manager; Gtk::Calendar *calendar; Gtk::Label *date_label; Gtk::Box *box_place; public: sigc::signal<void(const std::string &url)> signal_open_url; 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)) { auto history_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0); auto history_button_previous = Gtk::make_managed<Gtk::Button>(); history_button_previous->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic"))); history_button_previous->set_tooltip_text("Previous day"); auto history_button_next = Gtk::make_managed<Gtk::Button>(); history_button_next->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic"))); history_button_next->set_tooltip_text("Next day"); date_label = Gtk::make_managed<Gtk::Label>(""); auto date_button = Gtk::make_managed<Gtk::MenuButton>(); date_button->set_child(*date_label); date_button->set_tooltip_text("Select date"); date_button->set_direction(Gtk::ArrowType::NONE); auto date_popover = Gtk::make_managed<Gtk::Popover>(); calendar = Gtk::make_managed<Gtk::Calendar>(); date_popover->set_child(*calendar); date_button->set_popover(*date_popover); date_button->set_has_frame(false); date_button->set_hexpand(true); history_toolbar->append(*history_button_previous); history_toolbar->append(*date_button); history_toolbar->append(*history_button_next); auto box = dynamic_cast<Gtk::Box*>(this->child); box->append(*history_toolbar); calendar->signal_day_selected().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history)); Glib::DateTime datetime = Glib::DateTime::create_now_local(); calendar->select_day(datetime); this->signal_pane_shown.connect([this]() { // Load the current date reload_history(); }); box_place = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); box->append(*box_place); // TODO: make the arrows work, they should move to the previous/next day with any entries // TODO: allow deleting entries } void reload_history() { Glib::DateTime calendar_day = calendar->get_date(); Glib::DateTime datetime = Glib::DateTime::create_local( calendar_day.get_year(), calendar_day.get_month(), calendar_day.get_day_of_month(), 0, 0, 0 ); date_label->set_text(datetime.format("%x")); auto box = Gtk::make_managed<Gtk::ListBox>(); auto history_entries = history_manager->get_history_between(datetime.to_unix(), datetime.to_unix() + 86400); for(const auto &entry : history_entries) { auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4); auto title = Gtk::make_managed<Gtk::Label>(entry.title.empty() ? "Untitled" : entry.title); title->set_halign(Gtk::Align::START); title->set_ellipsize(Pango::EllipsizeMode::MIDDLE); auto label = Gtk::make_managed<Gtk::Label>(entry.url); label->set_halign(Gtk::Align::START); label->set_ellipsize(Pango::EllipsizeMode::MIDDLE); row->append(*title); row->append(*label); if(auto time = Glib::DateTime::create_now_local(entry.timestamp)) { label->set_tooltip_text(time.format("%c")); box->append(*row); } } if(box_place->get_first_child()) { box_place->remove(*box_place->get_first_child()); } box->signal_row_activated().connect([this](Gtk::ListBoxRow *row) { if(auto box_row = dynamic_cast<Gtk::Box*>(row->get_child())) { if(auto label = dynamic_cast<Gtk::Label*>(box_row->get_last_child())) { auto url = label->get_text(); if(!url.empty()) { signal_open_url.emit(url); } } } }); box_place->append(*box); }}; class PantheraWww : public Gtk::Application {
@@ -104,6 +208,7 @@ protected:
std::vector<SearchEngine> search_engines; SearchEngine *default_search_engine = nullptr; std::shared_ptr<HistoryManager> history_manager; HistoryViewer *history_viewer = nullptr;static void notify_callback(GObject *object, GParamSpec *pspec, gpointer data) { if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
@@ -129,7 +234,15 @@ protected:
} } } else if(g_strcmp0(pspec->name, "uri") == 0) { self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) { self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), ""); } else { self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))); } if(self->history_viewer->get_visible()) { self->history_viewer->reload_history(); }} } }
@@ -251,21 +364,8 @@ protected:
}); pane_1_content->append(*debug_button); pane_1_content->append(*print_history_button); auto pane_2_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);auto test_notebook = Gtk::make_managed<Gtk::Notebook>();test_notebook->append_page(*Gtk::make_managed<Gtk::Label>("Test 1"), *Gtk::make_managed<Gtk::Label>("Test 1"));pane_2_content->append(*test_notebook);auto pane_3_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);pane_3_content->append(*Gtk::make_managed<Gtk::Label>("Pane 3 content"));auto pane_1_icon = Gtk::make_managed<Gtk::Image>();pane_1_icon->set_from_icon_name("go-home-symbolic");auto pane_2_icon = Gtk::make_managed<Gtk::Image>();pane_2_icon->set_from_icon_name("folder-symbolic");auto pane_3_icon = Gtk::make_managed<Gtk::Image>();pane_3_icon->set_from_icon_name("network-transmit-receive-symbolic");auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "pane1", "Pane 1", pane_1_icon);auto pane_2 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_2_content, "pane2", "Pane 2", pane_2_icon);auto pane_3 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_3_content, "pane3", "Pane 3", pane_3_icon);auto pane_1_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("help-about-symbolic")); auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "debug", "Debugging options", pane_1_icon);dock_stack_1->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT); dock_stack_1->set_transition_duration(125);
@@ -373,6 +473,10 @@ protected:
}); }); history_manager = std::make_shared<HistoryManager>("history.db"); history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, history_manager); history_viewer->signal_open_url.connect([this](const std::string &url) { on_new_tab(nullptr, url, true); });// Back, forward, reload auto back_button = Gtk::make_managed<Gtk::Button>(); back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
@@ -467,8 +571,7 @@ protected:
layout_file_in.close(); dock_stack_1->add_pane(*pane_1); dock_stack_1->add_pane(*pane_3);dock_stack_2->add_pane(*pane_2);dock_stack_1->add_pane(*history_viewer);std::ofstream layout_file_out("layout.json"); layout_file_out << layout_manager->get_layout_as_json();