diff --git a/src/app/browser/window/tab.rs b/src/app/browser/window/tab.rs index 2cc5d540..10449129 100644 --- a/src/app/browser/window/tab.rs +++ b/src/app/browser/window/tab.rs @@ -142,7 +142,7 @@ impl Tab { // Register dynamically created tab components in the HashMap index self.index .borrow_mut() - .insert(item.widget.tab_page.clone(), item.clone()); + .insert(item.page.tab_page.clone(), item.clone()); update_actions( &self.tab_view, @@ -305,7 +305,7 @@ impl Tab { // Register dynamically created tab item in the HashMap index self.index .borrow_mut() - .insert(item.widget.tab_page.clone(), item.clone()); + .insert(item.page.tab_page.clone(), item.clone()); } } Err(e) => return Err(e.to_string()), @@ -333,10 +333,10 @@ impl Tab { item.save( transaction, id, - self.tab_view.page_position(&item.widget.tab_page), - item.widget.tab_page.is_pinned(), - item.widget.tab_page.is_selected(), - item.widget.tab_page.needs_attention(), + self.tab_view.page_position(&item.page.tab_page), + item.page.tab_page.is_pinned(), + item.page.tab_page.is_selected(), + item.page.tab_page.needs_attention(), )?; } } diff --git a/src/app/browser/window/tab/item.rs b/src/app/browser/window/tab/item.rs index 7a848738..ee65ab2d 100644 --- a/src/app/browser/window/tab/item.rs +++ b/src/app/browser/window/tab/item.rs @@ -3,7 +3,6 @@ mod client; mod database; mod identity; mod page; -mod widget; use super::{Action as TabAction, BrowserAction, Position, WindowAction}; use crate::Profile; @@ -14,14 +13,12 @@ use gtk::prelude::{ActionMapExt, Cast}; use page::Page; use sqlite::Transaction; use std::rc::Rc; -use widget::Widget; pub struct Item { // Multi-protocol handler pub client: Rc, // Components pub page: Rc, - pub widget: Rc, pub action: Rc, } @@ -37,7 +34,7 @@ impl Item { &Rc, &Rc, ), - (position, request, is_pinned, is_selected, is_attention, is_load): ( + (position, request, is_pinned, is_selected, is_needs_attention, is_load): ( Position, Option<&str>, bool, @@ -63,18 +60,12 @@ impl Item { let page = Rc::new(Page::build( profile, (browser_action, window_action, tab_action, &action), - )); - - let widget = Rc::new(Widget::build( tab_view, - &page.g_box, - None, - position, - (is_pinned, is_selected, is_attention), + (position, is_pinned, is_selected, is_needs_attention), )); // Update tab loading indicator - let client = Rc::new(Client::init(&page, &widget.tab_page)); + let client = Rc::new(Client::init(&page)); // Connect events action.home.connect_activate({ @@ -143,7 +134,6 @@ impl Item { Self { client, page, - widget, action, } } @@ -160,7 +150,6 @@ impl Item { Ok(_) => { // Delegate clean action to the item childs self.page.clean(transaction, record.id)?; - self.widget.clean(transaction, record.id)?; } Err(e) => return Err(e.to_string()), } @@ -203,14 +192,13 @@ impl Item { None, record.is_pinned, record.is_selected, - record.is_attention, + record.is_needs_attention, false, ), )); // Delegate restore action to the item childs item.page.restore(transaction, record.id)?; - item.widget.restore(transaction, record.id)?; // Result items.push(item); @@ -229,7 +217,7 @@ impl Item { page_position: i32, is_pinned: bool, is_selected: bool, - is_attention: bool, + is_needs_attention: bool, ) -> Result<(), String> { match database::insert( transaction, @@ -237,14 +225,13 @@ impl Item { page_position, is_pinned, is_selected, - is_attention, + is_needs_attention, ) { Ok(_) => { let id = database::last_insert_id(transaction); // Delegate save action to childs self.page.save(transaction, id)?; - self.widget.save(transaction, id)?; } Err(e) => return Err(e.to_string()), } @@ -262,7 +249,6 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> { // Delegate migration to childs page::migrate(tx)?; - widget::migrate(tx)?; // Success Ok(()) diff --git a/src/app/browser/window/tab/item/client.rs b/src/app/browser/window/tab/item/client.rs index faa496ed..17948df6 100644 --- a/src/app/browser/window/tab/item/client.rs +++ b/src/app/browser/window/tab/item/client.rs @@ -1,9 +1,7 @@ mod driver; mod feature; -mod subject; use super::Page; -use adw::TabPage; use driver::Driver; use feature::Feature; use gtk::{ @@ -12,29 +10,23 @@ use gtk::{ prelude::{ActionExt, CancellableExt}, }; use std::{cell::Cell, rc::Rc}; -use subject::Subject; /// Multi-protocol client API for tab `Item` pub struct Client { cancellable: Cell, driver: Rc, - subject: Rc, + page: Rc, } impl Client { // Constructors /// Create new `Self` - pub fn init(page: &Rc, tab_page: &TabPage) -> Self { - let subject = Rc::new(Subject { - page: page.clone(), - tab_page: tab_page.clone(), - }); - + pub fn init(page: &Rc) -> Self { Self { cancellable: Cell::new(Cancellable::new()), - driver: Rc::new(Driver::build(&subject)), - subject, + driver: Rc::new(Driver::build(page)), + page: page.clone(), } } @@ -44,32 +36,29 @@ impl Client { /// * or `navigation` entry if the value not provided pub fn handle(&self, request: &str, is_snap_history: bool) { // Move focus out from navigation entry @TODO - self.subject.page.browser_action.escape.activate(None); + self.page.browser_action.escape.activate(None); // Initially disable find action - self.subject - .page + self.page .window_action .find .simple_action .set_enabled(false); // Reset widgets - self.subject.page.search.unset(); - self.subject.page.input.unset(); - self.subject.tab_page.set_title("Loading.."); - self.subject.page.navigation.set_progress_fraction(0.1); - - self.subject.tab_page.set_loading(true); + self.page.search.unset(); + self.page.input.unset(); + self.page.set_title("Loading.."); + self.page.set_progress(0.1); if is_snap_history { - snap_history(&self.subject, None); + snap_history(&self.page, None); } // run async resolver to detect Uri, scheme-less host, or search query lookup(request, self.cancellable(), { let driver = self.driver.clone(); - let subject = self.subject.clone(); + let page = self.page.clone(); move |feature, cancellable, result| { match result { // route by scheme @@ -77,18 +66,16 @@ impl Client { "gemini" | "titan" => driver.gemini.handle(uri, feature, cancellable), scheme => { // no scheme match driver, complete with failure message - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(&format!( "Scheme `{scheme}` yet not supported" ))); - subject.tab_page.set_title(&status.title()); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); + page.set_title(&status.title()); + page.set_progress(0.0); } }, // begin redirection to new address suggested - Err(uri) => subject - .page + Err(uri) => page .item_action .load .activate(Some(&uri.to_string()), false), @@ -190,22 +177,22 @@ 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(); +fn snap_history(page: &Page, uri: Option<&Uri>) { + let request = page.navigation.request(); // Add new record into the global memory index (used in global menu) // * if the `Uri` is `None`, try parse it from `request` match uri { - Some(uri) => subject.page.profile.history.memory.request.set(uri.clone()), + Some(uri) => page.profile.history.memory.request.set(uri.clone()), None => { // this case especially useful for some routes that contain redirects // maybe some parental optimization wanted @TODO - if let Some(uri) = subject.page.navigation.uri() { - subject.page.profile.history.memory.request.set(uri); + if let Some(uri) = page.navigation.uri() { + page.profile.history.memory.request.set(uri); } } } // Add new record into the page navigation history - subject.page.item_action.history.add(request, true) + page.item_action.history.add(request, true) } diff --git a/src/app/browser/window/tab/item/client/driver.rs b/src/app/browser/window/tab/item/client/driver.rs index 792ea816..9df84ff7 100644 --- a/src/app/browser/window/tab/item/client/driver.rs +++ b/src/app/browser/window/tab/item/client/driver.rs @@ -1,6 +1,6 @@ mod gemini; -use super::{Feature, Subject}; +use super::{Feature, Page}; use gemini::Gemini; use std::rc::Rc; @@ -13,9 +13,9 @@ impl Driver { // Constructors /// Build new `Self` - pub fn build(subject: &Rc) -> Self { + pub fn build(page: &Rc) -> Self { Driver { - gemini: Gemini::init(subject), + gemini: Gemini::init(page), } } } 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 47b0c003..170aae6c 100644 --- a/src/app/browser/window/tab/item/client/driver/gemini.rs +++ b/src/app/browser/window/tab/item/client/driver/gemini.rs @@ -1,17 +1,17 @@ -use super::{Feature, Subject}; +use super::{Feature, Page}; use ggemini::client::{ connection::response::{data::Text, meta::Status}, Client, Request, }; +use gtk::glib::Bytes; use gtk::glib::{GString, UriFlags}; use gtk::{ gdk::Texture, gdk_pixbuf::Pixbuf, gio::{Cancellable, SocketClientEvent}, glib::{Priority, Uri}, - prelude::SocketClientExt, + prelude::{FileExt, SocketClientExt}, }; -use gtk::{glib::Bytes, prelude::FileExt}; use std::{cell::Cell, path::MAIN_SEPARATOR, rc::Rc, time::Duration}; /// Multi-protocol client API for `Page` object @@ -21,22 +21,22 @@ pub struct Gemini { /// Validate redirection count by Gemini protocol specification redirects: Rc>, /// Handle target - subject: Rc, + page: Rc, } impl Gemini { // Constructors /// Create new `Self` - pub fn init(subject: &Rc) -> Self { + pub fn init(page: &Rc) -> Self { // Init supported protocol libraries let client = Rc::new(ggemini::Client::new()); // Listen for [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html) updates client.socket.connect_event({ - let subject = subject.clone(); + let page = page.clone(); move |_, event, _, _| { - let progress_fraction = match event { + page.set_progress(match event { // 0.1 reserved for handle begin SocketClientEvent::Resolving => 0.2, SocketClientEvent::Resolved => 0.3, @@ -49,21 +49,14 @@ impl Gemini { SocketClientEvent::TlsHandshaked => 0.9, SocketClientEvent::Complete => 1.0, _ => todo!(), // alert on API change - }; - - subject.tab_page.set_loading(progress_fraction > 0.0); - - subject - .page - .navigation - .set_progress_fraction(progress_fraction); + }) } }); Self { client, redirects: Rc::new(Cell::new(0)), - subject: subject.clone(), + page: page.clone(), } } @@ -76,15 +69,15 @@ impl Gemini { "gemini" => handle( Request::Gemini { uri }, self.client.clone(), - self.subject.clone(), + self.page.clone(), self.redirects.clone(), feature, cancellable, ), "titan" => { - self.subject.page.input.set_new_titan({ + self.page.input.set_new_titan({ let client = self.client.clone(); - let subject = self.subject.clone(); + let page = self.page.clone(); let redirects = self.redirects.clone(); move |data, _label| { handle( @@ -96,7 +89,7 @@ impl Gemini { token: None, // @TODO }, client.clone(), - subject.clone(), + page.clone(), redirects.clone(), feature.clone(), cancellable.clone(), @@ -123,9 +116,8 @@ impl Gemini { todo!()*/ } }); - self.subject.tab_page.set_title("Titan input"); - self.subject.page.navigation.set_progress_fraction(0.0); - self.subject.tab_page.set_loading(false); + self.page.set_title("Titan input"); + self.page.set_progress(0.0); } _ => panic!(), // unexpected } @@ -135,7 +127,7 @@ impl Gemini { fn handle( request: Request, client: Rc, - subject: Rc, + page: Rc, redirects: Rc>, feature: Rc, cancellable: Cancellable, @@ -147,8 +139,7 @@ fn handle( cancellable.clone(), // Search for user certificate match request // * @TODO this feature does not support multi-protocol yet - match subject - .page + match page .profile .identity .get(&uri.to_string()) @@ -160,12 +151,12 @@ fn handle( None => None, }, { - let subject = subject.clone(); + let page = page.clone(); let redirects = redirects.clone(); move |result| { // Remove input forms when redirection expected has not been applied (e.g. failure status) // @TODO implement input data recovery on error (it's also available before unset, but reference lost at this point) - subject.page.input.unset(); + page.input.unset(); // Begin result handle match result { @@ -178,30 +169,29 @@ fn handle( None => Status::Input.to_string(), }; if matches!(response.meta.status, Status::SensitiveInput) { - subject.page.input.set_new_sensitive( - subject.page.item_action.clone(), + page.input.set_new_sensitive( + page.item_action.clone(), uri, Some(&title), Some(1024), ); } else { - subject.page.input.set_new_response( - subject.page.item_action.clone(), + page.input.set_new_response( + page.item_action.clone(), uri, Some(&title), Some(1024), ); } - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&title); + page.set_progress(0.0); + page.set_title(&title); redirects.replace(0); // reset } // https://geminiprotocol.net/docs/protocol-specification.gmi#status-20 Status::Success => match *feature { Feature::Download => { // Init download widget - let status = subject.page.content.to_status_download( + let status = page.content.to_status_download( uri_to_title(&uri).trim_matches(MAIN_SEPARATOR), // grab default filename from base URI, // format FS entities &cancellable, @@ -262,9 +252,8 @@ fn handle( } }, ); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset }, _ => match response.meta.mime { @@ -276,36 +265,34 @@ fn handle( move |result| match result { Ok(text) => { let widget = if matches!(*feature, Feature::Source) { - subject.page.content.to_text_source(&text.to_string()) + page.content.to_text_source(&text.to_string()) } else { - subject.page.content.to_text_gemini(&uri, &text.to_string()) + page.content.to_text_gemini(&uri, &text.to_string()) }; - subject.page.search.set(Some(widget.text_view)); - subject.tab_page.set_title(&match widget.meta.title { + page.search.set(Some(widget.text_view)); + page.set_title(&match widget.meta.title { Some(title) => title.into(), // @TODO None => uri_to_title(&uri), }); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.page.window_action + page.set_progress(0.0); + page.window_action .find .simple_action .set_enabled(true); redirects.replace(0); // reset } Err(e) => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset }, }, ), "image/png" | "image/gif" | "image/jpeg" | "image/webp" => { // Final image size unknown, show loading widget - let status = subject.page.content.to_status_loading( + let status = page.content.to_status_loading( Some(Duration::from_secs(1)), // show if download time > 1 second ); @@ -322,7 +309,7 @@ fn handle( move |_, total| status.set_description(Some(&format!("Download: {total} bytes"))), { - let subject = subject.clone(); + let page = page.clone(); move |result| match result { Ok((memory_input_stream, _)) => { Pixbuf::from_stream_async( @@ -331,27 +318,25 @@ fn handle( move |result| { match result { Ok(buffer) => { - subject.tab_page.set_title(&uri_to_title(&uri)); - subject.page.content.to_image(&Texture::for_pixbuf(&buffer)); + page.set_title(&uri_to_title(&uri)); + page.content.to_image(&Texture::for_pixbuf(&buffer)); } Err(e) => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(e.message())); - subject.tab_page.set_title(&status.title()); + page.set_title(&status.title()); } } - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); + page.set_progress(0.0); redirects.replace(0); // reset }, ) } Err(e) => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset } } @@ -359,22 +344,20 @@ fn handle( ) } mime => { - let status = subject.page + let status = page .content - .to_status_mime(mime, Some((&subject.page.item_action, &uri))); + .to_status_mime(mime, Some((&page.item_action, &uri))); status.set_description(Some(&format!("Content type `{mime}` yet not supported"))); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset }, }, None => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some("MIME type not found")); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset }, } @@ -408,48 +391,43 @@ fn handle( let total = redirects.take() + 1; // Validate total redirects by protocol specification if total > 5 { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some("Redirection limit reached")); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset // Disallow external redirection by protocol restrictions } else if "gemini" != target.scheme() || uri.port() != target.port() || uri.host() != target.host() { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some("External redirects not allowed by protocol specification")); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset // Valid } else { if matches!(response.meta.status, Status::PermanentRedirect) { - subject.page.navigation - .set_request(&uri.to_string()); + page.navigation.set_request(&uri.to_string()); } redirects.replace(total); - subject.page.item_action.load.activate(Some(&target.to_string()), false); + page.item_action.load.activate(Some(&target.to_string()), false); } } Err(e) => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset } } None => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some("Redirection target not found")); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset } } @@ -460,33 +438,30 @@ fn handle( Status::CertificateUnauthorized | // https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid Status::CertificateInvalid => { - let status = subject.page.content.to_status_identity(); + let status = page.content.to_status_identity(); status.set_description(Some(&match response.meta.data { Some(data) => data.to_string(), None => response.meta.status.to_string(), })); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset } error => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(&error.to_string())); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset }, } } Err(e) => { - let status = subject.page.content.to_status_failure(); + let status = page.content.to_status_failure(); status.set_description(Some(&e.to_string())); - subject.page.navigation.set_progress_fraction(0.0); - subject.tab_page.set_loading(false); - subject.tab_page.set_title(&status.title()); + page.set_progress(0.0); + page.set_title(&status.title()); redirects.replace(0); // reset } } diff --git a/src/app/browser/window/tab/item/client/subject.rs b/src/app/browser/window/tab/item/client/subject.rs deleted file mode 100644 index fdfac3f3..00000000 --- a/src/app/browser/window/tab/item/client/subject.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::Page; -use adw::TabPage; -use std::rc::Rc; - -/// The subject for `Client` handler -pub struct Subject { - pub page: Rc, - pub tab_page: TabPage, -} diff --git a/src/app/browser/window/tab/item/database.rs b/src/app/browser/window/tab/item/database.rs index 833fab1d..6ed7e329 100644 --- a/src/app/browser/window/tab/item/database.rs +++ b/src/app/browser/window/tab/item/database.rs @@ -5,7 +5,7 @@ pub struct Table { // pub app_browser_window_tab_id: i64, not in use pub is_pinned: bool, pub is_selected: bool, - pub is_attention: bool, + pub is_needs_attention: bool, } pub fn init(tx: &Transaction) -> Result { @@ -17,7 +17,7 @@ pub fn init(tx: &Transaction) -> Result { `page_position` INTEGER NOT NULL, `is_pinned` INTEGER NOT NULL, `is_selected` INTEGER NOT NULL, - `is_attention` INTEGER NOT NULL, + `is_needs_attention` INTEGER NOT NULL, FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab`(`id`) )", @@ -31,7 +31,7 @@ pub fn insert( page_position: i32, is_pinned: bool, is_selected: bool, - is_attention: bool, + is_needs_attention: bool, ) -> Result { tx.execute( "INSERT INTO `app_browser_window_tab_item` ( @@ -39,14 +39,14 @@ pub fn insert( `page_position`, `is_pinned`, `is_selected`, - `is_attention` + `is_needs_attention` ) VALUES (?, ?, ?, ?, ?)", [ app_browser_window_tab_id, page_position as i64, is_pinned as i64, is_selected as i64, - is_attention as i64, + is_needs_attention as i64, ], ) } @@ -57,7 +57,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result Result, pub input: Rc, pub navigation: Rc, - pub g_box: Box, + // System + pub tab_page: TabPage, } impl Page { @@ -40,17 +43,16 @@ impl Page { &Rc, &Rc, ), + tab_view: &TabView, + (position, is_pinned, is_selected, is_needs_attention): (Position, bool, bool, bool), ) -> Self { // Init components let content = Rc::new(Content::build((window_action, item_action))); - let search = Rc::new(Search::new()); - let navigation = Rc::new(Navigation::build( profile, (window_action, tab_action, item_action), )); - let input = Rc::new(Input::new()); // Init main widget @@ -61,9 +63,29 @@ impl Page { g_box.append(&search.g_box); g_box.append(&input.clamp); + // Generate `TabPage` by append widget into given `TabView` + let tab_page = match position { + Position::After => match tab_view.selected_page() { + Some(page) => add(tab_view, &g_box, tab_view.page_position(&page) + 1), + None => tab_view.append(&g_box), + }, + Position::End => tab_view.append(&g_box), + Position::Number(value) => add(tab_view, &g_box, value), + }; + + // Setup + tab_page.set_needs_attention(is_needs_attention); + tab_page.set_title("New page"); + + tab_view.set_page_pinned(&tab_page, is_pinned); + if is_selected { + tab_view.set_selected_page(&tab_page); + } + // Done Self { profile: profile.clone(), + tab_page: tab_page.clone(), // Actions browser_action: browser_action.clone(), item_action: item_action.clone(), @@ -73,8 +95,6 @@ impl Page { search, input, navigation, - // Widget - g_box, } } @@ -137,6 +157,10 @@ impl Page { match database::select(transaction, app_browser_window_tab_item_id) { Ok(records) => { for record in records { + // Restore main widget + if let Some(title) = record.title { + self.set_title(title.as_str()); + } // Restore self by last record // Delegate restore action to the item childs self.navigation.restore(transaction, &record.id)?; @@ -158,7 +182,17 @@ impl Page { transaction: &Transaction, app_browser_window_tab_item_id: i64, ) -> Result<(), String> { - match database::insert(transaction, app_browser_window_tab_item_id) { + // Keep value in memory until operation complete + let title = self.tab_page.title(); + + match database::insert( + transaction, + app_browser_window_tab_item_id, + match title.is_empty() { + true => None, + false => Some(title.as_str()), + }, + ) { Ok(_) => { let id = database::last_insert_id(transaction); @@ -170,6 +204,19 @@ impl Page { Ok(()) } + + // Setters + + /// Set title for `Self` + /// * this method allows to keep `tab_page` isolated from driver implementation + pub fn set_title(&self, title: &str) { + self.tab_page.set_title(title); + } + + pub fn set_progress(&self, progress_fraction: f64) { + self.navigation.set_progress_fraction(progress_fraction); + self.tab_page.set_loading(progress_fraction > 0.0) + } } // Tools @@ -186,3 +233,17 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> { // Success Ok(()) } + +/// Create new [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html) +/// in [TabView](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabView.html) at given position +/// +/// * if given `position` match pinned tab, GTK will panic with notice: +/// adw_tab_view_insert: assertion 'position >= self->n_pinned_pages'\ +/// as the solution, prepend new page after pinned tabs in this case +fn add(tab_view: &TabView, child: &impl IsA, position: i32) -> TabPage { + if position > tab_view.n_pinned_pages() { + tab_view.insert(child, position) + } else { + tab_view.prepend(child) + } +} diff --git a/src/app/browser/window/tab/item/page/database.rs b/src/app/browser/window/tab/item/page/database.rs index 1f0ea6b3..034e800e 100644 --- a/src/app/browser/window/tab/item/page/database.rs +++ b/src/app/browser/window/tab/item/page/database.rs @@ -3,6 +3,7 @@ use sqlite::{Error, Transaction}; pub struct Table { pub id: i64, // pub app_browser_window_tab_item_id: i64, not in use, + pub title: Option, } 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_id` INTEGER NOT NULL, + `title` VARCHAR(1024), FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`) )", @@ -18,19 +20,25 @@ pub fn init(tx: &Transaction) -> Result { ) } -pub fn insert(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result { +pub fn insert( + tx: &Transaction, + app_browser_window_tab_item_id: i64, + title: Option<&str>, +) -> Result { tx.execute( "INSERT INTO `app_browser_window_tab_item_page` ( - `app_browser_window_tab_item_id` - ) VALUES (?)", - [app_browser_window_tab_item_id], + `app_browser_window_tab_item_id`, + `title` + ) VALUES (?, ?)", + (app_browser_window_tab_item_id, title), ) } pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result, Error> { let mut stmt = tx.prepare( "SELECT `id`, - `app_browser_window_tab_item_id` + `app_browser_window_tab_item_id`, + `title` FROM `app_browser_window_tab_item_page` WHERE `app_browser_window_tab_item_id` = ?", )?; @@ -39,6 +47,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result, - title: Option<&str>, - position: Position, - state: (bool, bool, bool), - ) -> Self { - // Define state variables - let (is_pinned, is_selected, is_attention) = state; - - // Create new `TabPage` for given `TabView` - let tab_page = match position { - Position::After => match tab_view.selected_page() { - Some(page) => add(tab_view, child, tab_view.page_position(&page) + 1), - None => tab_view.append(child), - }, - Position::End => tab_view.append(child), - Position::Number(value) => add(tab_view, child, value), - }; - - // Setup - tab_page.set_needs_attention(is_attention); - tab_page.set_title(match title { - Some(value) => value, - None => DEFAULT_TITLE, - }); - - tab_view.set_page_pinned(&tab_page, is_pinned); - - if is_selected { - tab_view.set_selected_page(&tab_page); - } - - // Done - Self { tab_page } - } - - // Actions - - pub fn clean( - &self, - transaction: &Transaction, - app_browser_window_tab_item_id: i64, - ) -> Result<(), String> { - match database::select(transaction, app_browser_window_tab_item_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_id: i64, - ) -> Result<(), String> { - match database::select(transaction, app_browser_window_tab_item_id) { - Ok(records) => { - for record in records { - // Record value can be stored as NULL - if let Some(title) = record.title { - self.tab_page.set_title(title.as_str()); - } - - // 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_id: i64, - ) -> Result<(), String> { - // Keep value in memory until operation complete - let title = self.tab_page.title(); - - match database::insert( - transaction, - app_browser_window_tab_item_id, - match title.is_empty() { - true => None, - false => Some(title.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(()) - } -} - -// 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(()) -} - -/// Create new [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html) -/// in [TabView](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabView.html) at given position -/// -/// * if given `position` match pinned tab, GTK will panic with notice: -/// adw_tab_view_insert: assertion 'position >= self->n_pinned_pages'\ -/// as the solution, prepend new page after pinned tabs in this case -fn add(tab_view: &TabView, child: &impl IsA, position: i32) -> TabPage { - if position > tab_view.n_pinned_pages() { - tab_view.insert(child, position) - } else { - tab_view.prepend(child) - } -} diff --git a/src/app/browser/window/tab/item/widget/database.rs b/src/app/browser/window/tab/item/widget/database.rs deleted file mode 100644 index ecfbd0f9..00000000 --- a/src/app/browser/window/tab/item/widget/database.rs +++ /dev/null @@ -1,74 +0,0 @@ -use sqlite::{Error, Transaction}; - -pub struct Table { - pub id: i64, - // pub app_browser_window_tab_item_id: i64, not in use - pub title: Option, // can be stored as NULL -} - -pub fn init(tx: &Transaction) -> Result { - tx.execute( - "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_widget` - ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `app_browser_window_tab_item_id` INTEGER NOT NULL, - `title` VARCHAR(1024), - - FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`) - )", - [], - ) -} - -pub fn insert( - tx: &Transaction, - app_browser_window_tab_item_id: i64, - title: Option<&str>, -) -> Result { - tx.execute( - "INSERT INTO `app_browser_window_tab_item_widget` ( - `app_browser_window_tab_item_id`, - `title` - ) VALUES (?, ?)", - (app_browser_window_tab_item_id, title), - ) -} - -pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result, Error> { - let mut stmt = tx.prepare( - "SELECT `id`, - `app_browser_window_tab_item_id`, - `title` - FROM `app_browser_window_tab_item_widget` - WHERE `app_browser_window_tab_item_id` = ?", - )?; - - let result = stmt.query_map([app_browser_window_tab_item_id], |row| { - Ok(Table { - id: row.get(0)?, - // app_browser_window_tab_item_id: row.get(1)?, not in use - title: 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_widget` WHERE `id` = ?", - [id], - ) -} - -/* not in use -pub fn last_insert_id(tx: &Transaction) -> i64 { - tx.last_insert_rowid() -} */