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"
|
||||
ggemini = "0.19.0"
|
||||
ggemtext = "0.7.0"
|
||||
indexmap = "2.7.0"
|
||||
itertools = "0.14.0"
|
||||
# libspelling = "0.4.0"
|
||||
maxminddb = "0.26.0"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
mod about;
|
||||
mod action;
|
||||
mod bookmarks;
|
||||
mod database;
|
||||
mod history;
|
||||
mod proxy;
|
||||
mod widget;
|
||||
pub mod window;
|
||||
|
||||
use about::About;
|
||||
use action::Action;
|
||||
use bookmarks::Bookmarks;
|
||||
use history::History;
|
||||
use proxy::Proxy;
|
||||
use widget::Widget;
|
||||
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({
|
||||
let profile = profile.clone();
|
||||
let window = window.clone();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
mod about;
|
||||
mod bookmarks;
|
||||
mod close;
|
||||
mod debug;
|
||||
mod history;
|
||||
mod profile;
|
||||
mod proxy;
|
||||
|
||||
use about::About;
|
||||
use bookmarks::Bookmarks;
|
||||
use close::Close;
|
||||
use debug::Debug;
|
||||
use history::History;
|
||||
use profile::Profile;
|
||||
use proxy::Proxy;
|
||||
|
||||
|
|
@ -21,8 +25,10 @@ use std::rc::Rc;
|
|||
pub struct Action {
|
||||
// Actions
|
||||
pub about: Rc<About>,
|
||||
pub bookmarks: Rc<Bookmarks>,
|
||||
pub close: Rc<Close>,
|
||||
pub debug: Rc<Debug>,
|
||||
pub history: Rc<History>,
|
||||
pub profile: Rc<Profile>,
|
||||
pub proxy: Rc<Proxy>,
|
||||
// Group
|
||||
|
|
@ -43,8 +49,10 @@ impl Action {
|
|||
pub fn new() -> Self {
|
||||
// Init actions
|
||||
let about = Rc::new(About::new());
|
||||
let bookmarks = Rc::new(Bookmarks::new());
|
||||
let close = Rc::new(Close::new());
|
||||
let debug = Rc::new(Debug::new());
|
||||
let history = Rc::new(History::new());
|
||||
let profile = Rc::new(Profile::new());
|
||||
let proxy = Rc::new(Proxy::new());
|
||||
|
||||
|
|
@ -56,18 +64,22 @@ impl Action {
|
|||
|
||||
// Add action to given group
|
||||
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(&debug.simple_action);
|
||||
simple_action_group.add_action(&history.simple_action);
|
||||
simple_action_group.add_action(&profile.simple_action);
|
||||
simple_action_group.add_action(&proxy.simple_action);
|
||||
|
||||
// Done
|
||||
Self {
|
||||
about,
|
||||
bookmarks,
|
||||
close,
|
||||
debug,
|
||||
profile,
|
||||
proxy,
|
||||
history,
|
||||
id,
|
||||
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 header;
|
||||
pub mod tab;
|
||||
|
|
@ -119,7 +119,6 @@ impl Window {
|
|||
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
||||
g_box.append(&ToolbarView::header(
|
||||
(browser_action, &action),
|
||||
profile,
|
||||
&tab.tab_view,
|
||||
));
|
||||
g_box.append(&tab.tab_view);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
mod bar;
|
||||
|
||||
use super::{Action as WindowAction, BrowserAction, Profile};
|
||||
use super::{Action as WindowAction, BrowserAction};
|
||||
use adw::{TabView, ToolbarView};
|
||||
use bar::Bar;
|
||||
use gtk::Box;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub trait Header {
|
||||
fn header(
|
||||
action: (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||
profile: &Rc<Profile>,
|
||||
tab_view: &TabView,
|
||||
) -> Self;
|
||||
fn header(action: (&Rc<BrowserAction>, &Rc<WindowAction>), tab_view: &TabView) -> Self;
|
||||
}
|
||||
|
||||
impl Header for ToolbarView {
|
||||
|
|
@ -20,16 +16,11 @@ impl Header for ToolbarView {
|
|||
/// Build new `Self`
|
||||
fn header(
|
||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||
profile: &Rc<Profile>,
|
||||
tab_view: &TabView,
|
||||
) -> Self {
|
||||
let toolbar_view = ToolbarView::builder().build();
|
||||
|
||||
toolbar_view.add_top_bar(&Box::bar(
|
||||
(browser_action, window_action),
|
||||
profile,
|
||||
tab_view,
|
||||
));
|
||||
toolbar_view.add_top_bar(&Box::bar((browser_action, window_action), tab_view));
|
||||
|
||||
toolbar_view
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,13 @@ use control::Control;
|
|||
use menu::Menu;
|
||||
use tab::Tab;
|
||||
|
||||
use super::{BrowserAction, Profile, WindowAction};
|
||||
use super::{BrowserAction, WindowAction};
|
||||
use adw::{TabBar, TabView};
|
||||
use gtk::{Box, MenuButton, Orientation, prelude::BoxExt};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub trait Bar {
|
||||
fn bar(
|
||||
action: (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||
profile: &Rc<Profile>,
|
||||
view: &TabView,
|
||||
) -> Self;
|
||||
fn bar(action: (&Rc<BrowserAction>, &Rc<WindowAction>), view: &TabView) -> Self;
|
||||
}
|
||||
|
||||
impl Bar for Box {
|
||||
|
|
@ -25,7 +21,6 @@ impl Bar for Box {
|
|||
/// Build new `Self`
|
||||
fn bar(
|
||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||
profile: &Rc<Profile>,
|
||||
view: &TabView,
|
||||
) -> Self {
|
||||
let g_box = Box::builder()
|
||||
|
|
@ -34,7 +29,7 @@ impl Bar for Box {
|
|||
.build();
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
use super::{BrowserAction, Profile, WindowAction};
|
||||
use super::{BrowserAction, WindowAction};
|
||||
use gtk::{
|
||||
Align, MenuButton,
|
||||
gio::{self},
|
||||
glib::{GString, Uri, UriFlags},
|
||||
prelude::{ActionExt, ToVariant},
|
||||
prelude::ActionExt,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
// Config options
|
||||
|
||||
const LABEL_MAX_LENGTH: usize = 28;
|
||||
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?
|
||||
|
|
@ -22,7 +19,6 @@ impl Menu for MenuButton {
|
|||
/// Build new `Self`
|
||||
fn menu(
|
||||
(browser_action, window_action): (&Rc<BrowserAction>, &Rc<WindowAction>),
|
||||
profile: &Rc<Profile>,
|
||||
) -> Self {
|
||||
// Main
|
||||
let main = gio::Menu::new();
|
||||
|
|
@ -116,7 +112,7 @@ impl Menu for MenuButton {
|
|||
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);
|
||||
|
||||
|
|
@ -139,29 +135,22 @@ impl Menu for MenuButton {
|
|||
|
||||
main.append_submenu(Some("Page"), &main_page);
|
||||
|
||||
// Main > Bookmark
|
||||
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
||||
let main_bookmarks = gio::Menu::new();
|
||||
|
||||
main.append_submenu(Some("Bookmarks"), &main_bookmarks);
|
||||
// Main > Bookmarks
|
||||
main.append(Some("Bookmarks"), Some(&format!(
|
||||
"{}.{}",
|
||||
browser_action.id,
|
||||
browser_action.bookmarks.simple_action.name()
|
||||
)));
|
||||
|
||||
// Main > History
|
||||
let main_history = gio::Menu::new();
|
||||
|
||||
// Main > History > Recently closed
|
||||
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
||||
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.append(Some("History"), Some(&format!(
|
||||
"{}.{}",
|
||||
browser_action.id,
|
||||
browser_action.history.simple_action.name()
|
||||
)));
|
||||
|
||||
// Main > Proxy
|
||||
main.append(Some("Proxy settings"), Some(&format!(
|
||||
main.append(Some("Proxy"), Some(&format!(
|
||||
"{}.{}",
|
||||
browser_action.id,
|
||||
browser_action.proxy.simple_action.name()
|
||||
|
|
@ -198,124 +187,12 @@ impl Menu for MenuButton {
|
|||
)));
|
||||
|
||||
// Init main widget
|
||||
let menu_button = MenuButton::builder()
|
||||
.css_classes(["flat"])
|
||||
.icon_name("open-menu-symbolic")
|
||||
.menu_model(&main)
|
||||
.tooltip_text("Menu")
|
||||
.valign(Align::Center)
|
||||
.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
|
||||
MenuButton::builder()
|
||||
.css_classes(["flat"])
|
||||
.icon_name("open-menu-symbolic")
|
||||
.menu_model(&main)
|
||||
.tooltip_text("Menu")
|
||||
.valign(Align::Center)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
None => {
|
||||
let time = DateTime::now_local()?;
|
||||
memory.add(Item {
|
||||
id: self.database.add(DateTime::now_local()?, request, title)?,
|
||||
id: self.database.add(time.clone(), request, title)?,
|
||||
request: request.into(),
|
||||
time,
|
||||
title: title.map(|t| t.to_string()),
|
||||
});
|
||||
true
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ pub fn select(
|
|||
Ok(Item {
|
||||
id: row.get(0)?,
|
||||
//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)?,
|
||||
title: row.get(4)?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
pub struct Item {
|
||||
pub id: i64,
|
||||
pub request: String,
|
||||
pub time: gtk::glib::DateTime,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue