delegate history_snap action to protocol driver implementation

This commit is contained in:
yggverse 2025-03-12 11:00:16 +02:00
parent 68b3119bb1
commit 33d5d414ac
16 changed files with 124 additions and 147 deletions

View file

@ -93,16 +93,12 @@ impl Window {
action.history_back.connect_activate({ action.history_back.connect_activate({
let tab = tab.clone(); let tab = tab.clone();
move |position| { move |position| tab.history_back(position)
tab.history_back(position);
}
}); });
action.history_forward.connect_activate({ action.history_forward.connect_activate({
let tab = tab.clone(); let tab = tab.clone();
move |position| { move |position| tab.history_forward(position)
tab.history_forward(position);
}
}); });
action.load.on_activate({ action.load.on_activate({
@ -114,9 +110,7 @@ impl Window {
action.open.on_activate({ action.open.on_activate({
let tab = tab.clone(); let tab = tab.clone();
move |position, request| { move |position, request| tab.open(position, request)
tab.open(position, request, true);
}
}); });
// Init struct // Init struct

View file

@ -284,13 +284,13 @@ impl Tab {
/// Reload page at `i32` position or selected page on `None` given /// Reload page at `i32` position or selected page on `None` given
pub fn reload(&self, page_position: Option<i32>) { pub fn reload(&self, page_position: Option<i32>) {
if let Some(item) = self.item(page_position) { if let Some(item) = self.item(page_position) {
item.client.handle(&item.page.navigation.request(), true); item.client.handle(&item.page.navigation.request());
} }
} }
pub fn open(&self, page_position: Option<i32>, request: &str, is_snap_history: bool) { pub fn open(&self, page_position: Option<i32>, request: &str) {
if let Some(item) = self.item(page_position) { if let Some(item) = self.item(page_position) {
item.action.load.activate(Some(request), is_snap_history); item.action.load.activate(Some(request));
} }
} }

View file

@ -90,7 +90,7 @@ impl Item {
if let Some(uri) = page.navigation.home() { if let Some(uri) = page.navigation.home() {
let request = uri.to_string(); let request = uri.to_string();
page.navigation.set_request(&request); page.navigation.set_request(&request);
client.handle(&request, true); client.handle(&request);
} }
} }
}); });
@ -98,10 +98,10 @@ impl Item {
action.load.connect_activate({ action.load.connect_activate({
let page = page.clone(); let page = page.clone();
let client = client.clone(); let client = client.clone();
move |request, is_history| { move |request| {
if let Some(text) = request { if let Some(request) = request {
page.navigation.set_request(&text); page.navigation.set_request(&request);
client.handle(&text, is_history); client.handle(&request);
} }
} }
}); });
@ -114,9 +114,7 @@ impl Item {
action.reload.connect_activate({ action.reload.connect_activate({
let page = page.clone(); let page = page.clone();
let client = client.clone(); let client = client.clone();
move |_, _| { move |_, _| client.handle(&page.navigation.request())
client.handle(&page.navigation.request(), true);
}
}); });
action.reload.connect_enabled_notify({ action.reload.connect_enabled_notify({
@ -150,10 +148,10 @@ impl Item {
}); });
// Handle immediately on request // Handle immediately on request
if let Some(text) = request { if let Some(request) = request {
page.navigation.set_request(text); page.navigation.set_request(request);
if is_load { if is_load {
client.handle(text, true); client.handle(request)
} }
} }

View file

@ -40,7 +40,7 @@ impl Action {
let history = Rc::new(History::build({ let history = Rc::new(History::build({
let load = load.clone(); let load = load.clone();
move |request| load.activate(Some(&request), false) move |request| load.activate(Some(&request))
})); }));
Self { Self {

View file

@ -32,30 +32,25 @@ impl Load {
/// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal /// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal
/// with formatted for this action [Variant](https://docs.gtk.org/glib/struct.Variant.html) value /// with formatted for this action [Variant](https://docs.gtk.org/glib/struct.Variant.html) value
pub fn activate(&self, request: Option<&str>, is_history: bool) { pub fn activate(&self, request: Option<&str>) {
self.simple_action.activate(Some( self.simple_action
&(request.unwrap_or_default(), is_history).to_variant(), .activate(Some(&(request.unwrap_or_default()).to_variant()));
));
} }
// Events // Events
/// Define callback function for /// Define callback function for
/// [SimpleAction::activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal /// [SimpleAction::activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal
pub fn connect_activate(&self, callback: impl Fn(Option<GString>, bool) + 'static) { pub fn connect_activate(&self, callback: impl Fn(Option<GString>) + 'static) {
self.simple_action.connect_activate(move |_, this| { self.simple_action.connect_activate(move |_, this| {
let (request, is_history) = this let request = this
.expect("Expected (`request`,`is_history`) variant") .expect("Expected `request` variant")
.get::<(String, bool)>() .get::<String>()
.expect("Parameter type does not match (`String`,`bool`) tuple"); .expect("Parameter type does not match `String`");
callback(match request.is_empty() {
callback( true => None,
match request.is_empty() { false => Some(request.into()),
true => None, })
false => Some(request.into()),
},
is_history,
)
}); });
} }
} }

View file

@ -36,7 +36,7 @@ impl Client {
/// Route tab item `request` to protocol driver /// Route tab item `request` to protocol driver
/// * or `navigation` entry if the value not provided /// * or `navigation` entry if the value not provided
pub fn handle(&self, request: &str, is_snap_history: bool) { pub fn handle(&self, request: &str) {
// Move focus out from navigation entry @TODO // Move focus out from navigation entry @TODO
self.page.browser_action.escape.activate(None); self.page.browser_action.escape.activate(None);
@ -61,18 +61,8 @@ impl Client {
match result { match result {
// route by scheme // route by scheme
Ok(uri) => match uri.scheme().as_str() { Ok(uri) => match uri.scheme().as_str() {
"file" => { "file" => driver.file.handle(uri, feature, cancellable),
if is_snap_history { "gemini" | "titan" => driver.gemini.handle(uri, feature, cancellable),
snap_history(&page);
}
driver.file.handle(uri, feature, cancellable)
}
"gemini" | "titan" => {
if is_snap_history {
snap_history(&page);
}
driver.gemini.handle(uri, feature, cancellable)
}
scheme => { scheme => {
// no scheme match driver, complete with failure message // no scheme match driver, complete with failure message
let status = page.content.to_status_failure(); let status = page.content.to_status_failure();
@ -84,10 +74,7 @@ impl Client {
} }
}, },
// begin redirection to new address suggested // begin redirection to new address suggested
Err(query) => page Err(query) => page.item_action.load.activate(Some(&query)),
.item_action
.load
.activate(Some(&query), is_snap_history),
} }
} }
}) })
@ -199,13 +186,3 @@ fn search(profile: &Profile, query: &str) -> Uri {
) )
.unwrap() // @TODO handle or skip extra URI parse by String return .unwrap() // @TODO handle or skip extra URI parse by String return
} }
/// Make new history record in related components
fn snap_history(page: &Page) {
page.item_action
.history
.add(page.navigation.request(), true);
page.profile
.history
.open(page.navigation.request(), Some(page.title()))
}

View file

@ -20,18 +20,16 @@ impl Directory {
{ {
let page = page.clone(); let page = page.clone();
move |file| { move |file| {
page.item_action.load.activate( page.item_action.load.activate(Some(&format!(
Some(&format!( "file://{}",
"file://{}", file.path().unwrap().to_str().unwrap()
file.path().unwrap().to_str().unwrap() )))
)),
true,
)
} }
}, },
), ),
); );
page.set_title(&self.file.parse_name()); page.set_title(&self.file.parse_name());
page.snap_history();
page.window_action.find.simple_action.set_enabled(false); page.window_action.find.simple_action.set_enabled(false);
page.window_action.save_as.simple_action.set_enabled(false); page.window_action.save_as.simple_action.set_enabled(false);
} }

View file

@ -15,6 +15,7 @@ impl Image {
}; };
page.set_title(&crate::tool::uri_to_title(uri)); page.set_title(&crate::tool::uri_to_title(uri));
page.set_progress(0.0); page.set_progress(0.0);
page.snap_history();
page.window_action.find.simple_action.set_enabled(false); page.window_action.find.simple_action.set_enabled(false);
} }
} }

View file

@ -10,6 +10,7 @@ impl Status {
widget.set_description(Some(message)); widget.set_description(Some(message));
page.set_title(&widget.title()); page.set_title(&widget.title());
page.set_progress(0.0); page.set_progress(0.0);
page.snap_history();
page.window_action.find.simple_action.set_enabled(false); page.window_action.find.simple_action.set_enabled(false);
} }
} }

View file

@ -19,6 +19,7 @@ impl Text {
None => crate::tool::uri_to_title(uri), None => crate::tool::uri_to_title(uri),
}); });
page.set_progress(0.0); page.set_progress(0.0);
page.snap_history();
page.window_action.find.simple_action.set_enabled(true); page.window_action.find.simple_action.set_enabled(true);
} }
} }

View file

@ -102,6 +102,7 @@ impl Gemini {
}); });
self.page.set_title("Titan input"); self.page.set_title("Titan input");
self.page.set_progress(0.0); self.page.set_progress(0.0);
//self.page.snap_history();
} }
_ => panic!(), // unexpected _ => panic!(), // unexpected
} }
@ -145,6 +146,7 @@ fn handle(
let title = input.to_string(); let title = input.to_string();
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&title); page.set_title(&title);
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
match input { match input {
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-10 // https://geminiprotocol.net/docs/protocol-specification.gmi#status-10
@ -174,62 +176,61 @@ fn handle(
{ {
let cancellable = cancellable.clone(); let cancellable = cancellable.clone();
let stream = connection.stream(); let stream = connection.stream();
move |file, action| { move |file, action| match file.replace(
match file.replace( None,
None, false,
false, gtk::gio::FileCreateFlags::NONE,
gtk::gio::FileCreateFlags::NONE, Some(&cancellable),
Some(&cancellable), ) {
) { Ok(file_output_stream) => {
Ok(file_output_stream) => { use crate::tool::Format;
use crate::tool::Format; // Asynchronously read [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
// Asynchronously read [IOStream](https://docs.gtk.org/gio/class.IOStream.html) // to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html) // show bytes count in loading widget, validate max size for incoming data
// show bytes count in loading widget, validate max size for incoming data // * no dependency of Gemini library here, feel free to use any other `IOStream` processor
// * no dependency of Gemini library here, feel free to use any other `IOStream` processor file_output_stream::from_stream_async(
file_output_stream::from_stream_async( stream.clone(),
stream.clone(), file_output_stream,
file_output_stream, cancellable.clone(),
cancellable.clone(), Priority::DEFAULT,
Priority::DEFAULT, (
( 0x100000, // 1M bytes per chunk
0x100000, // 1M bytes per chunk None, // unlimited
None, // unlimited 0, // initial totals
0, // initial totals ),
), (
( // on chunk
// on chunk {
{ let action = action.clone();
let action = action.clone(); move |_, total| action.update.activate(&format!(
move |_, total| action.update.activate(&format!( "Received {}...",
"Received {}...", total.bytes()
total.bytes() ))
)) },
}, // on complete
// on complete {
{ let action = action.clone();
let action = action.clone(); move |result| match result {
move |result| match result { Ok((_, total)) => {
Ok((_, total)) => { action.complete.activate(&format!(
action.complete.activate(&format!( "Saved to {} ({} total)",
"Saved to {} ({} total)", file.parse_name(),
file.parse_name(), total.bytes()
total.bytes() ))
))
}
Err(e) => action.cancel.activate(&e.to_string())
} }
}, Err(e) => action.cancel.activate(&e.to_string())
), }
) },
} ),
Err(e) => action.cancel.activate(&e.to_string()), )
} }
Err(e) => action.cancel.activate(&e.to_string()),
} }
}, },
); );
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
}, },
_ => match success.mime() { _ => match success.mime() {
@ -270,6 +271,7 @@ fn handle(
.find .find
.simple_action .simple_action
.set_enabled(true); .set_enabled(true);
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
}, },
Err(e) => { Err(e) => {
@ -277,6 +279,7 @@ fn handle(
status.set_description(Some(&e.to_string())); status.set_description(Some(&e.to_string()));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
}, },
}, },
@ -285,6 +288,7 @@ fn handle(
status.set_description(Some(&e.to_string())); status.set_description(Some(&e.to_string()));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
} }
} }
@ -294,6 +298,7 @@ fn handle(
status.set_description(Some(&e.to_string())); status.set_description(Some(&e.to_string()));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
}, },
} }
@ -320,7 +325,6 @@ fn handle(
( (
move |_, total| status.set_description(Some(&format!("Download: {total} bytes"))), move |_, total| status.set_description(Some(&format!("Download: {total} bytes"))),
{ {
//let page = page.clone();
move | result | match result { move | result | match result {
Ok((memory_input_stream, _)) => { Ok((memory_input_stream, _)) => {
Pixbuf::from_stream_async( Pixbuf::from_stream_async(
@ -339,6 +343,7 @@ fn handle(
} }
} }
page.set_progress(0.0); page.set_progress(0.0);
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
}, },
) )
@ -348,6 +353,7 @@ fn handle(
status.set_description(Some(&e.to_string())); status.set_description(Some(&e.to_string()));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
} }
} }
@ -362,6 +368,7 @@ fn handle(
status.set_description(Some(&format!("Content type `{mime}` yet not supported"))); status.set_description(Some(&format!("Content type `{mime}` yet not supported")));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
}, },
} }
@ -397,7 +404,7 @@ fn handle(
page.navigation.set_request(&target.to_string()); page.navigation.set_request(&target.to_string());
} }
redirects.replace(total); redirects.replace(total);
page.item_action.load.activate(Some(&target.to_string()), false); page.item_action.load.activate(Some(&target.to_string()));
} }
} }
Err(e) => { Err(e) => {
@ -419,6 +426,7 @@ fn handle(
status.set_description(Some(message.as_ref().unwrap_or(&certificate.to_string()))); status.set_description(Some(message.as_ref().unwrap_or(&certificate.to_string())));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
} }
} }
@ -433,6 +441,7 @@ fn handle(
status.set_description(Some(message.as_ref().unwrap_or(&temporary.to_string()))); status.set_description(Some(message.as_ref().unwrap_or(&temporary.to_string())));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
if let Some(callback) = on_failure { if let Some(callback) = on_failure {
callback() callback()
@ -449,6 +458,7 @@ fn handle(
status.set_description(Some(message.as_ref().unwrap_or(&permanent.to_string()))); status.set_description(Some(message.as_ref().unwrap_or(&permanent.to_string())));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
if let Some(callback) = on_failure { if let Some(callback) = on_failure {
callback() callback()
@ -462,6 +472,7 @@ fn handle(
status.set_description(Some(&e.to_string())); status.set_description(Some(&e.to_string()));
page.set_progress(0.0); page.set_progress(0.0);
page.set_title(&status.title()); page.set_title(&status.title());
page.snap_history();
redirects.replace(0); // reset redirects.replace(0); // reset
} }
} }

View file

@ -93,6 +93,16 @@ impl Page {
self.search.show() self.search.show()
} }
/// Make new history record in related components
pub fn snap_history(&self) {
self.item_action
.history
.add(self.navigation.request(), true);
self.profile
.history
.open(self.navigation.request(), Some(self.title()))
}
/// Cleanup session for `Self` /// Cleanup session for `Self`
pub fn clean( pub fn clean(
&self, &self,

View file

@ -24,9 +24,7 @@ pub fn build(mime: &str, download: Option<(&Rc<ItemAction>, &Uri)>) -> StatusPag
let action = action.clone(); let action = action.clone();
let request = request.clone(); let request = request.clone();
move |_| { move |_| {
action action.load.activate(Some(&format!("download:{}", request)));
.load
.activate(Some(&format!("download:{}", request)), true);
} }
}); });

