diff --git a/src/app.rs b/src/app.rs index 8c46d969..b7eb7970 100644 --- a/src/app.rs +++ b/src/app.rs @@ -32,12 +32,10 @@ impl App { // Init components let browser = Rc::new(Browser::build(profile)); - // Init events - application.connect_activate({ - let browser = browser.clone(); - move |_| browser.update() - }); + // Prevent startup warning @TODO + application.connect_activate(|_| {}); + // Init events application.connect_startup({ let browser = browser.clone(); let profile = profile.clone(); @@ -150,11 +148,7 @@ impl App { &["i"], ), ( - format!( - "{}.{}", - browser.action.id, - browser.action.escape.simple_action.name() - ), + format!("{}.{}", browser.action.id, browser.action.escape.name()), &["Escape"], ), // Tab actions diff --git a/src/app/browser.rs b/src/app/browser.rs index 9bf85f4e..23fd1960 100644 --- a/src/app/browser.rs +++ b/src/app/browser.rs @@ -77,8 +77,8 @@ impl Browser { action.escape.connect_activate({ let widget = widget.clone(); let window = window.clone(); - move |tab_item_id| { - window.escape(tab_item_id); + move |_, _| { + window.escape(); widget.application_window.set_focus(gtk::Window::NONE); } }); @@ -98,11 +98,6 @@ impl Browser { } }); - action.update.connect_activate({ - let window = window.clone(); - move |tab_item_id| window.update(tab_item_id) - }); - // Return new activated `Self` Self { action, @@ -185,10 +180,6 @@ impl Browser { self.widget.application_window.present(); self } - - pub fn update(&self) { - self.window.update(None); - } } // Tools diff --git a/src/app/browser/action.rs b/src/app/browser/action.rs index 02bb5684..2e116523 100644 --- a/src/app/browser/action.rs +++ b/src/app/browser/action.rs @@ -3,17 +3,15 @@ mod close; mod debug; mod escape; mod profile; -mod update; use about::About; use close::Close; use debug::Debug; use escape::Escape; use profile::Profile; -use update::Update; use gtk::{ - gio::SimpleActionGroup, + gio::{SimpleAction, SimpleActionGroup}, glib::{uuid_string_random, GString}, prelude::ActionMapExt, }; @@ -25,9 +23,8 @@ pub struct Action { pub about: Rc, pub close: Rc, pub debug: Rc, - pub escape: Rc, + pub escape: SimpleAction, pub profile: Rc, - pub update: Rc, // Group pub id: GString, pub simple_action_group: SimpleActionGroup, @@ -48,9 +45,8 @@ impl Action { let about = Rc::new(About::new()); let close = Rc::new(Close::new()); let debug = Rc::new(Debug::new()); - let escape = Rc::new(Escape::new()); + let escape = SimpleAction::escape(); let profile = Rc::new(Profile::new()); - let update = Rc::new(Update::new()); // Generate unique group ID let id = uuid_string_random(); @@ -62,9 +58,8 @@ impl Action { simple_action_group.add_action(&about.simple_action); simple_action_group.add_action(&close.simple_action); simple_action_group.add_action(&debug.simple_action); - simple_action_group.add_action(&escape.simple_action); + simple_action_group.add_action(&escape); simple_action_group.add_action(&profile.simple_action); - simple_action_group.add_action(&update.simple_action); // Done Self { @@ -73,7 +68,6 @@ impl Action { debug, escape, profile, - update, id, simple_action_group, } diff --git a/src/app/browser/action/escape.rs b/src/app/browser/action/escape.rs index 23d28e6d..48e32ad6 100644 --- a/src/app/browser/action/escape.rs +++ b/src/app/browser/action/escape.rs @@ -1,85 +1,12 @@ -use gtk::{ - gio::SimpleAction, - glib::{uuid_string_random, GString}, - prelude::{ActionExt, ToVariant}, -}; +use gtk::{gio::SimpleAction, glib::uuid_string_random}; /// [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) wrapper for `Escape` action of `Browser` group -pub struct Escape { - pub simple_action: SimpleAction, +pub trait Escape { + fn escape() -> Self; } -impl Default for Escape { - fn default() -> Self { - Self::new() - } -} - -impl Escape { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - simple_action: SimpleAction::new_stateful( - &uuid_string_random(), - None, - &String::new().to_variant(), - ), - } - } - - // Actions - - /// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal - /// * this action reset previous state for action after activation - pub fn activate_stateful_once(&self, tab_item_id: Option) { - // Save current state in memory - let _tab_item_id = state(&self.simple_action); - - // Apply requested state - self.change_state(tab_item_id); - - // Activate action - self.simple_action.activate(None); - - // Return previous state - self.change_state(_tab_item_id); - } - - /// Change action [state](https://docs.gtk.org/gio/method.SimpleAction.set_state.html) - /// * set default state on `None` - pub fn change_state(&self, state: Option) { - self.simple_action.change_state( - &match state { - Some(value) => value.to_string(), - None => String::new(), - } - .to_variant(), - ) - } - - // 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(Option) + 'static) { - self.simple_action - .connect_activate(move |this, _| callback(state(this))); - } -} - -/// Shared helper to get C-based action state in Optional format -fn state(this: &SimpleAction) -> Option { - let state = this - .state() - .expect("State value required") - .get::() - .expect("Parameter type does not match `String`"); - - if state.is_empty() { - None - } else { - Some(state.into()) +impl Escape for SimpleAction { + fn escape() -> Self { + SimpleAction::new(&uuid_string_random(), None) } } diff --git a/src/app/browser/action/update.rs b/src/app/browser/action/update.rs deleted file mode 100644 index 34fdc805..00000000 --- a/src/app/browser/action/update.rs +++ /dev/null @@ -1,62 +0,0 @@ -use gtk::{ - gio::SimpleAction, - glib::{uuid_string_random, GString}, - prelude::{ActionExt, StaticVariantType, ToVariant}, -}; - -/// [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) wrapper for `Update` action of `Browser` group -pub struct Update { - pub simple_action: SimpleAction, -} - -impl Default for Update { - fn default() -> Self { - Self::new() - } -} - -impl Update { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - simple_action: SimpleAction::new( - &uuid_string_random(), - Some(&String::static_variant_type()), - ), - } - } - - // Actions - - /// 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 - pub fn activate(&self, tab_item_id: Option<&str>) { - self.simple_action.activate(Some( - &match tab_item_id { - Some(value) => String::from(value), - None => String::new(), - } - .to_variant(), - )); - } - - // 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(Option) + 'static) { - self.simple_action.connect_activate(move |_, variant| { - let tab_item_id = variant - .expect("Variant required to call this action") - .get::() - .expect("Parameter type does not match `String`"); - - callback(match tab_item_id.is_empty() { - true => None, - false => Some(tab_item_id.into()), - }) - }); - } -} diff --git a/src/app/browser/window.rs b/src/app/browser/window.rs index 2309b5b4..2c342ab4 100644 --- a/src/app/browser/window.rs +++ b/src/app/browser/window.rs @@ -11,7 +11,7 @@ use tab::Tab; use super::Action as BrowserAction; use crate::Profile; -use gtk::{glib::GString, prelude::BoxExt, Box, Orientation}; +use gtk::{prelude::BoxExt, Box, Orientation}; use std::rc::Rc; pub struct Window { @@ -131,12 +131,8 @@ impl Window { } // Actions - pub fn escape(&self, tab_item_id: Option) { - self.tab.escape(tab_item_id); - } - - pub fn update(&self, tab_item_id: Option) { - self.tab.update(tab_item_id); + pub fn escape(&self) { + self.tab.escape(); } pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { diff --git a/src/app/browser/window/header/bar/menu.rs b/src/app/browser/window/header/bar/menu.rs index 806c9ffe..54b81e7a 100644 --- a/src/app/browser/window/header/bar/menu.rs +++ b/src/app/browser/window/header/bar/menu.rs @@ -210,7 +210,7 @@ impl Menu { // Recently closed history main_history_tab.remove_all(); for item in profile.history.memory.tab.recent() { - let item_request = item.page.navigation.request.widget.entry.text(); // @TODO restore entire `Item` + let item_request = item.page.navigation.request.entry.text(); // @TODO restore entire `Item` let menu_item = gio::MenuItem::new(Some(&ellipsize(&item_request, LABEL_MAX_LENGTH)), None); menu_item.set_action_and_target_value(Some(&format!( "{}.{}", diff --git a/src/app/browser/window/tab.rs b/src/app/browser/window/tab.rs index 8e494f58..37bfd032 100644 --- a/src/app/browser/window/tab.rs +++ b/src/app/browser/window/tab.rs @@ -6,6 +6,7 @@ mod menu; mod widget; use action::Action; +use adw::TabPage; use error::Error; pub use item::Item; use menu::Menu; @@ -14,7 +15,7 @@ use widget::Widget; use super::{Action as WindowAction, BrowserAction, Position}; use crate::Profile; use gtk::{ - glib::{DateTime, GString, Propagation}, + glib::{DateTime, Propagation}, prelude::{ActionExt, EditableExt, WidgetExt}, }; use sqlite::Transaction; @@ -25,7 +26,7 @@ pub struct Tab { browser_action: Rc, window_action: Rc, profile: Rc, - index: Rc, Rc>>>, + index: Rc>>>, pub action: Rc, pub widget: Rc, } @@ -41,8 +42,7 @@ impl Tab { let action = Rc::new(Action::new()); // Init empty HashMap index - let index: Rc, Rc>>> = - Rc::new(RefCell::new(HashMap::new())); + let index: Rc>>> = Rc::new(RefCell::new(HashMap::new())); // Init context menu let menu = Menu::new(window_action); @@ -58,21 +58,18 @@ impl Tab { move |tab_view, tab_page| { let state = match tab_page { // on menu open - Some(this) => { - if let Some(id) = this.keyword() { - if let Some(item) = index.borrow().get(&id) { - item.page.update(); // update window actions using page of tab activated - } + Some(tab_page) => { + if let Some(item) = index.borrow().get(tab_page) { + item.page.update(); // update window actions using page of tab activated } - Some(tab_view.page_position(this)) // activated tab + + Some(tab_view.page_position(tab_page)) // activated tab } // on menu close None => { - if let Some(page) = widget.page(None) { - if let Some(id) = page.keyword() { - if let Some(item) = index.borrow().get(&id) { - item.page.update(); // update window actions using page of current tab - } + if let Some(tab_page) = widget.page(None) { + if let Some(item) = index.borrow().get(&tab_page) { + item.page.update(); // update window actions using page of current tab } } None // current tab @@ -97,57 +94,39 @@ impl Tab { widget.tab_view.connect_close_page({ let index = index.clone(); let profile = profile.clone(); - move |_, item| { - // Get index ID by keyword saved - match item.keyword() { - Some(id) => { - if id.is_empty() { - panic!("Tab index can not be empty!") - } - // Cleanup HashMap index - if let Some(item) = index.borrow_mut().remove(&id) { - // Add history record into profile memory pool - // * this action allows to recover recently closed tab (e.g. from the main menu) - profile - .history - .memory - .tab - .add(item, DateTime::now_local().unwrap().to_unix()); - } - } - None => panic!("Undefined tab index!"), + move |_, tab_page| { + // Cleanup HashMap index + if let Some(item) = index.borrow_mut().remove(tab_page) { + // Add history record into profile memory pool + // * this action allows to recover recently closed tab (e.g. from the main menu) + profile + .history + .memory + .tab + .add(item, DateTime::now_local().unwrap().to_unix()); } Propagation::Proceed } }); + widget.tab_view.connect_page_attached({ + let window_action = window_action.clone(); + let index = index.clone(); + move |_, tab_page, _| { + if tab_page.is_selected() { + update_actions(tab_page, &index, &window_action); + } + } + }); + widget.tab_view.connect_selected_page_notify({ let window_action = window_action.clone(); let index = index.clone(); move |this| { - if let Some(page) = this.selected_page() { - if let Some(id) = page.keyword() { - if let Some(item) = index.borrow().get(&id) { - window_action - .home - .simple_action - .set_enabled(item.action.home.is_enabled()); - window_action - .reload - .simple_action - .set_enabled(item.action.reload.is_enabled()); - window_action - .history_back - .simple_action - .set_enabled(item.action.history.back.is_enabled()); - window_action - .history_forward - .simple_action - .set_enabled(item.action.history.forward.is_enabled()); - } - } - page.set_needs_attention(false); + if let Some(tab_page) = this.selected_page() { + tab_page.set_needs_attention(false); + update_actions(&tab_page, &index, &window_action); } } }); @@ -193,13 +172,13 @@ impl Tab { // Expect user input on tab appended has empty request entry // * this action initiated here because should be applied on tab appending event only if request.is_none() || request.is_some_and(|value| value.is_empty()) { - item.page.navigation.request.widget.entry.grab_focus(); + item.page.navigation.request.entry.grab_focus(); } // Register dynamically created tab components in the HashMap index self.index .borrow_mut() - .insert(item.id.clone(), item.clone()); + .insert(item.widget.tab_page.clone(), item.clone()); item } @@ -215,18 +194,9 @@ impl Tab { } // Toggle escape action for specified or current item - pub fn escape(&self, item_id: Option) { - match item_id { - Some(id) => { - if let Some(item) = self.index.borrow().get(&id) { - item.page.escape() - } - } - None => { - if let Some(item) = self.item(None) { - item.page.escape(); - } - } + pub fn escape(&self) { + if let Some(item) = self.item(None) { + item.page.escape(); } } @@ -274,7 +244,7 @@ impl Tab { if let Some(item) = self.item(page_position) { if let Some(home) = item.page.navigation.request.home() { let home = home.to_string(); - item.page.navigation.request.widget.entry.set_text(&home); + item.page.navigation.request.entry.set_text(&home); item.client.handle(&home, true); } } @@ -296,23 +266,7 @@ impl Tab { pub fn page_reload(&self, page_position: Option) { if let Some(item) = self.item(page_position) { item.client - .handle(&item.page.navigation.request.widget.entry.text(), true); - } - } - - pub fn update(&self, item_id: Option) { - let key = item_id.unwrap_or_default(); - - match self.index.borrow().get(&key) { - Some(item) => { - item.update(); - } - None => { - // update all tabs - for (_, item) in self.index.borrow().iter() { - item.update(); - } - } + .handle(&item.page.navigation.request.entry.text(), true); } } @@ -361,7 +315,7 @@ impl Tab { // Register dynamically created tab item in the HashMap index self.index .borrow_mut() - .insert(item.id.clone(), item.clone()); + .insert(item.widget.tab_page.clone(), item.clone()); } } Err(e) => return Err(e.to_string()), @@ -412,11 +366,9 @@ impl Tab { } fn item(&self, position: Option) -> Option> { - if let Some(page) = self.widget.page(position) { - if let Some(id) = page.keyword() { - if let Some(item) = self.index.borrow().get(&id) { - return Some(item.clone()); - } + if let Some(tab_page) = self.widget.page(position) { + if let Some(item) = self.index.borrow().get(&tab_page) { + return Some(item.clone()); } } None @@ -424,6 +376,7 @@ impl Tab { } // Tools + pub fn migrate(tx: &Transaction) -> Result<(), String> { // Migrate self components if let Err(e) = database::init(tx) { @@ -436,3 +389,37 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> { // Success Ok(()) } + +fn update_actions( + tab_page: &TabPage, + index: &Rc>>>, + window_action: &Rc, +) { + if let Some(item) = index.borrow().get(tab_page) { + window_action + .home + .simple_action + .set_enabled(item.action.home.is_enabled()); + window_action + .reload + .simple_action + .set_enabled(item.action.reload.is_enabled()); + window_action + .history_back + .simple_action + .set_enabled(item.action.history.back.is_enabled()); + window_action + .history_forward + .simple_action + .set_enabled(item.action.history.forward.is_enabled()); + return; + } + + window_action.home.simple_action.set_enabled(false); + window_action.reload.simple_action.set_enabled(false); + window_action.history_back.simple_action.set_enabled(false); + window_action + .history_forward + .simple_action + .set_enabled(false); +} diff --git a/src/app/browser/window/tab/item.rs b/src/app/browser/window/tab/item.rs index a834fac6..dd3d3bf3 100644 --- a/src/app/browser/window/tab/item.rs +++ b/src/app/browser/window/tab/item.rs @@ -95,14 +95,13 @@ impl Item { this.set_enabled(false); if let Some(uri) = page.navigation.request.home() { let request = uri.to_string(); - page.navigation.request.widget.entry.set_text(&request); + page.navigation.request.entry.set_text(&request); client.handle(&request, true); } } }); action.ident.connect_activate({ - let browser_action = browser_action.clone(); let page = page.clone(); let parent = tab_view.clone().upcast::(); let profile = profile.clone(); @@ -111,12 +110,8 @@ impl Item { if let Some(uri) = page.navigation.request.uri() { let scheme = uri.scheme(); if scheme == "gemini" || scheme == "titan" { - return identity::default( - (&browser_action, &window_action), - &profile, - &uri, - ) - .present(Some(&parent)); + return identity::default(&window_action, &profile, &uri) + .present(Some(&parent)); } } identity::unsupported().present(Some(&parent)); @@ -128,7 +123,7 @@ impl Item { let client = client.clone(); move |request, is_history| { if let Some(text) = request { - page.navigation.request.widget.entry.set_text(&text); + page.navigation.request.entry.set_text(&text); client.handle(&text, is_history); } } @@ -138,13 +133,13 @@ impl Item { let page = page.clone(); let client = client.clone(); move |_, _| { - client.handle(&page.navigation.request.widget.entry.text(), false); + client.handle(&page.navigation.request.entry.text(), false); } }); // Handle immediately on request if let Some(text) = request { - page.navigation.request.widget.entry.set_text(text); + page.navigation.request.entry.set_text(text); if is_load { client.handle(text, true); } @@ -162,15 +157,17 @@ impl Item { // Actions pub fn update(&self) { // Update self actions - self.action - .home - .set_enabled(self.page.navigation.request.home().is_some_and(|home| { - home.to_string() != self.page.navigation.request.widget.entry.text() - })); + self.action.home.set_enabled( + self.page + .navigation + .request + .home() + .is_some_and(|home| home.to_string() != self.page.navigation.request.entry.text()), + ); self.action .reload - .set_enabled(!self.page.navigation.request.widget.entry.text().is_empty()); + .set_enabled(!self.page.navigation.request.entry.text().is_empty()); // Update child components self.page.update(); diff --git a/src/app/browser/window/tab/item/client.rs b/src/app/browser/window/tab/item/client.rs index 14dc7cc1..0e0fe916 100644 --- a/src/app/browser/window/tab/item/client.rs +++ b/src/app/browser/window/tab/item/client.rs @@ -9,7 +9,7 @@ use feature::Feature; use gtk::{ gio::Cancellable, glib::{Uri, UriFlags}, - prelude::{CancellableExt, EditableExt, EntryExt}, + prelude::{ActionExt, CancellableExt, EditableExt, EntryExt}, }; use std::{cell::Cell, rc::Rc}; use subject::Subject; @@ -43,12 +43,8 @@ impl Client { /// Route tab item `request` to protocol driver /// * or `navigation` entry if the value not provided pub fn handle(&self, request: &str, is_snap_history: bool) { - // Move focus out from navigation entry - self.subject - .page - .browser_action - .escape - .activate_stateful_once(Some(self.subject.page.id.as_str().into())); + // Move focus out from navigation entry @TODO + self.subject.page.browser_action.escape.activate(None); // Initially disable find action self.subject @@ -66,7 +62,6 @@ impl Client { .page .navigation .request - .widget .entry .set_progress_fraction(0.1); @@ -96,7 +91,6 @@ impl Client { .page .navigation .request - .widget .entry .set_progress_fraction(0.0); subject.tab_page.set_loading(false); @@ -207,7 +201,7 @@ fn search(query: &str) -> Uri { /// Make new history record in related components /// * optional [Uri](https://docs.gtk.org/glib/struct.Uri.html) reference wanted only for performance reasons, to not parse it twice fn snap_history(subject: &Rc, uri: Option<&Uri>) { - let request = subject.page.navigation.request.widget.entry.text(); + let request = subject.page.navigation.request.entry.text(); // Add new record into the global memory index (used in global menu) // * if the `Uri` is `None`, try parse it from `request` diff --git a/src/app/browser/window/tab/item/client/driver/gemini.rs b/src/app/browser/window/tab/item/client/driver/gemini.rs index 43f84741..d30dba70 100644 --- a/src/app/browser/window/tab/item/client/driver/gemini.rs +++ b/src/app/browser/window/tab/item/client/driver/gemini.rs @@ -60,7 +60,6 @@ impl Gemini { .page .navigation .request - .widget .entry .set_progress_fraction(progress_fraction); } @@ -135,7 +134,6 @@ impl Gemini { .page .navigation .request - .widget .entry .set_progress_fraction(0.0); self.subject.tab_page.set_loading(false); @@ -205,7 +203,7 @@ fn handle( Some(1024), ); } - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&title); redirects.replace(0); // reset @@ -277,7 +275,7 @@ fn handle( } }, ); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -300,7 +298,7 @@ fn handle( Some(title) => title.into(), // @TODO None => uri_to_title(&uri), }); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.page.window_action .find @@ -311,7 +309,7 @@ fn handle( Err(e) => { let status = subject.page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -355,7 +353,7 @@ fn handle( subject.tab_page.set_title(&status.title()); } } - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); redirects.replace(0); // reset }, @@ -364,7 +362,7 @@ fn handle( Err(e) => { let status = subject.page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -378,7 +376,7 @@ fn handle( .content .to_status_mime(mime, Some((&subject.page.item_action, &uri))); status.set_description(Some(&format!("Content type `{mime}` yet not supported"))); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -387,7 +385,7 @@ fn handle( None => { let status = subject.page.content.to_status_failure(); status.set_description(Some("MIME type not found")); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -425,7 +423,7 @@ fn handle( if total > 5 { let status = subject.page.content.to_status_failure(); status.set_description(Some("Redirection limit reached")); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -436,7 +434,7 @@ fn handle( || uri.host() != target.host() { let status = subject.page.content.to_status_failure(); status.set_description(Some("External redirects not allowed by protocol specification")); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -445,7 +443,6 @@ fn handle( if matches!(response.meta.status, Status::PermanentRedirect) { subject.page.navigation .request - .widget .entry .set_text(&uri.to_string()); } @@ -456,7 +453,7 @@ fn handle( Err(e) => { let status = subject.page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -465,7 +462,7 @@ fn handle( None => { let status = subject.page.content.to_status_failure(); status.set_description(Some("Redirection target not found")); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -484,7 +481,7 @@ fn handle( None => response.meta.status.to_string(), })); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -492,7 +489,7 @@ fn handle( error => { let status = subject.page.content.to_status_failure(); status.set_description(Some(&error.to_string())); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset @@ -502,7 +499,7 @@ fn handle( Err(e) => { let status = subject.page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.request.widget.entry.set_progress_fraction(0.0); + subject.page.navigation.request.entry.set_progress_fraction(0.0); subject.tab_page.set_loading(false); subject.tab_page.set_title(&status.title()); redirects.replace(0); // reset diff --git a/src/app/browser/window/tab/item/identity.rs b/src/app/browser/window/tab/item/identity.rs index c210a014..13ed006f 100644 --- a/src/app/browser/window/tab/item/identity.rs +++ b/src/app/browser/window/tab/item/identity.rs @@ -4,17 +4,13 @@ mod unsupported; use default::Default; use unsupported::Unsupported; -use super::{BrowserAction, Profile, WindowAction}; +use super::{Profile, WindowAction}; use gtk::glib::Uri; use std::rc::Rc; /// Create new identity widget for Gemini protocol match given URI -pub fn default( - action: (&Rc, &Rc), - profile: &Rc, - request: &Uri, -) -> Default { - Default::build(action, profile, request) +pub fn default(window_action: &Rc, profile: &Rc, request: &Uri) -> Default { + Default::build(window_action, profile, request) } /// Create new identity widget for unknown request diff --git a/src/app/browser/window/tab/item/identity/default.rs b/src/app/browser/window/tab/item/identity/default.rs index 4c997580..18133e90 100644 --- a/src/app/browser/window/tab/item/identity/default.rs +++ b/src/app/browser/window/tab/item/identity/default.rs @@ -1,7 +1,7 @@ mod widget; use widget::{form::list::item::value::Value, Widget}; -use super::{BrowserAction, Profile, WindowAction}; +use super::{Profile, WindowAction}; use gtk::{glib::Uri, prelude::IsA}; use std::rc::Rc; @@ -14,26 +14,12 @@ impl Default { // Construct /// Create new `Self` for given `Profile` - pub fn build( - (browser_action, window_action): (&Rc, &Rc), - profile: &Rc, - request: &Uri, - ) -> Self { + pub fn build(window_action: &Rc, profile: &Rc, request: &Uri) -> Self { // Init widget - let widget = Rc::new(Widget::build( - (browser_action, window_action), - profile, - request, - )); + let widget = Rc::new(Widget::build(profile, request)); // Init events - widget.on_cancel({ - let browser_action = browser_action.clone(); - move || browser_action.update.activate(None) - }); - widget.on_apply({ - let browser_action = browser_action.clone(); let profile = profile.clone(); let request = request.clone(); let widget = widget.clone(); @@ -79,7 +65,6 @@ impl Default { } // Apply changes - browser_action.update.activate(None); window_action.reload.activate(); } }); diff --git a/src/app/browser/window/tab/item/identity/default/widget.rs b/src/app/browser/window/tab/item/identity/default/widget.rs index eacd5d6c..3c35ec78 100644 --- a/src/app/browser/window/tab/item/identity/default/widget.rs +++ b/src/app/browser/window/tab/item/identity/default/widget.rs @@ -4,10 +4,7 @@ pub mod form; use action::Action as WidgetAction; use form::{list::item::value::Value, Form}; -use crate::{ - app::browser::{action::Action as BrowserAction, window::action::Action as WindowAction}, - Profile, -}; +use crate::Profile; use adw::{ prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual}, AlertDialog, ResponseAppearance, @@ -36,20 +33,12 @@ impl Widget { // Constructors /// Create new `Self` - pub fn build( - (browser_action, window_action): (&Rc, &Rc), - profile: &Rc, - request: &Uri, - ) -> Self { + pub fn build(profile: &Rc, request: &Uri) -> Self { // Init actions - let widget_action = Rc::new(WidgetAction::new()); + let action = Rc::new(WidgetAction::new()); // Init child container - let form = Rc::new(Form::build( - (browser_action, window_action, &widget_action), - profile, - request, - )); + let form = Rc::new(Form::build(&action, profile, request)); // Init main widget let alert_dialog = AlertDialog::builder() @@ -76,7 +65,7 @@ impl Widget { alert_dialog.set_response_appearance(RESPONSE_CANCEL.0, ResponseAppearance::Destructive); */ // Init events - widget_action.update.connect_activate({ + action.update.connect_activate({ let form = form.clone(); let alert_dialog = alert_dialog.clone(); move || { @@ -89,7 +78,7 @@ impl Widget { }); // Make initial update - widget_action.update.activate(); + action.update.activate(); // Return new activated `Self` Self { @@ -116,20 +105,6 @@ impl Widget { }); } - /// Callback wrapper to cancel - /// [response](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/signal.AlertDialog.response.html) - /// * return require reload state - pub fn on_cancel(&self, callback: impl Fn() + 'static) { - self.alert_dialog - .connect_response(Some(RESPONSE_CANCEL.0), move |this, response| { - // Prevent double-click action - this.set_response_enabled(response, false); - - // Result - callback() - }); - } - /// Show dialog with new preset pub fn present(&self, parent: Option<&impl IsA>) { self.alert_dialog.present(parent) diff --git a/src/app/browser/window/tab/item/identity/default/widget/form.rs b/src/app/browser/window/tab/item/identity/default/widget/form.rs index 71807395..39bc358f 100644 --- a/src/app/browser/window/tab/item/identity/default/widget/form.rs +++ b/src/app/browser/window/tab/item/identity/default/widget/form.rs @@ -13,10 +13,7 @@ use name::Name; use save::Save; use super::WidgetAction; -use crate::{ - app::browser::{action::Action as BrowserAction, window::action::Action as WindowAction}, - Profile, -}; +use crate::Profile; use gtk::{glib::Uri, prelude::BoxExt, Box, Orientation}; use std::rc::Rc; @@ -37,27 +34,14 @@ impl Form { // Constructors /// Create new `Self` - pub fn build( - (browser_action, _window_action, widget_action): ( - &Rc, - &Rc, - &Rc, - ), - profile: &Rc, - request: &Uri, - ) -> Self { + pub fn build(widget_action: &Rc, profile: &Rc, request: &Uri) -> Self { // Init components let list = Rc::new(List::build(widget_action, profile, request)); let file = Rc::new(File::build(widget_action)); let name = Rc::new(Name::build(widget_action)); let save = Rc::new(Save::build(profile, &list)); let drop = Rc::new(Drop::build(profile, &list)); - let exit = Rc::new(Exit::build( - (browser_action, widget_action), - profile, - &list, - request, - )); + let exit = Rc::new(Exit::build(widget_action, profile, &list, request)); // Init main container let g_box = Box::builder().orientation(Orientation::Vertical).build(); diff --git a/src/app/browser/window/tab/item/identity/default/widget/form/exit.rs b/src/app/browser/window/tab/item/identity/default/widget/form/exit.rs index ae49a6d7..8321d69e 100644 --- a/src/app/browser/window/tab/item/identity/default/widget/form/exit.rs +++ b/src/app/browser/window/tab/item/identity/default/widget/form/exit.rs @@ -2,7 +2,7 @@ use super::{ list::{item::Value, List}, WidgetAction, }; -use crate::{app::browser::Action as BrowserAction, Profile}; +use crate::Profile; use adw::{ prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual}, AlertDialog, ResponseAppearance, @@ -34,7 +34,7 @@ impl Exit { /// Create new `Self` pub fn build( - (browser_action, widget_action): (&Rc, &Rc), + widget_action: &Rc, profile: &Rc, list: &Rc, request: &Uri, @@ -49,7 +49,6 @@ impl Exit { // Init events button.connect_clicked({ - let browser_action = browser_action.clone(); let button = button.clone(); let list = list.clone(); let profile = profile.clone(); @@ -84,7 +83,6 @@ impl Exit { // Connect confirmation event alert_dialog.connect_response(Some(RESPONSE_CONFIRM.0), { - let browser_action = browser_action.clone(); let button = button.clone(); let list = list.clone(); let profile = profile.clone(); @@ -111,7 +109,6 @@ impl Exit { button.set_label(&e.to_string()) } } - browser_action.update.activate(None); widget_action.update.activate(); } }); diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index df95651d..192cd7e9 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -54,7 +54,7 @@ impl Page { let navigation = Rc::new(Navigation::build( profile, - (browser_action, window_action, tab_action, item_action), + (window_action, tab_action, item_action), )); let input = Rc::new(Input::new()); @@ -92,7 +92,7 @@ impl Page { let result = match self .profile .bookmark - .toggle(self.navigation.request.widget.entry.text().as_str()) + .toggle(self.navigation.request.entry.text().as_str()) { Ok(result) => Ok(result), Err(_) => Err(Error::Bookmark), // @TODO diff --git a/src/app/browser/window/tab/item/page/navigation.rs b/src/app/browser/window/tab/item/page/navigation.rs index f1e7da80..95af45c7 100644 --- a/src/app/browser/window/tab/item/page/navigation.rs +++ b/src/app/browser/window/tab/item/page/navigation.rs @@ -6,7 +6,7 @@ mod reload; mod request; mod widget; -use super::{BrowserAction, ItemAction, Profile, TabAction, WindowAction}; +use super::{ItemAction, Profile, TabAction, WindowAction}; use bookmark::Bookmark; use gtk::{Box, Button}; use history::History; @@ -29,8 +29,7 @@ pub struct Navigation { impl Navigation { pub fn build( profile: &Rc, - (browser_action, window_action, tab_action, item_action): ( - &Rc, + (window_action, tab_action, item_action): ( &Rc, &Rc, &Rc, @@ -39,7 +38,7 @@ impl Navigation { // init children components let history = Box::history((window_action, tab_action, item_action)); - let request = Rc::new(Request::build((browser_action, item_action))); + let request = Rc::new(Request::build(item_action, profile)); let reload = Button::reload((window_action, tab_action, item_action), &request); let home = Button::home((window_action, tab_action, item_action), &request); let bookmark = Button::bookmark(window_action); @@ -49,7 +48,7 @@ impl Navigation { &home, &history, &reload, - &request.widget.entry, // @TODO + &request.entry, // @TODO &bookmark, )); @@ -73,8 +72,6 @@ impl Navigation { // update children components self.bookmark .update(self.profile.bookmark.get(&request).is_ok()); - self.request - .update(self.profile.identity.get(&request).is_some()); } pub fn clean( diff --git a/src/app/browser/window/tab/item/page/navigation/request.rs b/src/app/browser/window/tab/item/page/navigation/request.rs index 8b55aaba..cbd485ae 100644 --- a/src/app/browser/window/tab/item/page/navigation/request.rs +++ b/src/app/browser/window/tab/item/page/navigation/request.rs @@ -1,38 +1,132 @@ mod database; -mod test; -mod widget; +mod primary_icon; -use widget::Widget; +use primary_icon::PrimaryIcon; -use crate::app::browser::{window::tab::item::Action as ItemAction, Action as BrowserAction}; +use super::{ItemAction, Profile}; use gtk::{ glib::{gformat, GString, Uri, UriFlags}, - prelude::EditableExt, + prelude::{EditableExt, EntryExt, WidgetExt}, + Entry, EntryIconPosition, StateFlags, }; use sqlite::Transaction; -use std::rc::Rc; +use std::{cell::Cell, rc::Rc}; + +const PLACEHOLDER_TEXT: &str = "URL or search term..."; -// Main pub struct Request { - pub widget: Rc, + pub entry: Entry, } impl Request { // Constructors /// Build new `Self` - pub fn build((browser_action, item_action): (&Rc, &Rc)) -> Self { - Self { - widget: Rc::new(Widget::build((browser_action, item_action))), - } + pub fn build(item_action: &Rc, profile: &Rc) -> Self { + // Init main widget + let entry = Entry::builder() + .placeholder_text(PLACEHOLDER_TEXT) + .secondary_icon_tooltip_text("Go to the location") + .hexpand(true) + .build(); + + // Connect events + entry.connect_icon_release({ + let item_action = item_action.clone(); + move |this, position| match position { + EntryIconPosition::Primary => item_action.ident.activate(), // @TODO PrimaryIcon impl + EntryIconPosition::Secondary => item_action.load.activate(Some(&this.text()), true), + _ => todo!(), // unexpected + } + }); + + entry.connect_has_focus_notify(|this| { + if this.focus_child().is_some_and(|text| text.has_focus()) { + this.set_secondary_icon_name(Some("pan-end-symbolic")); + } else { + this.set_secondary_icon_name(None); + this.select_region(0, 0); + } + }); + + entry.connect_changed({ + let profile = profile.clone(); + let item_action = item_action.clone(); + move |this| { + // Update actions + item_action.reload.set_enabled(!this.text().is_empty()); + item_action + .home + .set_enabled(home(uri(&this.text())).is_some()); + + // Update primary icon + this.first_child().unwrap().remove_css_class("success"); // @TODO handle + + this.set_primary_icon_activatable(false); + this.set_primary_icon_sensitive(false); + + match primary_icon::from(&this.text()) { + PrimaryIcon::Download { name, tooltip } => { + this.set_primary_icon_name(Some(name)); + this.set_primary_icon_tooltip_text(Some(tooltip)); + } + PrimaryIcon::Gemini { name, tooltip } + | PrimaryIcon::Titan { name, tooltip } => { + this.set_primary_icon_activatable(true); + this.set_primary_icon_sensitive(true); + this.set_primary_icon_name(Some(name)); + if profile.identity.get(&strip_prefix(this.text())).is_some() { + this.first_child().unwrap().add_css_class("success"); // @TODO handle + this.set_primary_icon_tooltip_text(Some(tooltip.1)); + } else { + this.set_primary_icon_tooltip_text(Some(tooltip.0)); + } + } + PrimaryIcon::Search { name, tooltip } => { + this.set_primary_icon_name(Some(name)); + this.set_primary_icon_tooltip_text(Some(tooltip)); + } + PrimaryIcon::Source { name, tooltip } => { + this.set_primary_icon_name(Some(name)); + this.set_primary_icon_tooltip_text(Some(tooltip)); + } + } + } + }); + + entry.connect_activate({ + let item_action = item_action.clone(); + move |entry| { + item_action.load.activate(Some(&entry.text()), true); + } + }); + + entry.connect_state_flags_changed({ + // Define last focus state container + let has_focus = Cell::new(false); + move |entry, state| { + // Select entire text on first click (release) + // this behavior implemented in most web-browsers, + // to simply overwrite current request with new value + // Note: + // * Custom GestureClick is not an option here, as GTK Entry has default controller + // * This is experimental feature does not follow native GTK behavior @TODO make optional + if !has_focus.take() + && state.contains(StateFlags::ACTIVE | StateFlags::FOCUS_WITHIN) + && entry.selection_bounds().is_none() + { + entry.select_region(0, entry.text_length().into()); + } + // Update last focus state + has_focus.replace(state.contains(StateFlags::FOCUS_WITHIN)); + } + }); + + // Return activated `Self` + Self { entry } } // Actions - - pub fn update(&self, is_identity_active: bool) { - self.widget.update(is_identity_active); - } - pub fn clean( &self, transaction: &Transaction, @@ -44,7 +138,7 @@ impl Request { match database::delete(transaction, &record.id) { Ok(_) => { // Delegate clean action to the item childs - self.widget.clean(transaction, &record.id)?; + // nothing yet.. } Err(e) => return Err(e.to_string()), } @@ -64,8 +158,12 @@ impl Request { match database::select(transaction, app_browser_window_tab_item_page_navigation_id) { Ok(records) => { for record in records { + if let Some(text) = record.text { + self.entry.set_text(&text); + } + // Delegate restore action to the item childs - self.widget.restore(transaction, &record.id)?; + // nothing yet.. } } Err(e) => return Err(e.to_string()), @@ -79,12 +177,22 @@ impl Request { transaction: &Transaction, app_browser_window_tab_item_page_navigation_id: &i64, ) -> Result<(), String> { - match database::insert(transaction, app_browser_window_tab_item_page_navigation_id) { + // Keep value in memory until operation complete + let text = self.entry.text(); + + match database::insert( + transaction, + app_browser_window_tab_item_page_navigation_id, + match text.is_empty() { + true => None, + false => Some(text.as_str()), + }, + ) { Ok(_) => { - let id = database::last_insert_id(transaction); + // let id = database::last_insert_id(transaction); // Delegate save action to childs - self.widget.save(transaction, &id)?; + // nothing yet.. } Err(e) => return Err(e.to_string()), } @@ -95,48 +203,19 @@ impl Request { // Setters pub fn to_download(&self) { - self.widget.entry.set_text(&self.download()); + self.entry.set_text(&self.download()); } pub fn to_source(&self) { - self.widget.entry.set_text(&self.source()); + self.entry.set_text(&self.source()); } // Getters - /// Try get current request value as [Uri](https://docs.gtk.org/glib/struct.Uri.html) - /// * `strip_prefix` on parse - pub fn uri(&self) -> Option { - match Uri::parse(&strip_prefix(self.widget.entry.text()), UriFlags::NONE) { - Ok(uri) => Some(uri), - _ => None, - } - } - /// Get current request value without system prefix /// * the `prefix` is not `scheme` pub fn strip_prefix(&self) -> GString { - strip_prefix(self.widget.entry.text()) - } - - /// Parse home [Uri](https://docs.gtk.org/glib/struct.Uri.html) of `Self` - pub fn home(&self) -> Option { - self.uri().map(|uri| { - Uri::build( - UriFlags::NONE, - &if uri.scheme() == "titan" { - GString::from("gemini") - } else { - uri.scheme() - }, - uri.userinfo().as_deref(), - uri.host().as_deref(), - uri.port(), - "/", - None, - None, - ) - }) + strip_prefix(self.entry.text()) } /// Get request value in `download:` format @@ -148,13 +227,37 @@ impl Request { pub fn source(&self) -> GString { gformat!("source:{}", self.strip_prefix()) } + + /// Try get current request value as [Uri](https://docs.gtk.org/glib/struct.Uri.html) + /// * `strip_prefix` on parse + pub fn uri(&self) -> Option { + uri(&strip_prefix(self.entry.text())) + } + + /// Try build home [Uri](https://docs.gtk.org/glib/struct.Uri.html) for `Self` + pub fn home(&self) -> Option { + home(self.uri()) + } } // Tools +pub fn migrate(tx: &Transaction) -> Result<(), String> { + // Migrate self components + if let Err(e) = database::init(tx) { + return Err(e.to_string()); + } + + // Delegate migration to childs + // nothing yet.. + + // Success + Ok(()) +} + /// Strip system prefix from request string /// * the `prefix` is not `scheme` -pub fn strip_prefix(mut request: GString) -> GString { +fn strip_prefix(mut request: GString) -> GString { if let Some(postfix) = request.strip_prefix("source:") { request = postfix.into() }; @@ -166,15 +269,29 @@ pub fn strip_prefix(mut request: GString) -> GString { request } // @TODO move prefix features to page client -pub fn migrate(tx: &Transaction) -> Result<(), String> { - // Migrate self components - if let Err(e) = database::init(tx) { - return Err(e.to_string()); +fn uri(value: &str) -> Option { + match Uri::parse(value, UriFlags::NONE) { + Ok(uri) => Some(uri), + _ => None, } - - // Delegate migration to childs - widget::migrate(tx)?; - - // Success - Ok(()) +} + +/// Parse home [Uri](https://docs.gtk.org/glib/struct.Uri.html) for `subject` +fn home(subject: Option) -> Option { + subject.map(|uri| { + Uri::build( + UriFlags::NONE, + &if uri.scheme() == "titan" { + GString::from("gemini") + } else { + uri.scheme() + }, + uri.userinfo().as_deref(), + uri.host().as_deref(), + uri.port(), + "/", + None, + None, + ) + }) } diff --git a/src/app/browser/window/tab/item/page/navigation/request/database.rs b/src/app/browser/window/tab/item/page/navigation/request/database.rs index 8e22c90d..da181106 100644 --- a/src/app/browser/window/tab/item/page/navigation/request/database.rs +++ b/src/app/browser/window/tab/item/page/navigation/request/database.rs @@ -3,6 +3,7 @@ use sqlite::{Error, Transaction}; pub struct Table { pub id: i64, // pub app_browser_window_tab_item_page_navigation_id: i64, not in use + pub text: Option, // can be stored as NULL } pub fn init(tx: &Transaction) -> Result { @@ -11,6 +12,7 @@ pub fn init(tx: &Transaction) -> Result { ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `app_browser_window_tab_item_page_navigation_id` INTEGER NOT NULL, + `text` VARCHAR(1024), FOREIGN KEY (`app_browser_window_tab_item_page_navigation_id`) REFERENCES `app_browser_window_tab_item_page_navigation`(`id`) )", @@ -21,12 +23,14 @@ pub fn init(tx: &Transaction) -> Result { pub fn insert( tx: &Transaction, app_browser_window_tab_item_page_navigation_id: &i64, + text: Option<&str>, ) -> Result { tx.execute( "INSERT INTO `app_browser_window_tab_item_page_navigation_request` ( - `app_browser_window_tab_item_page_navigation_id` - ) VALUES (?)", - [app_browser_window_tab_item_page_navigation_id], + `app_browser_window_tab_item_page_navigation_id`, + `text` + ) VALUES (?, ?)", + (app_browser_window_tab_item_page_navigation_id, text), ) } @@ -36,7 +40,8 @@ pub fn select( ) -> Result, Error> { let mut stmt = tx.prepare( "SELECT `id`, - `app_browser_window_tab_item_page_navigation_id` + `app_browser_window_tab_item_page_navigation_id`, + `text` FROM `app_browser_window_tab_item_page_navigation_request` WHERE `app_browser_window_tab_item_page_navigation_id` = ?", )?; @@ -45,6 +50,7 @@ pub fn select( Ok(Table { id: row.get(0)?, // app_browser_window_tab_item_page_navigation_id: row.get(1)?, not in use + text: row.get(2)?, }) })?; @@ -65,6 +71,7 @@ pub fn delete(tx: &Transaction, id: &i64) -> Result { ) } +/* not in use pub fn last_insert_id(tx: &Transaction) -> i64 { tx.last_insert_rowid() -} +} */ diff --git a/src/app/browser/window/tab/item/page/navigation/request/widget/primary_icon.rs b/src/app/browser/window/tab/item/page/navigation/request/primary_icon.rs similarity index 100% rename from src/app/browser/window/tab/item/page/navigation/request/widget/primary_icon.rs rename to src/app/browser/window/tab/item/page/navigation/request/primary_icon.rs diff --git a/src/app/browser/window/tab/item/page/navigation/request/test.rs b/src/app/browser/window/tab/item/page/navigation/request/test.rs deleted file mode 100644 index b96e2b70..00000000 --- a/src/app/browser/window/tab/item/page/navigation/request/test.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn strip_prefix() { - assert_eq!(super::strip_prefix("source:gemini".into()), "gemini"); - assert_eq!(super::strip_prefix("download:gemini".into()), "gemini"); -} diff --git a/src/app/browser/window/tab/item/page/navigation/request/widget.rs b/src/app/browser/window/tab/item/page/navigation/request/widget.rs deleted file mode 100644 index d4b3c416..00000000 --- a/src/app/browser/window/tab/item/page/navigation/request/widget.rs +++ /dev/null @@ -1,286 +0,0 @@ -mod database; -mod primary_icon; - -use primary_icon::PrimaryIcon; - -use super::{BrowserAction, ItemAction}; -use gtk::{ - glib::{timeout_add_local, ControlFlow, SourceId}, - prelude::{EditableExt, EntryExt, WidgetExt}, - Entry, EntryIconPosition, StateFlags, -}; -use sqlite::Transaction; -use std::{ - cell::{Cell, RefCell}, - rc::Rc, - time::Duration, -}; - -const PLACEHOLDER_TEXT: &str = "URL or search term..."; - -// Progress bar animation setup -const PROGRESS_ANIMATION_STEP: f64 = 0.05; -const PROGRESS_ANIMATION_TIME: u64 = 20; //ms - -struct Progress { - fraction: RefCell, - source_id: RefCell>, -} - -pub struct Widget { - pub entry: Entry, - progress: Rc, -} - -impl Widget { - // Constructors - - /// Build new `Self` - pub fn build((browser_action, item_action): (&Rc, &Rc)) -> Self { - // Init animated progress bar state - let progress = Rc::new(Progress { - fraction: RefCell::new(0.0), - source_id: RefCell::new(None), - }); - - // Init main widget - let entry = Entry::builder() - .placeholder_text(PLACEHOLDER_TEXT) - .secondary_icon_tooltip_text("Go to the location") - .hexpand(true) - .build(); - - // Connect events - entry.connect_icon_release({ - let item_action = item_action.clone(); - move |this, position| match position { - EntryIconPosition::Primary => item_action.ident.activate(), // @TODO PrimaryIcon impl - EntryIconPosition::Secondary => item_action.load.activate(Some(&this.text()), true), - _ => todo!(), // unexpected - } - }); - - entry.connect_has_focus_notify(|this| { - if this.focus_child().is_some_and(|text| text.has_focus()) { - this.set_secondary_icon_name(Some("pan-end-symbolic")); - } else { - this.set_secondary_icon_name(None); - this.select_region(0, 0); - } - }); - - entry.connect_changed({ - let browser_action = browser_action.clone(); - move |_| { - browser_action.update.activate(None); - } - }); - - entry.connect_activate({ - let item_action = item_action.clone(); - move |entry| { - item_action.load.activate(Some(&entry.text()), true); - } - }); - - entry.connect_state_flags_changed({ - // Define last focus state container - let has_focus = Cell::new(false); - move |entry, state| { - // Select entire text on first click (release) - // this behavior implemented in most web-browsers, - // to simply overwrite current request with new value - // Note: - // * Custom GestureClick is not an option here, as GTK Entry has default controller - // * This is experimental feature does not follow native GTK behavior @TODO make optional - if !has_focus.take() - && state.contains(StateFlags::ACTIVE | StateFlags::FOCUS_WITHIN) - && entry.selection_bounds().is_none() - { - entry.select_region(0, entry.text_length().into()); - } - // Update last focus state - has_focus.replace(state.contains(StateFlags::FOCUS_WITHIN)); - } - }); - - // Return activated `Self` - Self { entry, progress } - } - - // Actions - pub fn clean( - &self, - transaction: &Transaction, - app_browser_window_tab_item_page_navigation_request_id: &i64, - ) -> Result<(), String> { - match database::select( - transaction, - app_browser_window_tab_item_page_navigation_request_id, - ) { - Ok(records) => { - for record in records { - match database::delete(transaction, &record.id) { - Ok(_) => { - // Delegate clean action to the item childs - // nothing yet.. - } - Err(e) => return Err(e.to_string()), - } - } - } - Err(e) => return Err(e.to_string()), - } - - Ok(()) - } - - pub fn restore( - &self, - transaction: &Transaction, - app_browser_window_tab_item_page_navigation_request_id: &i64, - ) -> Result<(), String> { - match database::select( - transaction, - app_browser_window_tab_item_page_navigation_request_id, - ) { - Ok(records) => { - for record in records { - if let Some(text) = record.text { - self.entry.set_text(&text); - } - - // Delegate restore action to the item childs - // nothing yet.. - } - } - Err(e) => return Err(e.to_string()), - } - - Ok(()) - } - - pub fn save( - &self, - transaction: &Transaction, - app_browser_window_tab_item_page_navigation_request_id: &i64, - ) -> Result<(), String> { - // Keep value in memory until operation complete - let text = self.entry.text(); - - match database::insert( - transaction, - app_browser_window_tab_item_page_navigation_request_id, - match text.is_empty() { - true => None, - false => Some(text.as_str()), - }, - ) { - Ok(_) => { - // let id = database::last_insert_id(transaction); - - // Delegate save action to childs - // nothing yet.. - } - Err(e) => return Err(e.to_string()), - } - - Ok(()) - } - - pub fn update(&self, is_identity_active: bool) { - // Update primary icon - self.entry - .first_child() - .unwrap() - .remove_css_class("success"); // @TODO handle - - self.entry.set_primary_icon_activatable(false); - self.entry.set_primary_icon_sensitive(false); - - match primary_icon::from(&self.entry.text()) { - PrimaryIcon::Download { name, tooltip } => { - self.entry.set_primary_icon_name(Some(name)); - self.entry.set_primary_icon_tooltip_text(Some(tooltip)); - } - PrimaryIcon::Gemini { name, tooltip } | PrimaryIcon::Titan { name, tooltip } => { - self.entry.set_primary_icon_activatable(true); - self.entry.set_primary_icon_sensitive(true); - self.entry.set_primary_icon_name(Some(name)); - if is_identity_active { - self.entry.first_child().unwrap().add_css_class("success"); // @TODO handle - self.entry.set_primary_icon_tooltip_text(Some(tooltip.1)); - } else { - self.entry.set_primary_icon_tooltip_text(Some(tooltip.0)); - } - } - PrimaryIcon::Search { name, tooltip } => { - self.entry.set_primary_icon_name(Some(name)); - self.entry.set_primary_icon_tooltip_text(Some(tooltip)); - } - PrimaryIcon::Source { name, tooltip } => { - self.entry.set_primary_icon_name(Some(name)); - self.entry.set_primary_icon_tooltip_text(Some(tooltip)); - } - } - - // Update progress - // * @TODO skip update animation for None value - let value = self.entry.progress_fraction(); - - // Update shared fraction on new value was changed - if value != self.progress.fraction.replace(value) { - // Start new frame on previous process function completed (`source_id` changed to None) - // If previous process still active, we have just updated shared fraction value before, to use it inside the active process - if self.progress.source_id.borrow().is_none() { - // Start new animation frame iterator, update `source_id` - self.progress.source_id.replace(Some(timeout_add_local( - Duration::from_millis(PROGRESS_ANIMATION_TIME), - { - // Clone async pointers dependency - let entry = self.entry.clone(); - let progress = self.progress.clone(); - - // Frame - move || { - // Animate - if *progress.fraction.borrow() > entry.progress_fraction() { - entry.set_progress_fraction( - // Currently, here is no outrange validation, seems that wrapper make this work @TODO - entry.progress_fraction() + PROGRESS_ANIMATION_STEP, - ); - return ControlFlow::Continue; - } - // Deactivate - progress.source_id.replace(None); - - // Reset on 100% (to hide progress bar) - // or, just await for new value request - if entry.progress_fraction() == 1.0 { - entry.set_progress_fraction(0.0); - } - - // Stop iteration - ControlFlow::Break - } - }, - ))); - } - } - } -} - -// Tools - -pub fn migrate(tx: &Transaction) -> Result<(), String> { - // Migrate self components - if let Err(e) = database::init(tx) { - return Err(e.to_string()); - } - - // Delegate migration to childs - // nothing yet.. - - // Success - Ok(()) -} diff --git a/src/app/browser/window/tab/item/page/navigation/request/widget/database.rs b/src/app/browser/window/tab/item/page/navigation/request/widget/database.rs deleted file mode 100644 index 345707d2..00000000 --- a/src/app/browser/window/tab/item/page/navigation/request/widget/database.rs +++ /dev/null @@ -1,80 +0,0 @@ -use sqlite::{Error, Transaction}; - -pub struct Table { - pub id: i64, - // pub app_browser_window_tab_item_page_navigation_request_id: i64, not in use - pub text: Option, // can be stored as NULL -} - -pub fn init(tx: &Transaction) -> Result { - tx.execute( - "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request_widget` - ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `app_browser_window_tab_item_page_navigation_request_id` INTEGER NOT NULL, - `text` VARCHAR(1024), - - FOREIGN KEY (`app_browser_window_tab_item_page_navigation_request_id`) REFERENCES `app_browser_window_tab_item_page_navigation_request`(`id`) - )", - [], - ) -} - -pub fn insert( - tx: &Transaction, - app_browser_window_tab_item_page_navigation_request_id: &i64, - text: Option<&str>, -) -> Result { - tx.execute( - "INSERT INTO `app_browser_window_tab_item_page_navigation_request_widget` ( - `app_browser_window_tab_item_page_navigation_request_id`, - `text` - ) VALUES (?, ?)", - (app_browser_window_tab_item_page_navigation_request_id, text), - ) -} - -pub fn select( - tx: &Transaction, - app_browser_window_tab_item_page_navigation_request_id: &i64, -) -> Result, Error> { - let mut stmt = tx.prepare( - "SELECT `id`, - `app_browser_window_tab_item_page_navigation_request_id`, - `text` - FROM `app_browser_window_tab_item_page_navigation_request_widget` - WHERE `app_browser_window_tab_item_page_navigation_request_id` = ?", - )?; - - let result = stmt.query_map( - [app_browser_window_tab_item_page_navigation_request_id], - |row| { - Ok(Table { - id: row.get(0)?, - // app_browser_window_tab_item_page_navigation_request_id: row.get(1)?, not in use - text: row.get(2)?, - }) - }, - )?; - - let mut records = Vec::new(); - - for record in result { - let table = record?; - records.push(table); - } - - Ok(records) -} - -pub fn delete(tx: &Transaction, id: &i64) -> Result { - tx.execute( - "DELETE FROM `app_browser_window_tab_item_page_navigation_request_widget` WHERE `id` = ?", - [id], - ) -} - -/* not in use -pub fn last_insert_id(tx: &Transaction) -> i64 { - tx.last_insert_rowid() -} */