begin local actions implementation

This commit is contained in:
yggverse 2024-11-10 07:09:55 +02:00
parent 9398a835cd
commit 9ff32a3419
19 changed files with 402 additions and 226 deletions

View File

@ -1,17 +0,0 @@
//! Global actions config
use gtk::glib::VariantTy;
// app/browser/widget.rs
pub const APP_BROWSER_WIDGET: &str = "app_browser_widget";
// group | action | variant
pub const APP_BROWSER_WIDGET_ABOUT: (&str, &str, Option<&VariantTy>) =
(APP_BROWSER_WIDGET, "about", None);
pub const APP_BROWSER_WIDGET_CLOSE: (&str, &str, Option<&VariantTy>, &[&str]) =
(APP_BROWSER_WIDGET, "close", None, &["<Primary>Escape"]);
// group | action | variant | accels
pub const APP_BROWSER_WIDGET_DEBUG: (&str, &str, Option<&VariantTy>, &[&str]) =
(APP_BROWSER_WIDGET, "debug", None, &["<Primary>i"]);

View File

@ -67,11 +67,8 @@ impl App {
// Init events // Init events
gobject.connect_activate({ gobject.connect_activate({
let update = browser.action().update().clone(); let browser = browser.clone();
move |_| { move |_| browser.update()
// Make initial update
update.activate(Some(&"".to_variant())); // @TODO
}
}); });
gobject.connect_startup({ gobject.connect_startup({
@ -174,22 +171,33 @@ impl App {
}); });
// Init accels // Init accels
for (detailed_action_name, accels) in &[ for (detailed_action_name, &accels) in &[
// Browser actions // Browser actions
{
let (group, action, _, accels) = crate::action::APP_BROWSER_WIDGET_DEBUG;
(gformat!("{group}.{action}"), accels)
},
{
let (group, action, _, accels) = crate::action::APP_BROWSER_WIDGET_CLOSE;
(gformat!("{group}.{action}"), accels)
},
// @TODO
( (
gformat!("win.{}", browser.action().update().name()), gformat!(
"{}.{}",
browser.action().id(),
browser.action().close().id()
),
&["<Primary>Escape"],
),
(
gformat!(
"{}.{}",
browser.action().id(),
browser.action().debug().id()
),
&["<Primary>i"],
),
(
gformat!(
"{}.{}",
browser.action().id(),
browser.action().update().id()
),
&["<Primary>u"], &["<Primary>u"],
), ),
// Other // @TODO
( (
gformat!("win.{}", action_page_reload.name()), gformat!("win.{}", action_page_reload.name()),
&["<Primary>r"], &["<Primary>r"],

View File

@ -14,8 +14,7 @@ use crate::profile::Profile;
use adw::ApplicationWindow; use adw::ApplicationWindow;
use gtk::{ use gtk::{
gio::{Cancellable, File, SimpleAction}, gio::{Cancellable, File, SimpleAction},
glib::Variant, prelude::{ActionExt, GtkWindowExt, WidgetExt},
prelude::ActionExt,
FileLauncher, FileLauncher,
}; };
use sqlite::Transaction; use sqlite::Transaction;
@ -40,10 +39,8 @@ impl Browser {
action_page_reload: SimpleAction, action_page_reload: SimpleAction,
action_page_pin: SimpleAction, action_page_pin: SimpleAction,
) -> Browser { ) -> Browser {
// Init actions
let action = Rc::new(Action::new());
// Init components // Init components
let action = Rc::new(Action::new());
let window = Rc::new(Window::new( let window = Rc::new(Window::new(
action.clone(), action.clone(),
action_page_new.clone(), action_page_new.clone(),
@ -60,11 +57,6 @@ impl Browser {
let widget = Rc::new(Widget::new( let widget = Rc::new(Widget::new(
window.gobject(), window.gobject(),
&[ &[
action.about().clone(),
action.debug().clone(),
action.profile().clone(),
action.quit().clone(),
action.update().clone(),
action_page_new.clone(), action_page_new.clone(),
action_page_close.clone(), action_page_close.clone(),
action_page_close_all.clone(), action_page_close_all.clone(),
@ -76,18 +68,33 @@ impl Browser {
], ],
)); ));
// Init events // Connect actions to browser window
widget
.gobject()
.insert_action_group(action.id(), Some(action.gobject()));
// Browser actions // Connect events
action.about().connect_activate({ action.about().connect_activate({
let window = window.clone(); let window = window.clone();
move |_, _| { move || {
About::new().present(Some(window.gobject())); About::new().present(Some(window.gobject()));
} }
}); });
action.close().connect_activate({
let widget = widget.clone();
move || widget.gobject().close()
});
action.debug().connect_activate({
let widget = widget.clone();
move || {
widget.gobject().emit_enable_debugging(true);
}
});
action.profile().connect_activate({ action.profile().connect_activate({
move |_, _| { move || {
FileLauncher::new(Some(&File::for_path(profile.config_path()))).launch( FileLauncher::new(Some(&File::for_path(profile.config_path()))).launch(
None::<&gtk::Window>, None::<&gtk::Window>,
None::<&Cancellable>, None::<&Cancellable>,
@ -96,16 +103,16 @@ impl Browser {
println!("{error}") println!("{error}")
} }
}, },
); ); // @TODO move out?
} }
}); });
action.update().connect_activate({ action.update().connect_activate({
let window = window.clone(); let window = window.clone();
move |_, this| window.update(string_from_variant(this).as_str()) move |tab_item_id| window.update(tab_item_id)
}); });
// Other // @TODO
action_page_new.connect_activate({ action_page_new.connect_activate({
let window = window.clone(); let window = window.clone();
move |_, _| { move |_, _| {
@ -233,6 +240,10 @@ impl Browser {
self.window.init(); self.window.init();
} }
pub fn update(&self) {
self.window.update(None);
}
// Getters // Getters
pub fn action(&self) -> &Rc<Action> { pub fn action(&self) -> &Rc<Action> {
@ -278,11 +289,3 @@ fn page_position_from_action_state(action: &SimpleAction) -> Option<i32> {
None None
} }
} }
/// Extract `String` from [Variant](https://docs.gtk.org/glib/struct.Variant.html)
fn string_from_variant(variant: Option<&Variant>) -> String {
variant
.expect("Variant required for this action")
.get::<String>()
.expect("Parameter type does not match `String`")
}

View File

@ -1,45 +1,109 @@
use gtk::{gio::SimpleAction, glib::uuid_string_random, prelude::StaticVariantType}; mod about;
mod close;
mod debug;
mod profile;
mod update;
use about::About;
use close::Close;
use debug::Debug;
use profile::Profile;
use update::Update;
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 { pub struct Action {
about: SimpleAction, // Actions
debug: SimpleAction, about: Rc<About>,
profile: SimpleAction, close: Rc<Close>,
quit: SimpleAction, debug: Rc<Debug>,
update: SimpleAction, profile: Rc<Profile>,
update: Rc<Update>,
// Group
id: GString,
gobject: SimpleActionGroup,
} }
impl Action { impl Action {
// Constructors // Constructors
/// Create new `Self`
pub fn new() -> Self { pub fn new() -> Self {
// Init actions
let about = Rc::new(About::new());
let close = Rc::new(Close::new());
let debug = Rc::new(Debug::new());
let profile = Rc::new(Profile::new());
let update = Rc::new(Update::new());
// Generate unique group ID
let id = uuid_string_random();
// Init group
let gobject = SimpleActionGroup::new();
// Add action to given group
gobject.add_action(about.gobject());
gobject.add_action(close.gobject());
gobject.add_action(debug.gobject());
gobject.add_action(profile.gobject());
gobject.add_action(update.gobject());
// Done
Self { Self {
about: SimpleAction::new(&uuid_string_random(), None), about,
debug: SimpleAction::new(&uuid_string_random(), None), close,
profile: SimpleAction::new(&uuid_string_random(), None), debug,
quit: SimpleAction::new(&uuid_string_random(), None), profile,
update: SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())), update,
id,
gobject,
} }
} }
// Getters // Getters
pub fn about(&self) -> &SimpleAction { /// Get reference `About` action
pub fn about(&self) -> &Rc<About> {
&self.about &self.about
} }
pub fn debug(&self) -> &SimpleAction { /// Get reference `Close` action
pub fn close(&self) -> &Rc<Close> {
&self.close
}
/// Get reference `Debug` action
pub fn debug(&self) -> &Rc<Debug> {
&self.debug &self.debug
} }
pub fn profile(&self) -> &SimpleAction { /// Get reference `Profile` action
pub fn profile(&self) -> &Rc<Profile> {
&self.profile &self.profile
} }
pub fn quit(&self) -> &SimpleAction { /// Get reference `Update` action
&self.quit pub fn update(&self) -> &Rc<Update> {
}
pub fn update(&self) -> &SimpleAction {
&self.update &self.update
} }
/// 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
}
} }

View File

@ -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 `About` action of `Browser` group
pub struct About {
gobject: SimpleAction,
}
impl About {
// 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()
}
}

View File

@ -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 Close {
gobject: SimpleAction,
}
impl Close {
// 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()
}
}

View File

@ -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 `Debug` action of `Browser` group
pub struct Debug {
gobject: SimpleAction,
}
impl Debug {
// 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()
}
}

View File

@ -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 `Profile` action of `Browser` group
pub struct Profile {
gobject: SimpleAction,
}
impl Profile {
// 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()
}
}

View File

@ -0,0 +1,65 @@
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 {
gobject: SimpleAction,
}
impl Update {
// 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<GString>) + 'static) {
self.gobject.connect_activate(move |_, variant| {
let tab_item_id = variant
.expect("Variant required to call this action")
.get::<String>()
.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
}
/// Get auto-generated [action name](https://docs.gtk.org/gio/property.SimpleAction.name.html)
pub fn id(&self) -> GString {
self.gobject.name()
}
}

View File

@ -1,7 +1,4 @@
mod action;
mod database; mod database;
use action::Action;
use database::Database; use database::Database;
use adw::ApplicationWindow; use adw::ApplicationWindow;
@ -31,8 +28,6 @@ impl Widget {
.maximized(MAXIMIZED) .maximized(MAXIMIZED)
.build(); .build();
Action::new_for(&gobject);
// Register actions // Register actions
for action in actions { for action in actions {
gobject.add_action(action); gobject.add_action(action);

View File

@ -1,47 +0,0 @@
mod close;
mod debug;
use close::Close;
use debug::Debug;
use gtk::{
gio::SimpleActionGroup,
prelude::{IsA, WidgetExt},
Window,
};
pub struct Action {
// Actions
close: Close,
debug: Debug,
// Group
gobject: SimpleActionGroup,
}
impl Action {
// Constructors
/// Create **activated** [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) set
/// with new [SimpleActionGroup](https://docs.gtk.org/gio/class.SimpleActionGroup.html)
/// for given [Window](https://docs.gtk.org/gtk4/class.Window.html)
/// * useful for object-oriented work with GTK `detailed_name`, e.g. on GTK [Menu](https://docs.gtk.org/gio/class.Menu.html) build
/// * this implementation also encapsulates `GObject` to prevent unexpected assignments
/// * children actions implemented as wrapper also, that extend default [Variant](https://docs.gtk.org/glib/struct.Variant.html) features, etc
pub fn new_for(window: &(impl IsA<Window> + WidgetExt)) -> Self {
// Init group
let gobject = SimpleActionGroup::new();
// Add group to window
window.insert_action_group(crate::action::APP_BROWSER_WIDGET, Some(&gobject));
// Init actions
let close = Close::new_for(&gobject, window.clone());
let debug = Debug::new_for(&gobject, window.clone());
Self {
close,
debug,
gobject,
}
}
}

View File

@ -1,37 +0,0 @@
use gtk::{
gio::{SimpleAction, SimpleActionGroup},
prelude::{ActionMapExt, GtkWindowExt, IsA},
Window,
};
pub struct Close {
gobject: SimpleAction,
}
impl Close {
// Constructors
/// Create new [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html)
/// for given [SimpleActionGroup](https://docs.gtk.org/gio/class.SimpleActionGroup.html)
/// and [Window](https://docs.gtk.org/gtk4/class.Window.html)
/// * this constructor **activate** default feature
pub fn new_for(group: &SimpleActionGroup, window: impl IsA<Window>) -> Self {
// Get action config
let (_group_name, action_name, parameter_type, _accels) =
crate::action::APP_BROWSER_WIDGET_CLOSE;
// Init action GObject
let gobject = SimpleAction::new(&action_name, parameter_type);
// Add action to given group
group.add_action(&gobject);
// Connect default feature on activate
gobject.connect_activate(move |_, _| {
window.close();
});
// Done
Self { gobject }
}
}

View File

@ -1,37 +0,0 @@
use gtk::{
gio::{SimpleAction, SimpleActionGroup},
prelude::{ActionMapExt, GtkWindowExt, IsA},
Window,
};
pub struct Debug {
gobject: SimpleAction,
}
impl Debug {
// Constructors
/// Create new [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html)
/// for given [SimpleActionGroup](https://docs.gtk.org/gio/class.SimpleActionGroup.html)
/// and [Window](https://docs.gtk.org/gtk4/class.Window.html)
/// * this constructor **activate** default feature
pub fn new_for(group: &SimpleActionGroup, window: impl IsA<Window>) -> Self {
// Get action config
let (_group_name, action_name, parameter_type, _accels) =
crate::action::APP_BROWSER_WIDGET_DEBUG;
// Init action GObject
let gobject = SimpleAction::new(&action_name, parameter_type);
// Add action to given group
group.add_action(&gobject);
// Connect default feature on activate
gobject.connect_activate(move |_, _| {
window.emit_enable_debugging(true);
});
// Done
Self { gobject }
}
}

View File

@ -10,7 +10,7 @@ use tab::Tab;
use widget::Widget; use widget::Widget;
use crate::app::browser::action::Action as BrowserAction; use crate::app::browser::action::Action as BrowserAction;
use gtk::{gio::SimpleAction, Box}; use gtk::{gio::SimpleAction, glib::GString, Box};
use std::rc::Rc; use std::rc::Rc;
pub struct Window { pub struct Window {
@ -106,8 +106,8 @@ impl Window {
self.tab.pin(page_position); self.tab.pin(page_position);
} }
pub fn update(&self, id: &str) { pub fn update(&self, tab_item_id: Option<GString>) {
self.tab.update(id); self.tab.update(tab_item_id);
} }
pub fn clean(&self, transaction: &Transaction, app_browser_id: &i64) -> Result<(), String> { pub fn clean(&self, transaction: &Transaction, app_browser_id: &i64) -> Result<(), String> {

View File

@ -61,21 +61,32 @@ impl Menu {
// Main > Tool // Main > Tool
let main_tool = gio::Menu::new(); let main_tool = gio::Menu::new();
{ // Debug // Debug
let (group, action, _, _) = crate::action::APP_BROWSER_WIDGET_DEBUG; main_tool.append(Some("Debug"), Some(&gformat!(
main_tool.append(Some("Debug"), Some(&gformat!("{group}.{action}"))); "{}.{}",
} browser_action.id(),
browser_action.debug().id()
)));
main_tool.append(Some("Profile"), Some(&detailed_action_name(browser_action.profile()))); main_tool.append(Some("Profile"), Some(&gformat!(
main_tool.append(Some("About"), Some(&detailed_action_name(browser_action.about()))); "{}.{}",
browser_action.id(),
browser_action.profile().id()
)));
main_tool.append(Some("About"), Some(&gformat!(
"{}.{}",
browser_action.id(),
browser_action.about().id()
)));
main.append_submenu(Some("Tool"), &main_tool); main.append_submenu(Some("Tool"), &main_tool);
{ main.append(Some("Quit"), Some(&gformat!(
// Quit "{}.{}",
let (group, action, _, _) = crate::action::APP_BROWSER_WIDGET_CLOSE; browser_action.id(),
main.append(Some("Quit"), Some(&gformat!("{group}.{action}"))); browser_action.close().id()
} )));
// Result // Result
Rc::new(Self { widget:Widget::new_rc(&main) }) Rc::new(Self { widget:Widget::new_rc(&main) })

View File

@ -274,8 +274,13 @@ impl Tab {
} }
} }
pub fn update(&self, id: &str) { pub fn update(&self, item_id: Option<GString>) {
match self.index.borrow().get(id) { let key = match item_id {
Some(value) => value,
None => GString::new(), // @TODO
};
match self.index.borrow().get(&key) {
Some(item) => { Some(item) => {
// Update item components // Update item components
item.update(); item.update();
@ -298,7 +303,7 @@ impl Tab {
} }
} }
} }
} } // @TODO need optimization
pub fn clean( pub fn clean(
&self, &self,

View File

@ -196,7 +196,7 @@ impl Page {
self.cancellable.replace(Cancellable::new()); self.cancellable.replace(Cancellable::new());
// Create shared variant value // Create shared variant value
let id = self.id.to_variant(); let id = self.id.clone();
// Try **take** request value from Redirect holder first // Try **take** request value from Redirect holder first
let request = if let Some(redirect) = self.meta.take_redirect() { let request = if let Some(redirect) = self.meta.take_redirect() {
@ -446,7 +446,7 @@ impl Page {
let action_page_load = self.action_page_load.clone(); let action_page_load = self.action_page_load.clone();
let action_page_open = self.action_page_open.clone(); let action_page_open = self.action_page_open.clone();
let content = self.content.clone(); let content = self.content.clone();
let id = self.id.to_variant(); let id = self.id.clone();
let input = self.input.clone(); let input = self.input.clone();
let meta = self.meta.clone(); let meta = self.meta.clone();
let url = uri.clone().to_str(); let url = uri.clone().to_str();

View File

@ -45,7 +45,7 @@ impl Widget {
// Connect events // Connect events
gobject.connect_changed(move |_| { gobject.connect_changed(move |_| {
browser_action.update().activate(Some(&"".to_variant())); // @TODO browser_action.update().activate(None);
}); });
gobject.connect_activate(move |this| { gobject.connect_activate(move |this| {

View File

@ -1,4 +1,3 @@
mod action;
mod app; mod app;
mod profile; mod profile;