From 25db274d2e2ef7d5cafea82d7be57dda63c6ae70 Mon Sep 17 00:00:00 2001 From: yggverse Date: Thu, 30 Jan 2025 22:40:47 +0200 Subject: [PATCH] move `is_needs_attention` state to `page`, reorganize `TabView` access levels --- src/app/browser/window/tab.rs | 129 ++++++++++++------ src/app/browser/window/tab/item.rs | 106 +++++--------- src/app/browser/window/tab/item/database.rs | 23 ++-- src/app/browser/window/tab/item/page.rs | 73 +++------- .../browser/window/tab/item/page/database.rs | 18 ++- 5 files changed, 158 insertions(+), 191 deletions(-) diff --git a/src/app/browser/window/tab.rs b/src/app/browser/window/tab.rs index 10449129..8a34071d 100644 --- a/src/app/browser/window/tab.rs +++ b/src/app/browser/window/tab.rs @@ -13,9 +13,11 @@ use gtk::{ gio::Icon, glib::{DateTime, Propagation}, prelude::ActionExt, + Box, Orientation, }; pub use item::Item; use menu::Menu; +use sourceview::prelude::IsA; use sqlite::Transaction; use std::{cell::RefCell, collections::HashMap, rc::Rc}; @@ -113,26 +115,27 @@ impl Tab { request: Option<&str>, is_pinned: bool, is_selected: bool, - is_attention: bool, + is_needs_attention: bool, is_load: bool, ) -> Rc { + // Generate new `TabPage` with blank `Widget` + let (tab_page, target_child) = new_tab_page(&self.tab_view, position); + // Init new tab item let item = Rc::new(Item::build( - &self.tab_view, + (&tab_page, &target_child), &self.profile, // Actions (&self.browser_action, &self.window_action, &self.action), // Options - ( - position, - request, - is_pinned, - is_selected, - is_attention, - is_load, - ), + request, + is_load, )); + // Make initial setup + item.page.set_needs_attention(is_needs_attention); + item.page.set_title("New page"); + // 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()) { @@ -142,7 +145,14 @@ impl Tab { // Register dynamically created tab components in the HashMap index self.index .borrow_mut() - .insert(item.page.tab_page.clone(), item.clone()); + .insert(item.tab_page.clone(), item.clone()); + + // Setup + // * important to call these actions after index! + self.tab_view.set_page_pinned(&item.tab_page, is_pinned); + if is_selected { + self.tab_view.set_selected_page(&item.tab_page); + } update_actions( &self.tab_view, @@ -281,7 +291,6 @@ impl Tab { } Err(e) => return Err(e.to_string()), } - Ok(()) } @@ -291,30 +300,44 @@ impl Tab { app_browser_window_id: i64, ) -> Result<(), String> { match database::select(transaction, app_browser_window_id) { - Ok(records) => { - for record in records { - match Item::restore( - &self.tab_view, - transaction, - record.id, - &self.profile, - (&self.browser_action, &self.window_action, &self.action), - ) { - Ok(items) => { - for item in items { - // Register dynamically created tab item in the HashMap index - self.index - .borrow_mut() - .insert(item.page.tab_page.clone(), item.clone()); - } + Ok(tab_records) => { + for tab_record in tab_records { + for item_record in item::restore(transaction, tab_record.id)? { + // Generate new `TabPage` with blank `Widget` + let (tab_page, target_child) = + new_tab_page(&self.tab_view, Position::After); + + // Init new tab item + let item = Rc::new(Item::build( + (&tab_page, &target_child), + &self.profile, + // Actions + (&self.browser_action, &self.window_action, &self.action), + // Options + None, + false, + )); + + self.index + .borrow_mut() + .insert(item.tab_page.clone(), item.clone()); + + // Restore `Self` + // * important to call these actions after index! + self.tab_view + .set_page_pinned(&item.tab_page, item_record.is_pinned); + + if item_record.is_selected { + self.tab_view.set_selected_page(&item.tab_page); } - Err(e) => return Err(e.to_string()), + + // Restore children components + item.page.restore(transaction, item_record.id)?; } } } Err(e) => return Err(e.to_string()), } - Ok(()) } @@ -327,22 +350,12 @@ impl Tab { Ok(_) => { // Delegate save action to childs let id = database::last_insert_id(transaction); - - // Read collected HashMap index for (_, item) in self.index.borrow().iter() { - item.save( - transaction, - id, - 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(), - )?; + item.save(transaction, id, self.tab_view.page_position(&item.tab_page))?; } } Err(e) => return Err(e.to_string()), } - Ok(()) } @@ -355,8 +368,8 @@ impl Tab { // @TODO other/child features.. } - fn item(&self, tab_page_position: Option) -> Option> { - if let Some(tab_page) = match tab_page_position { + fn item(&self, page_position: Option) -> Option> { + if let Some(tab_page) = match page_position { Some(value) => Some(self.tab_view.nth_page(value)), None => self.tab_view.selected_page(), } { @@ -426,3 +439,33 @@ fn update_actions( } } } + +/// 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_page(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) + } +} +fn new_tab_page(tab_view: &TabView, position: Position) -> (TabPage, Box) { + let child = Box::builder().orientation(Orientation::Vertical).build(); + ( + match position { + Position::After => match tab_view.selected_page() { + Some(selected_page) => { + add_tab_page(tab_view, &child, tab_view.page_position(&selected_page) + 1) + } + None => tab_view.append(&child), + }, + Position::End => tab_view.append(&child), + Position::Number(value) => add_tab_page(tab_view, &child, value), + }, + child, + ) +} diff --git a/src/app/browser/window/tab/item.rs b/src/app/browser/window/tab/item.rs index 30bcbbf5..d7739781 100644 --- a/src/app/browser/window/tab/item.rs +++ b/src/app/browser/window/tab/item.rs @@ -3,12 +3,15 @@ mod client; mod database; mod page; -use super::{Action as TabAction, BrowserAction, Position, WindowAction}; +use super::{Action as TabAction, BrowserAction, WindowAction}; use crate::Profile; use action::Action; -use adw::TabView; +use adw::TabPage; use client::Client; -use gtk::prelude::ActionMapExt; +use gtk::{ + prelude::{ActionMapExt, BoxExt}, + Box, +}; use page::Page; use sqlite::Transaction; use std::rc::Rc; @@ -19,6 +22,7 @@ pub struct Item { // Components pub page: Rc, pub action: Rc, + pub tab_page: TabPage, } impl Item { @@ -26,21 +30,15 @@ impl Item { /// Build new `Self` pub fn build( - tab_view: &TabView, + (tab_page, target_child): (&TabPage, &Box), profile: &Rc, (browser_action, window_action, tab_action): ( &Rc, &Rc, &Rc, ), - (position, request, is_pinned, is_selected, is_needs_attention, is_load): ( - Position, - Option<&str>, - bool, - bool, - bool, - bool, - ), + request: Option<&str>, + is_load: bool, ) -> Self { // Init components let action = Rc::new(Action::new()); @@ -57,13 +55,18 @@ impl Item { .simple_action_group .add_action(&action.history.forward); + // Create new `Page` implementation for `TabPage` let page = Rc::new(Page::build( profile, (browser_action, window_action, tab_action, &action), - tab_view, - (position, is_pinned, is_selected, is_needs_attention), + tab_page, )); + target_child.append(&page.navigation.g_box); + target_child.append(&page.content.g_box); + target_child.append(&page.search.g_box); + target_child.append(&page.input.clamp); + // Update tab loading indicator let client = Rc::new(Client::init(profile, &page)); @@ -112,11 +115,12 @@ impl Item { client.handle(text, true); } } - // Done + Self { client, page, action, + tab_page: tab_page.clone(), } } @@ -143,76 +147,22 @@ impl Item { Ok(()) } - // This method does not contain Self context, - // because child items creating in the runtime (by parent component) - pub fn restore( - tab_view: &TabView, - transaction: &Transaction, - app_browser_window_tab_id: i64, - profile: &Rc, - // Actions - (browser_action, window_action, item_action): ( - &Rc, - &Rc, - &Rc, - ), - ) -> Result>, String> { - let mut items = Vec::new(); - - match database::select(transaction, app_browser_window_tab_id) { - Ok(records) => { - for record in records { - // Construct new item object - let item = Rc::new(Item::build( - tab_view, - profile, - // Actions - (browser_action, window_action, item_action), - // Options tuple - ( - Position::End, - None, - record.is_pinned, - record.is_selected, - record.is_needs_attention, - false, - ), - )); - - // Delegate restore action to the item childs - item.page.restore(transaction, record.id)?; - - // Result - items.push(item); - } - } - Err(e) => return Err(e.to_string()), - } - - Ok(items) - } - pub fn save( &self, transaction: &Transaction, app_browser_window_tab_id: i64, page_position: i32, - is_pinned: bool, - is_selected: bool, - is_needs_attention: bool, ) -> Result<(), String> { match database::insert( transaction, app_browser_window_tab_id, page_position, - is_pinned, - is_selected, - is_needs_attention, + self.tab_page.is_pinned(), + self.tab_page.is_selected(), ) { Ok(_) => { - let id = database::last_insert_id(transaction); - // Delegate save action to childs + let id = database::last_insert_id(transaction); self.page.save(transaction, id)?; } Err(e) => return Err(e.to_string()), @@ -235,3 +185,15 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> { // Success Ok(()) } + +// This feature restore require parental implementation +// * see `super::Tab::restore()` +pub fn restore( + transaction: &Transaction, + app_browser_window_tab_id: i64, +) -> Result, String> { + match database::select(transaction, app_browser_window_tab_id) { + Ok(records) => Ok(records), + Err(e) => Err(e.to_string()), + } +} diff --git a/src/app/browser/window/tab/item/database.rs b/src/app/browser/window/tab/item/database.rs index 6ed7e329..4d19cd1a 100644 --- a/src/app/browser/window/tab/item/database.rs +++ b/src/app/browser/window/tab/item/database.rs @@ -5,21 +5,19 @@ pub struct Table { // pub app_browser_window_tab_id: i64, not in use pub is_pinned: bool, pub is_selected: bool, - pub is_needs_attention: bool, } pub fn init(tx: &Transaction) -> Result { tx.execute( "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `app_browser_window_tab_id` INTEGER NOT NULL, - `page_position` INTEGER NOT NULL, - `is_pinned` INTEGER NOT NULL, - `is_selected` INTEGER NOT NULL, - `is_needs_attention` INTEGER NOT NULL, + `page_position` INTEGER NOT NULL, + `is_pinned` INTEGER NOT NULL, + `is_selected` INTEGER NOT NULL, - FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab`(`id`) + FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab` (`id`) )", [], ) @@ -31,22 +29,19 @@ pub fn insert( page_position: i32, is_pinned: bool, is_selected: bool, - is_needs_attention: bool, ) -> Result { tx.execute( "INSERT INTO `app_browser_window_tab_item` ( `app_browser_window_tab_id`, `page_position`, `is_pinned`, - `is_selected`, - `is_needs_attention` - ) VALUES (?, ?, ?, ?, ?)", + `is_selected` + ) VALUES (?, ?, ?, ?)", [ app_browser_window_tab_id, page_position as i64, is_pinned as i64, is_selected as i64, - is_needs_attention as i64, ], ) } @@ -56,8 +51,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result Result, pub navigation: Rc, // System - pub tab_page: TabPage, + /// Reference to [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html) + /// wanted to update title, loading status and other features related with page. + /// * this member sensitively dependent of parental HashMap index,\ + /// as connection drivers interact with `Page` API,\ + /// let's keep it private to isolate direct access and prevent their implementation errors + tab_page: TabPage, } impl Page { @@ -43,8 +46,7 @@ impl Page { &Rc, &Rc, ), - tab_view: &TabView, - (position, is_pinned, is_selected, is_needs_attention): (Position, bool, bool, bool), + tab_page: &TabPage, ) -> Self { // Init components let content = Rc::new(Content::build((window_action, tab_action, item_action))); @@ -55,33 +57,6 @@ impl Page { )); let input = Rc::new(Input::new()); - // Init main widget - let g_box = Box::builder().orientation(Orientation::Vertical).build(); - - g_box.append(&navigation.g_box); - g_box.append(&content.g_box); - 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(), @@ -157,15 +132,14 @@ impl Page { match database::select(transaction, app_browser_window_tab_item_id) { Ok(records) => { for record in records { - // Restore main widget + // Restore `Self` 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.set_needs_attention(record.is_needs_attention); + // Restore child components self.navigation.restore(transaction, &record.id)?; // Make initial page history snap using `navigation` values restored - // * just to have back/forward navigation ability if let Some(uri) = self.navigation.uri() { self.profile.history.memory.request.set(uri); } @@ -184,10 +158,10 @@ impl Page { ) -> 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, + self.tab_page.needs_attention(), match title.is_empty() { true => None, false => Some(title.as_str()), @@ -201,7 +175,6 @@ impl Page { } Err(e) => return Err(e.to_string()), } - Ok(()) } @@ -210,7 +183,11 @@ impl Page { /// 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); + self.tab_page.set_title(title) + } + + pub fn set_needs_attention(&self, is_needs_attention: bool) { + self.tab_page.set_needs_attention(is_needs_attention) } pub fn set_progress(&self, progress_fraction: f64) { @@ -233,17 +210,3 @@ 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 034e800e..f8b2d606 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 is_needs_attention: bool, pub title: Option, } @@ -10,11 +11,12 @@ pub fn init(tx: &Transaction) -> Result { tx.execute( "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `app_browser_window_tab_item_id` INTEGER NOT NULL, - `title` VARCHAR(1024), + `is_needs_attention` INTEGER NOT NULL, + `title` TEXT, - FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`) + FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item` (`id`) )", [], ) @@ -23,14 +25,16 @@ pub fn init(tx: &Transaction) -> Result { pub fn insert( tx: &Transaction, app_browser_window_tab_item_id: i64, + is_needs_attention: bool, title: Option<&str>, ) -> Result { tx.execute( "INSERT INTO `app_browser_window_tab_item_page` ( `app_browser_window_tab_item_id`, + `is_needs_attention`, `title` - ) VALUES (?, ?)", - (app_browser_window_tab_item_id, title), + ) VALUES (?, ?, ?)", + (app_browser_window_tab_item_id, is_needs_attention, title), ) } @@ -38,6 +42,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result Result