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
gobject.connect_activate({
let update = browser.action().update().clone();
move |_| {
// Make initial update
update.activate(Some(&"".to_variant())); // @TODO
}
let browser = browser.clone();
move |_| browser.update()
});
gobject.connect_startup({
@ -174,22 +171,33 @@ impl App {
});
// Init accels
for (detailed_action_name, accels) in &[
for (detailed_action_name, &accels) in &[
// 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"],
),
// Other
// @TODO
(
gformat!("win.{}", action_page_reload.name()),
&["<Primary>r"],

View File

@ -14,8 +14,7 @@ use crate::profile::Profile;
use adw::ApplicationWindow;
use gtk::{
gio::{Cancellable, File, SimpleAction},
glib::Variant,
prelude::ActionExt,
prelude::{ActionExt, GtkWindowExt, WidgetExt},
FileLauncher,
};
use sqlite::Transaction;
@ -40,10 +39,8 @@ impl Browser {
action_page_reload: SimpleAction,
action_page_pin: SimpleAction,
) -> Browser {
// Init actions
let action = Rc::new(Action::new());
// Init components
let action = Rc::new(Action::new());
let window = Rc::new(Window::new(
action.clone(),
action_page_new.clone(),
@ -60,11 +57,6 @@ impl Browser {
let widget = Rc::new(Widget::new(
window.gobject(),
&[
action.about().clone(),
action.debug().clone(),
action.profile().clone(),
action.quit().clone(),
action.update().clone(),
action_page_new.clone(),
action_page_close.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({
let window = window.clone();
move |_, _| {
move || {
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({
move |_, _| {
move || {
FileLauncher::new(Some(&File::for_path(profile.config_path()))).launch(
None::<&gtk::Window>,
None::<&Cancellable>,
@ -96,16 +103,16 @@ impl Browser {
println!("{error}")
}
},
);
); // @TODO move out?
}
});
action.update().connect_activate({
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({
let window = window.clone();
move |_, _| {
@ -233,6 +240,10 @@ impl Browser {
self.window.init();
}
pub fn update(&self) {
self.window.update(None);
}
// Getters
pub fn action(&self) -> &Rc<Action> {
@ -278,11 +289,3 @@ fn page_position_from_action_state(action: &SimpleAction) -> Option<i32> {
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 {
about: SimpleAction,
debug: SimpleAction,
profile: SimpleAction,
quit: SimpleAction,
update: SimpleAction,
// Actions
about: Rc<About>,
close: Rc<Close>,
debug: Rc<Debug>,
profile: Rc<Profile>,
update: Rc<Update>,
// Group
id: GString,
gobject: SimpleActionGroup,
}
impl Action {
// Constructors
/// Create 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 {
about: SimpleAction::new(&uuid_string_random(), None),
debug: SimpleAction::new(&uuid_string_random(), None),
profile: SimpleAction::new(&uuid_string_random(), None),
quit: SimpleAction::new(&uuid_string_random(), None),
update: SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())),
about,
close,
debug,
profile,
update,
id,
gobject,
}
}
// Getters
pub fn about(&self) -> &SimpleAction {
/// Get reference `About` action
pub fn about(&self) -> &Rc<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
}
pub fn profile(&self) -> &SimpleAction {
/// Get reference `Profile` action
pub fn profile(&self) -> &Rc<Profile> {
&self.profile
}
pub fn quit(&self) -> &SimpleAction {
&self.quit
}
pub fn update(&self) -> &SimpleAction {
/// Get reference `Update` action
pub fn update(&self) -> &Rc<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;
use action::Action;
use database::Database;
use adw::ApplicationWindow;
@ -31,8 +28,6 @@ impl Widget {
.maximized(MAXIMIZED)
.build();
Action::new_for(&gobject);
// Register actions
for action in actions {
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 crate::app::browser::action::Action as BrowserAction;
use gtk::{gio::SimpleAction, Box};
use gtk::{gio::SimpleAction, glib::GString, Box};
use std::rc::Rc;
pub struct Window {
@ -106,8 +106,8 @@ impl Window {
self.tab.pin(page_position);
}
pub fn update(&self, id: &str) {
self.tab.update(id);
pub fn update(&self, tab_item_id: Option<GString>) {
self.tab.update(tab_item_id);
}
pub fn clean(&self, transaction: &Transaction, app_browser_id: &i64) -> Result<(), String> {

View File

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

View File

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

View File

@ -196,7 +196,7 @@ impl Page {
self.cancellable.replace(Cancellable::new());
// Create shared variant value
let id = self.id.to_variant();
let id = self.id.clone();
// Try **take** request value from Redirect holder first
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_open = self.action_page_open.clone();
let content = self.content.clone();
let id = self.id.to_variant();
let id = self.id.clone();
let input = self.input.clone();
let meta = self.meta.clone();
let url = uri.clone().to_str();

View File

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

View File

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