View file

@ -388,8 +388,7 @@ impl Gemini {
// Select link handler by scheme // Select link handler by scheme
return match uri.scheme().as_str() { return match uri.scheme().as_str() {
"gemini" | "titan" => { "gemini" | "titan" => {
// Open new page in browser item_action.load.activate(Some(&uri.to_str()))
item_action.load.activate(Some(&uri.to_str()), true);
} }
// Scheme not supported, delegate // Scheme not supported, delegate
_ => UriLauncher::new(&uri.to_str()).launch( _ => UriLauncher::new(&uri.to_str()).launch(

View file

@ -81,14 +81,11 @@ impl Response for Box {
action_send.connect_activate({ action_send.connect_activate({
let form = form.clone(); let form = form.clone();
move |_, _| { move |_, _| {
item_action.load.activate( item_action.load.activate(Some(&format!(
Some(&format!( "{}?{}",
"{}?{}", base.to_string_partial(UriHideFlags::QUERY),
base.to_string_partial(UriHideFlags::QUERY), Uri::escape_string(&form.text(), None, false),
Uri::escape_string(&form.text(), None, false), )))
)),
false, // prevent re-send on history navigation
);
} }
}); });

View file

@ -59,14 +59,11 @@ impl Sensitive for Box {
action_send.connect_activate({ action_send.connect_activate({
let form = form.clone(); let form = form.clone();
move |_, _| { move |_, _| {
item_action.load.activate( item_action.load.activate(Some(&format!(
Some(&format!( "{}?{}",
"{}?{}", base.to_string_partial(UriHideFlags::QUERY),
base.to_string_partial(UriHideFlags::QUERY), Uri::escape_string(&form.password_entry_row.text(), None, false),
Uri::escape_string(&form.password_entry_row.text(), None, false), )))
)),
false, // prevent re-send on history navigation
);
} }
}); });