diff --git a/src/app/browser/window/tab.rs b/src/app/browser/window/tab.rs index dfe78391..4be9ba99 100644 --- a/src/app/browser/window/tab.rs +++ b/src/app/browser/window/tab.rs @@ -1,8 +1,10 @@ +mod action; mod database; mod item; mod menu; mod widget; +use action::Action; use database::Database; use item::Item; use menu::Menu; @@ -12,8 +14,8 @@ use crate::app::browser::action::Action as BrowserAction; use adw::TabView; use gtk::{ gio::SimpleAction, - glib::{uuid_string_random, GString, Propagation}, - prelude::{ActionExt, StaticVariantType, ToVariant}, + glib::{GString, Propagation}, + prelude::{ActionExt, ToVariant}, }; use sqlite::Transaction; use std::{cell::RefCell, collections::HashMap, rc::Rc}; @@ -22,8 +24,6 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; pub struct Tab { // Global actions browser_action: Rc, - // Local actions - action_tab_open: SimpleAction, // Page actions action_page_home: SimpleAction, action_page_history_back: SimpleAction, @@ -31,7 +31,7 @@ pub struct Tab { action_page_reload: SimpleAction, // Dynamically allocated reference index index: Rc>>>, - // GTK + action: Rc, widget: Rc, } @@ -48,8 +48,7 @@ impl Tab { action_page_reload: SimpleAction, ) -> Rc { // Init local actions - let action_tab_open = - SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())); + let action = Rc::new(Action::new()); // Init empty HashMap index as no tabs appended yet let index = Rc::new(RefCell::new(HashMap::new())); @@ -70,26 +69,25 @@ impl Tab { // Init events - action_tab_open.connect_activate({ + action.open().connect_activate({ let index = index.clone(); let gobject = widget.gobject().clone(); // Actions let browser_action = browser_action.clone(); + let action = action.clone(); - let action_tab_open = action_tab_open.clone(); let action_page_home = action_page_home.clone(); let action_page_history_back = action_page_history_back.clone(); let action_page_history_forward = action_page_history_forward.clone(); let action_page_reload = action_page_reload.clone(); - move |_, request| { + move |request| { // Init new tab item let item = Item::new_rc( &gobject, // Global actions browser_action.clone(), - // Local actions - action_tab_open.clone(), + action.clone(), // Page actions action_page_home.clone(), action_page_history_back.clone(), @@ -107,11 +105,9 @@ impl Tab { index.borrow_mut().insert(item.id(), item.clone()); // Apply request - if let Some(variant) = request { - if let Some(value) = variant.get::() { - item.set_page_navigation_request_text(value.as_str()); - item.page_reload(); - } + if let Some(value) = request { + item.set_page_navigation_request_text(value.as_str()); + item.page_reload(); } } }); @@ -178,8 +174,6 @@ impl Tab { // Return activated struct Rc::new(Self { browser_action, - // Local actions - action_tab_open, // Global actions action_page_home, action_page_history_back, @@ -187,7 +181,7 @@ impl Tab { action_page_reload, // Init empty HashMap index as no tabs appended yet index, - // GTK + action, widget, }) } @@ -199,7 +193,7 @@ impl Tab { self.gobject(), self.browser_action.clone(), // Local actions - self.action_tab_open.clone(), + self.action.clone(), // Global actions self.action_page_home.clone(), self.action_page_history_back.clone(), @@ -343,7 +337,7 @@ impl Tab { transaction, &record.id, self.browser_action.clone(), - self.action_tab_open.clone(), + self.action.clone(), self.action_page_home.clone(), self.action_page_history_back.clone(), self.action_page_history_forward.clone(), diff --git a/src/app/browser/window/tab/action.rs b/src/app/browser/window/tab/action.rs new file mode 100644 index 00000000..42c52857 --- /dev/null +++ b/src/app/browser/window/tab/action.rs @@ -0,0 +1,76 @@ +mod append; +mod open; + +use append::Append; +use open::Open; + +use gtk::{ + gio::SimpleActionGroup, + glib::{uuid_string_random, GString}, + prelude::ActionMapExt, +}; +use std::rc::Rc; + +/// [SimpleActionGroup](https://docs.gtk.org/gio/class.SimpleActionGroup.html) wrapper for `Browser` actions +pub struct Action { + // Actions + append: Rc, + open: Rc, + // Group + id: GString, + gobject: SimpleActionGroup, +} + +impl Action { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + // Init actions + let append = Rc::new(Append::new()); + let open = Rc::new(Open::new()); + + // Generate unique group ID + let id = uuid_string_random(); + + // Init group + let gobject = SimpleActionGroup::new(); + + // Add action to given group + gobject.add_action(append.gobject()); + gobject.add_action(open.gobject()); + + // Done + Self { + append, + open, + id, + gobject, + } + } + + // Getters + + /// Get reference `Append` action + pub fn append(&self) -> &Rc { + &self.append + } + + /// Get reference `Open` action + pub fn open(&self) -> &Rc { + &self.open + } + + /// Get auto-generated name for action group + /// * useful for manual relationship with GObjects or as the `detailed_name` + /// for [Accels](https://docs.gtk.org/gtk4/method.Application.set_accels_for_action.html) or + /// [Menu](https://docs.gtk.org/gio/class.Menu.html) builder + pub fn id(&self) -> &GString { + &self.id + } + + /// Get reference to [SimpleActionGroup](https://docs.gtk.org/gio/class.SimpleActionGroup.html) GObject + pub fn gobject(&self) -> &SimpleActionGroup { + &self.gobject + } +} diff --git a/src/app/browser/window/tab/action/append.rs b/src/app/browser/window/tab/action/append.rs new file mode 100644 index 00000000..d67e41d0 --- /dev/null +++ b/src/app/browser/window/tab/action/append.rs @@ -0,0 +1,41 @@ +use gtk::{ + gio::SimpleAction, + glib::{uuid_string_random, GString}, + prelude::ActionExt, +}; + +/// [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) wrapper for `Close` action of `Browser` group +pub struct Append { + gobject: SimpleAction, +} + +impl Append { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + gobject: SimpleAction::new(&uuid_string_random(), None), + } + } + + // Events + + /// Define callback function for + /// [SimpleAction::activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal + pub fn connect_activate(&self, callback: impl Fn() + 'static) { + self.gobject.connect_activate(move |_, _| callback()); + } + + // Getters + + /// Get reference to [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) GObject + pub fn gobject(&self) -> &SimpleAction { + &self.gobject + } + + /// Get auto-generated [action name](https://docs.gtk.org/gio/property.SimpleAction.name.html) + pub fn id(&self) -> GString { + self.gobject.name() + } +} diff --git a/src/app/browser/window/tab/action/open.rs b/src/app/browser/window/tab/action/open.rs new file mode 100644 index 00000000..22f630cc --- /dev/null +++ b/src/app/browser/window/tab/action/open.rs @@ -0,0 +1,66 @@ +use gtk::{ + gio::SimpleAction, + glib::{uuid_string_random, GString}, + prelude::{ActionExt, StaticVariantType, ToVariant}, +}; + +/// [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) wrapper for `Open` action of `Browser` group +pub struct Open { + gobject: SimpleAction, +} + +impl Open { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + gobject: 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.gobject.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.gobject.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()), + }) + }); + } + + // Getters + + /// Get reference to [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) GObject + pub fn gobject(&self) -> &SimpleAction { + &self.gobject + } + + /* @TODO not in use + /// Get auto-generated [action name](https://docs.gtk.org/gio/property.SimpleAction.name.html) + pub fn id(&self) -> GString { + self.gobject.name() + } */ +} diff --git a/src/app/browser/window/tab/item.rs b/src/app/browser/window/tab/item.rs index a4011b29..b3668486 100644 --- a/src/app/browser/window/tab/item.rs +++ b/src/app/browser/window/tab/item.rs @@ -7,6 +7,7 @@ use page::Page; use widget::Widget; use crate::app::browser::action::Action as BrowserAction; +use crate::app::browser::window::tab::action::Action as TabAction; use adw::{TabPage, TabView}; use gtk::{ gio::SimpleAction, @@ -30,8 +31,8 @@ impl Item { tab_view: &TabView, // Global actions browser_action: Rc, + tab_action: Rc, // @TODO - action_tab_open: SimpleAction, action_page_home: SimpleAction, action_page_history_back: SimpleAction, action_page_history_forward: SimpleAction, @@ -49,7 +50,7 @@ impl Item { id.clone(), // Actions browser_action, - action_tab_open.clone(), + tab_action, action_page_home.clone(), action_page_history_back.clone(), action_page_history_forward.clone(), @@ -131,7 +132,7 @@ impl Item { app_browser_window_tab_id: &i64, // Actions browser_action: Rc, - action_tab_open: SimpleAction, + tab_action: Rc, action_page_home: SimpleAction, action_page_history_back: SimpleAction, action_page_history_forward: SimpleAction, @@ -147,7 +148,7 @@ impl Item { tab_view, // Actions browser_action.clone(), - action_tab_open.clone(), + tab_action.clone(), action_page_home.clone(), action_page_history_back.clone(), action_page_history_forward.clone(), diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index 5c1a5a0b..ea525898 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -13,6 +13,7 @@ use navigation::Navigation; use widget::Widget; use crate::app::browser::action::Action as BrowserAction; +use crate::app::browser::window::tab::action::Action as TabAction; use gtk::{ gdk_pixbuf::Pixbuf, gio::{ @@ -24,8 +25,7 @@ use gtk::{ RegexMatchFlags, Uri, UriFlags, UriHideFlags, }, prelude::{ - ActionExt, CancellableExt, IOStreamExt, OutputStreamExt, SocketClientExt, - StaticVariantType, ToVariant, + ActionExt, CancellableExt, IOStreamExt, OutputStreamExt, SocketClientExt, StaticVariantType, }, Box, }; @@ -37,8 +37,8 @@ pub struct Page { cancellable: RefCell, // Actions browser_action: Rc, + tab_action: Rc, action_page_load: SimpleAction, - action_page_open: SimpleAction, // Components navigation: Rc, content: Rc, @@ -56,7 +56,7 @@ impl Page { pub fn new_rc( id: GString, browser_action: Rc, - action_tab_open: SimpleAction, + tab_action: Rc, action_page_home: SimpleAction, action_page_history_back: SimpleAction, action_page_history_forward: SimpleAction, @@ -68,7 +68,7 @@ impl Page { SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())); // Init components - let content = Content::new_rc(action_tab_open.clone(), action_page_open.clone()); + let content = Content::new_rc(tab_action.clone(), action_page_open.clone()); let navigation = Navigation::new_rc( browser_action.clone(), @@ -97,8 +97,8 @@ impl Page { id, // Actions browser_action, + tab_action, action_page_load: action_page_load.clone(), - action_page_open: action_page_open.clone(), // Components content, navigation, @@ -142,7 +142,7 @@ impl Page { pub fn home(&self) { if let Some(url) = self.navigation.home_url() { // Update with history record - self.action_page_open.activate(Some(&url.to_variant())); + self.tab_action.open().activate(Some(&url)); } } @@ -444,7 +444,7 @@ impl Page { let cancellable = self.cancellable.borrow().clone(); let update = self.browser_action.update().clone(); let action_page_load = self.action_page_load.clone(); - let action_page_open = self.action_page_open.clone(); + let tab_action = self.tab_action.clone(); let content = self.content.clone(); let id = self.id.clone(); let input = self.input.clone(); @@ -519,14 +519,14 @@ impl Page { match response.status() { gemini::client::response::meta::Status::SensitiveInput => input.set_new_sensitive( - action_page_open, + tab_action, uri, Some(description), Some(1024), ), _ => input.set_new_response( - action_page_open, + tab_action, uri, Some(description), Some(1024), diff --git a/src/app/browser/window/tab/item/page/content.rs b/src/app/browser/window/tab/item/page/content.rs index 9d15654e..281efca6 100644 --- a/src/app/browser/window/tab/item/page/content.rs +++ b/src/app/browser/window/tab/item/page/content.rs @@ -6,6 +6,7 @@ use image::Image; use status::Status; use text::Text; +use crate::app::browser::window::tab::action::Action as TabAction; use gtk::{ gdk_pixbuf::Pixbuf, gio::SimpleAction, @@ -19,7 +20,7 @@ pub struct Content { // GTK gobject: Box, // Actions - action_tab_open: SimpleAction, + tab_action: Rc, action_page_open: SimpleAction, } @@ -27,10 +28,10 @@ impl Content { // Construct /// Create new container for different components - pub fn new_rc(action_tab_open: SimpleAction, action_page_open: SimpleAction) -> Rc { + pub fn new_rc(tab_action: Rc, action_page_open: SimpleAction) -> Rc { Rc::new(Self { gobject: Box::builder().orientation(Orientation::Vertical).build(), - action_tab_open, + tab_action, action_page_open, }) } @@ -87,7 +88,7 @@ impl Content { let text = Text::gemini( data, base, - self.action_tab_open.clone(), + self.tab_action.clone(), self.action_page_open.clone(), ); self.gobject.append(text.gobject()); diff --git a/src/app/browser/window/tab/item/page/content/text.rs b/src/app/browser/window/tab/item/page/content/text.rs index c780b5b4..fe36bd47 100644 --- a/src/app/browser/window/tab/item/page/content/text.rs +++ b/src/app/browser/window/tab/item/page/content/text.rs @@ -2,11 +2,13 @@ mod gemini; use gemini::Gemini; +use crate::app::browser::window::tab::action::Action as TabAction; use gtk::{ gio::SimpleAction, glib::{GString, Uri}, ScrolledWindow, }; +use std::rc::Rc; pub struct Meta { title: Option, @@ -22,11 +24,11 @@ impl Text { pub fn gemini( gemtext: &str, base: &Uri, - action_page_new: SimpleAction, + tab_action: Rc, action_page_open: SimpleAction, ) -> Self { // Init components - let gemini = Gemini::new(gemtext, base, action_page_new, action_page_open); + let gemini = Gemini::new(gemtext, base, tab_action, action_page_open); // Init meta let meta = Meta { diff --git a/src/app/browser/window/tab/item/page/content/text/gemini.rs b/src/app/browser/window/tab/item/page/content/text/gemini.rs index 51c5d4f7..9af567e3 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini.rs @@ -4,13 +4,12 @@ mod widget; use reader::Reader; use widget::Widget; +use crate::app::browser::window::tab::action::Action as TabAction; +use adw::ClampScrollable; use gtk::{ gio::SimpleAction, glib::{GString, Uri}, }; - -use adw::ClampScrollable; - use std::rc::Rc; pub struct Gemini { @@ -23,11 +22,11 @@ impl Gemini { pub fn new( gemtext: &str, base: &Uri, - action_tab_open: SimpleAction, + tab_action: Rc, action_page_open: SimpleAction, ) -> Self { // Init components - let reader = Reader::new_rc(gemtext, base, action_tab_open, action_page_open); + let reader = Reader::new_rc(gemtext, base, tab_action, action_page_open); let widget = Widget::new_rc(reader.gobject()); diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs index bf32364a..720ddb05 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs @@ -4,6 +4,7 @@ mod widget; use tag::Tag; use widget::Widget; +use crate::app::browser::window::tab::action::Action as TabAction; use adw::StyleManager; use gemtext::line::{ code::Code, @@ -20,7 +21,6 @@ use gtk::{ EventControllerMotion, GestureClick, TextBuffer, TextTag, TextView, TextWindowType, UriLauncher, Window, WrapMode, }; - use std::{collections::HashMap, rc::Rc}; pub struct Reader { @@ -33,7 +33,7 @@ impl Reader { pub fn new_rc( gemtext: &str, base: &Uri, - action_tab_open: SimpleAction, + tab_action: Rc, action_page_open: SimpleAction, ) -> Rc { // Init default values @@ -289,7 +289,7 @@ impl Reader { return match uri.scheme().as_str() { "gemini" => { // Open new page in browser - action_tab_open.activate(Some(&uri.to_string().to_variant())); + tab_action.open().activate(Some(&uri.to_string())); } // Scheme not supported, delegate _ => UriLauncher::new(&uri.to_str()).launch( diff --git a/src/app/browser/window/tab/item/page/input.rs b/src/app/browser/window/tab/item/page/input.rs index 6344944c..9eb84870 100644 --- a/src/app/browser/window/tab/item/page/input.rs +++ b/src/app/browser/window/tab/item/page/input.rs @@ -2,12 +2,13 @@ mod response; mod sensitive; mod widget; -use gtk::{gio::SimpleAction, glib::Uri}; use response::Response; use sensitive::Sensitive; use widget::Widget; +use crate::app::browser::window::tab::action::Action as TabAction; use adw::Clamp; +use gtk::glib::Uri; use std::rc::Rc; pub struct Input { @@ -32,25 +33,25 @@ impl Input { // Setters pub fn set_new_response( &self, - action_page_open: SimpleAction, + tab_action: Rc, base: Uri, title: Option<&str>, size_limit: Option, ) { self.widget.update(Some( - Response::new_rc(action_page_open, base, title, size_limit).gobject(), + Response::new_rc(tab_action, base, title, size_limit).gobject(), )); } pub fn set_new_sensitive( &self, - action_page_open: SimpleAction, + tab_action: Rc, base: Uri, title: Option<&str>, max_length: Option, ) { self.widget.update(Some( - Sensitive::new_rc(action_page_open, base, title, max_length).gobject(), + Sensitive::new_rc(tab_action, base, title, max_length).gobject(), )); } diff --git a/src/app/browser/window/tab/item/page/input/response.rs b/src/app/browser/window/tab/item/page/input/response.rs index 5d39e25a..7c877c1f 100644 --- a/src/app/browser/window/tab/item/page/input/response.rs +++ b/src/app/browser/window/tab/item/page/input/response.rs @@ -8,10 +8,11 @@ use form::Form; use title::Title; use widget::Widget; +use crate::app::browser::window::tab::action::Action as TabAction; use gtk::{ gio::SimpleAction, glib::{uuid_string_random, Uri, UriHideFlags}, - prelude::{ActionExt, ToVariant, WidgetExt}, + prelude::WidgetExt, Box, }; use std::rc::Rc; @@ -24,7 +25,7 @@ pub struct Response { impl Response { // Construct pub fn new_rc( - action_page_open: SimpleAction, + tab_action: Rc, base: Uri, title: Option<&str>, size_limit: Option, @@ -58,14 +59,11 @@ impl Response { action_send.connect_activate({ let form = form.clone(); move |_, _| { - action_page_open.activate(Some( - &format!( - "{}?{}", - base.to_string_partial(UriHideFlags::QUERY), - Uri::escape_string(form.text().as_str(), None, false), - ) - .to_variant(), - )); + tab_action.open().activate(Some(&format!( + "{}?{}", + base.to_string_partial(UriHideFlags::QUERY), + Uri::escape_string(form.text().as_str(), None, false), + ))); } }); diff --git a/src/app/browser/window/tab/item/page/input/sensitive.rs b/src/app/browser/window/tab/item/page/input/sensitive.rs index 60a7bfbe..f927ba13 100644 --- a/src/app/browser/window/tab/item/page/input/sensitive.rs +++ b/src/app/browser/window/tab/item/page/input/sensitive.rs @@ -4,10 +4,11 @@ mod widget; use form::Form; use widget::Widget; +use crate::app::browser::window::tab::action::Action as TabAction; use gtk::{ gio::SimpleAction, glib::{uuid_string_random, Uri, UriHideFlags}, - prelude::{ActionExt, ToVariant, WidgetExt}, + prelude::WidgetExt, Box, }; use std::rc::Rc; @@ -20,7 +21,7 @@ pub struct Sensitive { impl Sensitive { // Construct pub fn new_rc( - action_page_open: SimpleAction, + tab_action: Rc, base: Uri, title: Option<&str>, max_length: Option, @@ -43,14 +44,11 @@ impl Sensitive { action_send.connect_activate({ let form = form.clone(); move |_, _| { - action_page_open.activate(Some( - &format!( - "{}?{}", - base.to_string_partial(UriHideFlags::QUERY), - Uri::escape_string(form.text().as_str(), None, false), - ) - .to_variant(), - )); + tab_action.open().activate(Some(&format!( + "{}?{}", + base.to_string_partial(UriHideFlags::QUERY), + Uri::escape_string(form.text().as_str(), None, false), + ))); } });