mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 16:45:27 +00:00
implement separated dialogs for the Bookmarks and History menu items
This commit is contained in:
parent
e548efc93f
commit
ebb38008e1
14 changed files with 378 additions and 171 deletions
|
|
@ -34,7 +34,6 @@ anyhow = "1.0.97"
|
||||||
async-channel = "2.5.0"
|
async-channel = "2.5.0"
|
||||||
ggemini = "0.19.0"
|
ggemini = "0.19.0"
|
||||||
ggemtext = "0.7.0"
|
ggemtext = "0.7.0"
|
||||||
indexmap = "2.7.0"
|
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
# libspelling = "0.4.0"
|
# libspelling = "0.4.0"
|
||||||
maxminddb = "0.26.0"
|
maxminddb = "0.26.0"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
mod about;
|
mod about;
|
||||||
mod action;
|
mod action;
|
||||||
|
mod bookmarks;
|
||||||
mod database;
|
mod database;
|
||||||
|
mod history;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
mod widget;
|
mod widget;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
use about::About;
|
use about::About;
|
||||||
use action::Action;
|
use action::Action;
|
||||||
|
use bookmarks::Bookmarks;
|
||||||
|
use history::History;
|
||||||
use proxy::Proxy;
|
use proxy::Proxy;
|
||||||
use widget::Widget;
|
use widget::Widget;
|
||||||
use window::Window;
|
use window::Window;
|
||||||
|
|
@ -93,6 +97,22 @@ impl Browser {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
action.history.connect_activate({
|
||||||
|
let profile = profile.clone();
|
||||||
|
let window = window.clone();
|
||||||
|
move || {
|
||||||
|
PreferencesDialog::history(&window.action, &profile).present(Some(&window.g_box))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
action.bookmarks.connect_activate({
|
||||||
|
let profile = profile.clone();
|
||||||
|
let window = window.clone();
|
||||||
|
move || {
|
||||||
|
PreferencesDialog::bookmarks(&window.action, &profile).present(Some(&window.g_box))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
action.proxy.connect_activate({
|
action.proxy.connect_activate({
|
||||||
let profile = profile.clone();
|
let profile = profile.clone();
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
mod about;
|
mod about;
|
||||||
|
mod bookmarks;
|
||||||
mod close;
|
mod close;
|
||||||
mod debug;
|
mod debug;
|
||||||
|
mod history;
|
||||||
mod profile;
|
mod profile;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
|
|
||||||
use about::About;
|
use about::About;
|
||||||
|
use bookmarks::Bookmarks;
|
||||||
use close::Close;
|
use close::Close;
|
||||||
use debug::Debug;
|
use debug::Debug;
|
||||||
|
use history::History;
|
||||||
use profile::Profile;
|
use profile::Profile;
|
||||||
use proxy::Proxy;
|
use proxy::Proxy;
|
||||||
|
|
||||||
|
|
@ -21,8 +25,10 @@ use std::rc::Rc;
|
||||||
pub struct Action {
|
pub struct Action {
|
||||||
// Actions
|
// Actions
|
||||||
pub about: Rc<About>,
|
pub about: Rc<About>,
|
||||||
|
pub bookmarks: Rc<Bookmarks>,
|
||||||
pub close: Rc<Close>,
|
pub close: Rc<Close>,
|
||||||
pub debug: Rc<Debug>,
|
pub debug: Rc<Debug>,
|
||||||
|
pub history: Rc<History>,
|
||||||
pub profile: Rc<Profile>,
|
pub profile: Rc<Profile>,
|
||||||
pub proxy: Rc<Proxy>,
|
pub proxy: Rc<Proxy>,
|
||||||
// Group
|
// Group
|
||||||
|
|
@ -43,8 +49,10 @@ impl Action {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// Init actions
|
// Init actions
|
||||||
let about = Rc::new(About::new());
|
let about = Rc::new(About::new());
|
||||||
|
let bookmarks = Rc::new(Bookmarks::new());
|
||||||
let close = Rc::new(Close::new());
|
let close = Rc::new(Close::new());
|
||||||
let debug = Rc::new(Debug::new());
|
let debug = Rc::new(Debug::new());
|
||||||
|
let history = Rc::new(History::new());
|
||||||
let profile = Rc::new(Profile::new());
|
let profile = Rc::new(Profile::new());
|
||||||
let proxy = Rc::new(Proxy::new());
|
let proxy = Rc::new(Proxy::new());
|
||||||
|
|
||||||
|
|
@ -56,18 +64,22 @@ impl Action {
|
||||||
|
|
||||||
// Add action to given group
|
// Add action to given group
|
||||||
simple_action_group.add_action(&about.simple_action);
|
simple_action_group.add_action(&about.simple_action);
|
||||||
|
simple_action_group.add_action(&bookmarks.simple_action);
|
||||||
simple_action_group.add_action(&close.simple_action);
|
simple_action_group.add_action(&close.simple_action);
|
||||||
simple_action_group.add_action(&debug.simple_action);
|
simple_action_group.add_action(&debug.simple_action);
|
||||||
|
simple_action_group.add_action(&history.simple_action);
|
||||||
simple_action_group.add_action(&profile.simple_action);
|
simple_action_group.add_action(&profile.simple_action);
|
||||||
simple_action_group.add_action(&proxy.simple_action);
|
simple_action_group.add_action(&proxy.simple_action);
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
Self {
|
Self {
|
||||||
about,
|
about,
|
||||||
|
bookmarks,
|
||||||
close,
|
close,
|
||||||
debug,
|
debug,
|
||||||
profile,
|
profile,
|
||||||
proxy,
|
proxy,
|
||||||
|
history,
|
||||||
id,
|
id,
|
||||||
simple_action_group,
|
simple_action_group,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/app/browser/action/bookmarks.rs
Normal file
31
src/app/browser/action/bookmarks.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use gtk::{gio::SimpleAction, glib::uuid_string_random};
|
||||||
|
|
||||||
|
/// [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) wrapper for `Profile` action of `Browser` group
|
||||||
|
pub struct Bookmarks {
|
||||||
|
pub simple_action: SimpleAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Bookmarks {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bookmarks {
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/// Create new `Self`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
simple_action: SimpleAction::new(&uuid_string_random(), None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
/// Define callback function for
|
||||||
|
/// [SimpleAction::activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal
|
||||||
|
pub fn connect_activate(&self, callback: impl Fn() + 'static) {
|
||||||
|
self.simple_action.connect_activate(move |_, _| callback());
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/app/browser/action/history.rs
Normal file
31
src/app/browser/action/history.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use gtk::{gio::SimpleAction, glib::uuid_string_random};
|
||||||
|
|
||||||
|
/// [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) wrapper for `Profile` action of `Browser` group
|
||||||
|
pub struct History {
|
||||||
|
pub simple_action: SimpleAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for History {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl History {
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/// Create new `Self`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
simple_action: SimpleAction::new(&uuid_string_random(), None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
/// Define callback function for
|
||||||
|
/// [SimpleAction::activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal
|
||||||
|
pub fn connect_activate(&self, callback: impl Fn() + 'static) {
|
||||||
|
self.simple_action.connect_activate(move |_, _| callback());
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/app/browser/bookmarks.rs
Normal file
117
src/app/browser/bookmarks.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
//! Browser bookmarks dialog
|
||||||
|
|
||||||
|
use super::Profile;
|
||||||
|
use crate::app::browser::window::action::{Action as WindowAction, Position};
|
||||||
|
use adw::{
|
||||||
|
ActionRow, PreferencesGroup, PreferencesPage,
|
||||||
|
prelude::{
|
||||||
|
ActionRowExt, AdwDialogExt, ExpanderRowExt, PreferencesDialogExt, PreferencesGroupExt,
|
||||||
|
PreferencesPageExt,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use gtk::glib::{DateTime, GString, Uri, UriFlags};
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
struct Record {
|
||||||
|
time: DateTime,
|
||||||
|
request: String,
|
||||||
|
title: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Bookmarks {
|
||||||
|
fn bookmarks(window_action: &Rc<WindowAction>, profile: &Rc<Profile>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bookmarks for adw::PreferencesDialog {
|
||||||
|
fn bookmarks(window_action: &Rc<WindowAction>, profile: &Rc<Profile>) -> Self {
|
||||||
|
let mut index: HashMap<GString, Vec<Record>> = HashMap::new();
|
||||||
|
for bookmark in profile.bookmark.recent(None) {
|
||||||
|
match Uri::parse(&bookmark.request, UriFlags::NONE) {
|
||||||
|
Ok(uri) => index
|
||||||
|
.entry(match uri.host() {
|
||||||
|
Some(host) => host,
|
||||||
|
None => uri.to_str(),
|
||||||
|
})
|
||||||
|
.or_default()
|
||||||
|
.push(Record {
|
||||||
|
request: bookmark.request,
|
||||||
|
time: bookmark.time,
|
||||||
|
title: bookmark.title,
|
||||||
|
}),
|
||||||
|
Err(_) => continue, // @TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let d = adw::PreferencesDialog::builder()
|
||||||
|
.search_enabled(true)
|
||||||
|
.title("Bookmarks")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
d.add(&{
|
||||||
|
let p = PreferencesPage::builder()
|
||||||
|
.icon_name("document-open-recent-symbolic")
|
||||||
|
.title("All")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (group, records) in index {
|
||||||
|
p.add(&{
|
||||||
|
let g = PreferencesGroup::new();
|
||||||
|
g.add(&{
|
||||||
|
let e = adw::ExpanderRow::builder()
|
||||||
|
.enable_expansion(true)
|
||||||
|
.expanded(false)
|
||||||
|
.subtitle(
|
||||||
|
records
|
||||||
|
.iter()
|
||||||
|
.max_by_key(|r| r.time.to_unix())
|
||||||
|
.unwrap()
|
||||||
|
.time
|
||||||
|
.format_iso8601()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.title_selectable(true)
|
||||||
|
.title(group)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for record in records {
|
||||||
|
e.add_row(&{
|
||||||
|
let a = ActionRow::builder()
|
||||||
|
.activatable(true)
|
||||||
|
// @TODO use button widget to open the links on click
|
||||||
|
//.title_selectable(true)
|
||||||
|
.title(match record.title {
|
||||||
|
Some(title) => title,
|
||||||
|
None => record.time.format_iso8601().unwrap().to_string(),
|
||||||
|
})
|
||||||
|
.subtitle_selectable(true)
|
||||||
|
.subtitle(&record.request)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
a.connect_activated({
|
||||||
|
let a = window_action.clone();
|
||||||
|
let d = d.clone();
|
||||||
|
move |_| {
|
||||||
|
a.append.activate_stateful_once(
|
||||||
|
Position::After,
|
||||||
|
Some(record.request.clone()),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
e
|
||||||
|
});
|
||||||
|
g
|
||||||
|
});
|
||||||
|
}
|
||||||
|
p
|
||||||
|
});
|
||||||
|
d
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/app/browser/history.rs
Normal file
132
src/app/browser/history.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
//! Browser history dialog
|
||||||
|
|
||||||
|
use super::Profile;
|
||||||
|
use crate::app::browser::window::action::{Action as WindowAction, Position};
|
||||||
|
use adw::{
|
||||||
|
ActionRow, PreferencesGroup, PreferencesPage,
|
||||||
|
prelude::{
|
||||||
|
ActionRowExt, AdwDialogExt, ExpanderRowExt, PreferencesDialogExt, PreferencesGroupExt,
|
||||||
|
PreferencesPageExt,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use gtk::glib::{DateTime, GString, Uri, UriFlags, gformat};
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
pub struct Event {
|
||||||
|
pub time: DateTime,
|
||||||
|
pub count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Record {
|
||||||
|
event: Event,
|
||||||
|
request: GString,
|
||||||
|
title: Option<GString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait History {
|
||||||
|
fn history(window_action: &Rc<WindowAction>, profile: &Rc<Profile>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl History for adw::PreferencesDialog {
|
||||||
|
fn history(window_action: &Rc<WindowAction>, profile: &Rc<Profile>) -> Self {
|
||||||
|
let mut visited: HashMap<GString, Vec<Record>> = HashMap::new();
|
||||||
|
// @TODO recently closed
|
||||||
|
|
||||||
|
for history in profile.history.recently_opened(None) {
|
||||||
|
match Uri::parse(&history.request, UriFlags::NONE) {
|
||||||
|
Ok(uri) => visited
|
||||||
|
.entry(match uri.host() {
|
||||||
|
Some(host) => host,
|
||||||
|
None => uri.to_str(),
|
||||||
|
})
|
||||||
|
.or_default()
|
||||||
|
.push(Record {
|
||||||
|
event: Event {
|
||||||
|
time: history.opened.time,
|
||||||
|
count: history.opened.count,
|
||||||
|
},
|
||||||
|
request: history.request,
|
||||||
|
title: history.title,
|
||||||
|
}),
|
||||||
|
Err(_) => continue, // @TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let d = adw::PreferencesDialog::builder()
|
||||||
|
.search_enabled(true)
|
||||||
|
.title("History")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
d.add(&{
|
||||||
|
let p = PreferencesPage::builder()
|
||||||
|
.icon_name("document-open-recent-symbolic")
|
||||||
|
.title("Recently visited")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (group, records) in visited {
|
||||||
|
p.add(&{
|
||||||
|
let g = PreferencesGroup::new();
|
||||||
|
g.add(&{
|
||||||
|
let e = adw::ExpanderRow::builder()
|
||||||
|
.enable_expansion(true)
|
||||||
|
.expanded(false)
|
||||||
|
.subtitle(
|
||||||
|
records
|
||||||
|
.iter()
|
||||||
|
.max_by_key(|r| r.event.time.to_unix())
|
||||||
|
.unwrap()
|
||||||
|
.event
|
||||||
|
.time
|
||||||
|
.format_iso8601()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.title_selectable(true)
|
||||||
|
.title(group)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for record in records {
|
||||||
|
e.add_row(&{
|
||||||
|
let a = ActionRow::builder()
|
||||||
|
.activatable(true)
|
||||||
|
// @TODO use button widget to open the links on click
|
||||||
|
//.title_selectable(true)
|
||||||
|
.title(match record.title {
|
||||||
|
Some(title) => title,
|
||||||
|
None => gformat!(
|
||||||
|
"{} ({})",
|
||||||
|
record.event.time.format_iso8601().unwrap(),
|
||||||
|
record.event.count
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.subtitle_selectable(true)
|
||||||
|
.subtitle(&*record.request)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
a.connect_activated({
|
||||||
|
let a = window_action.clone();
|
||||||
|
let d = d.clone();
|
||||||
|
move |_| {
|
||||||
|
a.append.activate_stateful_once(
|
||||||
|
Position::After,
|
||||||
|
Some(record.request.to_string()),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
e
|
||||||
|
});
|
||||||
|
g
|
||||||
|
});
|
||||||
|
}
|
||||||
|
p
|
||||||
|
});
|
||||||
|
d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
mod action;
|
pub mod action;
|
||||||
mod database;
|
mod database;
|
||||||
mod header;
|
mod header;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
@ -119,7 +119,6 @@ impl Window {
|
||||||
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
||||||
g_box.append(&ToolbarView::header(
|
g_box.append(&ToolbarView::header(
|
||||||
(browser_action, &action),
|
(browser_action, &action),
|
||||||
profile,
|
|
||||||
&tab.tab_view,
|
&tab.tab_view,
|
||||||
));
|
));
|
||||||
g_box.append(&tab.tab_view);
|
g_box.append(&tab.tab_view);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
mod bar;
|
mod bar;
|
||||||
|
|
||||||
use super::{Action as WindowAction, BrowserAction, Profile};
|
use super::{Action as WindowAction, BrowserAction};
|
||||||
use adw::{TabView, ToolbarView};
|
use adw::{TabView, ToolbarView};
|
||||||
use bar::Bar;
|
use bar::Bar;
|
||||||
use gtk::Box;
|
use gtk::Box;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub trait Header {
|
pub trait Header {
|
||||||
fn header(
|
fn header(action: (&Rc<BrowserAction>, &Rc<WindowAction>), tab_view: &TabView) -> Self;
|
||||||
action: (&Rc<BrowserAction>, &Rc<WindowAction>),
|
|
||||||
profile: &Rc<Profile>,
|
|
||||||
tab_view: &TabView,
|
|
||||||
) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header for ToolbarView {
|
impl Header for ToolbarView {
|
||||||
|
|
@ -20,16 +16,11 @@ impl Header for ToolbarView {
|
||||||
/// Build new `Self`
|
/// Build new `Self`
|
||||||
fn header(
|
fn header(
|
||||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||||
profile: &Rc<Profile>,
|
|
||||||
tab_view: &TabView,
|
tab_view: &TabView,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let toolbar_view = ToolbarView::builder().build();
|
let toolbar_view = ToolbarView::builder().build();
|
||||||
|
|
||||||
toolbar_view.add_top_bar(&Box::bar(
|
toolbar_view.add_top_bar(&Box::bar((browser_action, window_action), tab_view));
|
||||||
(browser_action, window_action),
|
|
||||||
profile,
|
|
||||||
tab_view,
|
|
||||||
));
|
|
||||||
|
|
||||||
toolbar_view
|
toolbar_view
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,13 @@ use control::Control;
|
||||||
use menu::Menu;
|
use menu::Menu;
|
||||||
use tab::Tab;
|
use tab::Tab;
|
||||||
|
|
||||||
use super::{BrowserAction, Profile, WindowAction};
|
use super::{BrowserAction, WindowAction};
|
||||||
use adw::{TabBar, TabView};
|
use adw::{TabBar, TabView};
|
||||||
use gtk::{Box, MenuButton, Orientation, prelude::BoxExt};
|
use gtk::{Box, MenuButton, Orientation, prelude::BoxExt};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub trait Bar {
|
pub trait Bar {
|
||||||
fn bar(
|
fn bar(action: (&Rc<BrowserAction>, &Rc<WindowAction>), view: &TabView) -> Self;
|
||||||
action: (&Rc<BrowserAction>, &Rc<WindowAction>),
|
|
||||||
profile: &Rc<Profile>,
|
|
||||||
view: &TabView,
|
|
||||||
) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bar for Box {
|
impl Bar for Box {
|
||||||
|
|
@ -25,7 +21,6 @@ impl Bar for Box {
|
||||||
/// Build new `Self`
|
/// Build new `Self`
|
||||||
fn bar(
|
fn bar(
|
||||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||||
profile: &Rc<Profile>,
|
|
||||||
view: &TabView,
|
view: &TabView,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let g_box = Box::builder()
|
let g_box = Box::builder()
|
||||||
|
|
@ -34,7 +29,7 @@ impl Bar for Box {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
g_box.append(&TabBar::tab(window_action, view));
|
g_box.append(&TabBar::tab(window_action, view));
|
||||||
g_box.append(&MenuButton::menu((browser_action, window_action), profile));
|
g_box.append(&MenuButton::menu((browser_action, window_action)));
|
||||||
g_box.append(&Control::new().window_controls);
|
g_box.append(&Control::new().window_controls);
|
||||||
g_box
|
g_box
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
use super::{BrowserAction, Profile, WindowAction};
|
use super::{BrowserAction, WindowAction};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
Align, MenuButton,
|
Align, MenuButton,
|
||||||
gio::{self},
|
gio::{self},
|
||||||
glib::{GString, Uri, UriFlags},
|
prelude::ActionExt,
|
||||||
prelude::{ActionExt, ToVariant},
|
|
||||||
};
|
};
|
||||||
use indexmap::IndexMap;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
// Config options
|
// Config options
|
||||||
|
|
||||||
const LABEL_MAX_LENGTH: usize = 28;
|
|
||||||
pub trait Menu {
|
pub trait Menu {
|
||||||
fn menu(action: (&Rc<BrowserAction>, &Rc<WindowAction>), profile: &Rc<Profile>) -> Self;
|
fn menu(actions: (&Rc<BrowserAction>, &Rc<WindowAction>)) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip] // @TODO template builder?
|
#[rustfmt::skip] // @TODO template builder?
|
||||||
|
|
@ -22,7 +19,6 @@ impl Menu for MenuButton {
|
||||||
/// Build new `Self`
|
/// Build new `Self`
|
||||||
fn menu(
|
fn menu(
|
||||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||||
profile: &Rc<Profile>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Main
|
// Main
|
||||||
let main = gio::Menu::new();
|
let main = gio::Menu::new();
|
||||||
|
|
@ -116,7 +112,7 @@ impl Menu for MenuButton {
|
||||||
window_action.history_forward.simple_action.name()
|
window_action.history_forward.simple_action.name()
|
||||||
)));
|
)));
|
||||||
|
|
||||||
main_page_navigation.append_submenu(Some("Navigation history"), &main_page_navigation_history);
|
main_page_navigation.append_submenu(Some("Navigation"), &main_page_navigation_history);
|
||||||
|
|
||||||
main_page.append_section(None, &main_page_navigation);
|
main_page.append_section(None, &main_page_navigation);
|
||||||
|
|
||||||
|
|
@ -139,29 +135,22 @@ impl Menu for MenuButton {
|
||||||
|
|
||||||
main.append_submenu(Some("Page"), &main_page);
|
main.append_submenu(Some("Page"), &main_page);
|
||||||
|
|
||||||
// Main > Bookmark
|
// Main > Bookmarks
|
||||||
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
main.append(Some("Bookmarks"), Some(&format!(
|
||||||
let main_bookmarks = gio::Menu::new();
|
"{}.{}",
|
||||||
|
browser_action.id,
|
||||||
main.append_submenu(Some("Bookmarks"), &main_bookmarks);
|
browser_action.bookmarks.simple_action.name()
|
||||||
|
)));
|
||||||
|
|
||||||
// Main > History
|
// Main > History
|
||||||
let main_history = gio::Menu::new();
|
main.append(Some("History"), Some(&format!(
|
||||||
|
"{}.{}",
|
||||||
// Main > History > Recently closed
|
browser_action.id,
|
||||||
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
browser_action.history.simple_action.name()
|
||||||
let main_history_tab = gio::Menu::new();
|
)));
|
||||||
main_history.append_submenu(Some("Recently closed"), &main_history_tab);
|
|
||||||
|
|
||||||
// Main > History > Recent requests
|
|
||||||
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
|
||||||
let main_history_request = gio::Menu::new();
|
|
||||||
main_history.append_section(None, &main_history_request);
|
|
||||||
|
|
||||||
main.append_submenu(Some("History"), &main_history);
|
|
||||||
|
|
||||||
// Main > Proxy
|
// Main > Proxy
|
||||||
main.append(Some("Proxy settings"), Some(&format!(
|
main.append(Some("Proxy"), Some(&format!(
|
||||||
"{}.{}",
|
"{}.{}",
|
||||||
browser_action.id,
|
browser_action.id,
|
||||||
browser_action.proxy.simple_action.name()
|
browser_action.proxy.simple_action.name()
|
||||||
|
|
@ -198,124 +187,12 @@ impl Menu for MenuButton {
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// Init main widget
|
// Init main widget
|
||||||
let menu_button = MenuButton::builder()
|
MenuButton::builder()
|
||||||
.css_classes(["flat"])
|
.css_classes(["flat"])
|
||||||
.icon_name("open-menu-symbolic")
|
.icon_name("open-menu-symbolic")
|
||||||
.menu_model(&main)
|
.menu_model(&main)
|
||||||
.tooltip_text("Menu")
|
.tooltip_text("Menu")
|
||||||
.valign(Align::Center)
|
.valign(Align::Center)
|
||||||
.build();
|
.build()
|
||||||
|
|
||||||
// Generate dynamical menu items
|
|
||||||
menu_button.set_create_popup_func({
|
|
||||||
let profile = profile.clone();
|
|
||||||
let main_bookmarks = main_bookmarks.clone();
|
|
||||||
let window_action = window_action.clone();
|
|
||||||
move |_| {
|
|
||||||
// Bookmarks
|
|
||||||
main_bookmarks.remove_all();
|
|
||||||
for bookmark in profile.bookmark.recent(None) {
|
|
||||||
let menu_item = gio::MenuItem::new(Some(&ellipsize(&bookmark.request, LABEL_MAX_LENGTH)), None);
|
|
||||||
menu_item.set_action_and_target_value(Some(&format!(
|
|
||||||
"{}.{}",
|
|
||||||
window_action.id,
|
|
||||||
window_action.load.simple_action.name()
|
|
||||||
)), Some(&bookmark.request.to_variant()));
|
|
||||||
|
|
||||||
main_bookmarks.append_item(&menu_item);
|
|
||||||
} // @TODO `menu_item`
|
|
||||||
|
|
||||||
// Recently closed history
|
|
||||||
main_history_tab.remove_all();
|
|
||||||
for history in profile.history.recently_closed(None) {
|
|
||||||
let menu_item = gio::MenuItem::new(Some(&ellipsize(&history.request, LABEL_MAX_LENGTH)), None);
|
|
||||||
menu_item.set_action_and_target_value(Some(&format!(
|
|
||||||
"{}.{}",
|
|
||||||
window_action.id,
|
|
||||||
window_action.load.simple_action.name()
|
|
||||||
)), Some(&history.request.to_variant()));
|
|
||||||
|
|
||||||
main_history_tab.append_item(&menu_item);
|
|
||||||
} // @TODO `menu_item`
|
|
||||||
|
|
||||||
// Recently visited history
|
|
||||||
// * in first iteration, group records by it hostname
|
|
||||||
// * in second iteration, collect uri path as the menu sub-item label
|
|
||||||
main_history_request.remove_all();
|
|
||||||
|
|
||||||
let mut list: IndexMap<GString, Vec<Uri>> = IndexMap::new();
|
|
||||||
for history in profile.history.recently_opened(None) {
|
|
||||||
match Uri::parse(&history.request, UriFlags::NONE) {
|
|
||||||
Ok(uri) => list.entry(match uri.host() {
|
|
||||||
Some(host) => host,
|
|
||||||
None => uri.to_str(),
|
|
||||||
}).or_default().push(uri),
|
|
||||||
Err(_) => continue // @TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (group, items) in list {
|
|
||||||
let list = gio::Menu::new();
|
|
||||||
|
|
||||||
// Show first menu item only without children menu
|
|
||||||
if items.len() == 1 {
|
|
||||||
main_history_request.append_item(&menu_item(&window_action, &items[0], true));
|
|
||||||
|
|
||||||
// Create children menu items related to parental host item
|
|
||||||
} else {
|
|
||||||
for uri in items {
|
|
||||||
list.append_item(&menu_item(&window_action, &uri, false));
|
|
||||||
}
|
|
||||||
main_history_request.append_submenu(Some(&group), &list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
menu_button
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format dynamically generated strings for menu item label
|
|
||||||
/// * crop resulting string at the middle position on new `value` longer than `limit`
|
|
||||||
fn ellipsize(value: &str, limit: usize) -> String {
|
|
||||||
if value.len() <= limit {
|
|
||||||
return value.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let length = (limit - 2) / 2;
|
|
||||||
|
|
||||||
format!("{}..{}", &value[..length], &value[value.len() - length..])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format [Uri](https://docs.gtk.org/glib/struct.Uri.html)
|
|
||||||
/// as [MenuItem](https://docs.gtk.org/gio/class.MenuItem.html) label
|
|
||||||
fn uri_to_label(uri: &Uri, is_parent: bool) -> GString {
|
|
||||||
let path = uri.path();
|
|
||||||
if path == "/" || path.is_empty() {
|
|
||||||
if is_parent {
|
|
||||||
uri.host().unwrap_or(uri.to_str())
|
|
||||||
} else {
|
|
||||||
gtk::glib::gformat!("{}{path}", uri.host().unwrap_or(uri.to_str()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shared helper to create new [MenuItem](https://docs.gtk.org/gio/class.MenuItem.html)
|
|
||||||
fn menu_item(action: &WindowAction, uri: &Uri, is_parent: bool) -> gio::MenuItem {
|
|
||||||
let item = gio::MenuItem::new(
|
|
||||||
Some(&ellipsize(&uri_to_label(uri, is_parent), LABEL_MAX_LENGTH)),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
item.set_action_and_target_value(
|
|
||||||
Some(&format!(
|
|
||||||
"{}.{}",
|
|
||||||
action.id,
|
|
||||||
action.load.simple_action.name()
|
|
||||||
)),
|
|
||||||
Some(&uri.to_string().to_variant()),
|
|
||||||
);
|
|
||||||
item
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,11 @@ impl Bookmark {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
let time = DateTime::now_local()?;
|
||||||
memory.add(Item {
|
memory.add(Item {
|
||||||
id: self.database.add(DateTime::now_local()?, request, title)?,
|
id: self.database.add(time.clone(), request, title)?,
|
||||||
request: request.into(),
|
request: request.into(),
|
||||||
|
time,
|
||||||
title: title.map(|t| t.to_string()),
|
title: title.map(|t| t.to_string()),
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ pub fn select(
|
||||||
Ok(Item {
|
Ok(Item {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
//profile_id: row.get(1)?,
|
//profile_id: row.get(1)?,
|
||||||
//time: DateTime::from_unix_local(row.get(2)?).unwrap(),
|
time: DateTime::from_unix_local(row.get(2)?).unwrap(),
|
||||||
request: row.get(3)?,
|
request: row.get(3)?,
|
||||||
title: row.get(4)?,
|
title: row.get(4)?,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,6 @@
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub request: String,
|
pub request: String,
|
||||||
|
pub time: gtk::glib::DateTime,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue