Skip to content

Allow implementing a GAction interface #1640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

andy128k
Copy link
Contributor

@andy128k andy128k commented Jan 27, 2025

Closes #1636

@andy128k
Copy link
Contributor Author

Another issue I have is that GAction implies that an implementor had some properties.

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'state-type' from interface 'GAction'

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'state' from interface 'GAction'

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'parameter-type' from interface 'GAction'

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'enabled' from interface 'GAction'

I have no clue what would be an idiomatic way to use g_object_class_override_property in Rust.

@bilelmoussaoui
Copy link
Member

Another issue I have is that GAction implies that an implementor had some properties.

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'state-type' from interface 'GAction'

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'state' from interface 'GAction'

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'parameter-type' from interface 'GAction'

(process:527349): GLib-GObject-CRITICAL **: 20:51:28.369: Object class ExampleRenamedAction doesn't implement property 'enabled' from interface 'GAction'

I have no clue what would be an idiomatic way to use g_object_class_override_property in Rust.

GtkEditable provides a method to automatically add the properties for you along with the get_property and set_property. Either the example implementing a custom Action would have to do that manually or you could add helpers that would do it automatically for you, see the EditableImpl in the gtk4-rs repo.


#[derive(glib::Properties, Default)]
#[properties(wrapper_type = super::RenamedAction)]
pub struct RenamedAction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The properties macro allows overriding properties by the way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave it a try but received an error

(process:718176): GLib-GObject-CRITICAL **: 22:59:07.010: When installing property: type 'ExampleRenamedAction' already has a property named 'name'

I guess it's because of the sequence of initialization. When I define a class and implement an interface then class installs properties first and interface fails to install them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way how you do it now should work or not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not work. I guess It works as expected in the cases like

class A implements IFace

class B extends A implements IFace {
   override property x of IFace
}

where initialization sequence is: A, IFace, B. So, when B is initializing IFace is already there and could be overriden.

but does not work for

class B implements IFace {
   override property x of IFace
}

where initialization sequence is: B, IFace. So, B has nothing to override yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't find a solution to this yet, right? This probably should be solved independent of this PR (please create an issue with all you remember)

@andy128k
Copy link
Contributor Author

@bilelmoussaoui Thanks for pointing a direction! Unfortunately Gio does not provide helpers similar to gtk_editable_*_delegate_*, so I needed to replicate C code myself.


I am not sure if I fully understand the subject but this line seems wrong. Gtk's doc uses NUM_PROPERTIES as a second parameter which is usually not an amount of properties but amount + 1 (props are numbered from 1). gtk_editable_install_properties does not add 1.

glib::ffi::g_free(properties as *mut _);
let first_prop = first_prop.assume_init() + 1;

set_first_prop(instance_type, first_prop as usize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice hack for the properties :)

_pspec: &glib::ParamSpec,
) -> Option<glib::Value> {
let type_: glib::Type = self.obj().type_();
let property = ActionProperty::from_type(type_, prop_id)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you pass in Self::type_() here then you don't need to do all the parent type walking inside from_type() and always have directly the correct type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I've found in GtkEditable's code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bilelmoussaoui Why does it do things so complicated, do you remember? :)

let instance = &*(actionptr as *mut T::Instance);
let imp = instance.imp();

if let Some(parameter_type) = imp.parameter_type() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be moved into the set_qdata() call. Now you'd get inconsistent behaviour if the function returns Some on the first call and None afterwards

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not directly forbidden by Gio's docs, but I think it could be insane if the type of a parameter would change during the action's lifetime.

A similar assumption is made in ActionGroup.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My personal approach here would be to either only call the function once to don't give the user a chance to do it wrong, or otherwise at least to add an assertion :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this and other places (name, parameter_type and state_type) in the way that the function is called once.

@andy128k andy128k marked this pull request as ready for review January 30, 2025 11:25
})
}

fn delegate_set_property(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call of adding this even if it just returns false; it would allow adding some attribute to glib::derived_properties to automatically use the delegate_get_property / delegate_set_property calls, simplifying things a bit.

@bilelmoussaoui
Copy link
Member

what is left to be done here btw?

@andy128k
Copy link
Contributor Author

andy128k commented May 9, 2025

what is left to be done here btw?

I'd say its ready, he-he. But I have a concern about a potential off-by-one error (see above) and waited for your judgement.

@bilelmoussaoui
Copy link
Member

It looks good to me as is. Any future improvements can be done in the future without blocking this forever.

I will wait for an ack from @sdroege, otherwise i will merge in few days if nothing.

bilelmoussaoui
bilelmoussaoui previously approved these changes May 22, 2025
}

fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
if !self.delegate_set_property(id, value, pspec) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If multiple interfaces like this are implemented, the method name would probably conflict. Should we write the interface name in the method name (delegate_action_set_property()), or change the example to specify the trait (<Self as ActionImplExt>::delegate_set_property(self, id, value, pspec))?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far there are only Actions and Editable.i rather keep it this way until we find more cases like this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd already have this problem then if you implement both interfaces in the same type :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is 0 use case for that. You would rather implement GActionMap instead

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but if we want to solve this problem later we need to change API in even more places so it seems useful to figure this out from the beginning. I'm not going to block this PR on this though, just don't tell me in a few years that I didn't warn you :P

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal is to basically make the derived_properties macro automatically generate those bits for you. I will send a patch for it once this PR lands

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good

.expect("no parent \"activate\" implementation");
func(
self.obj().unsafe_cast_ref::<Action>().to_glib_none().0,
parameter.to_glib_none().0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is to_glib_none(), maybe the parameter should be a reference (same for others above)

Comment on lines +414 to +416
let parameter: Option<glib::Variant> = from_glib_none(parameterptr);

imp.activate(parameter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let parameter: Option<glib::Variant> = from_glib_none(parameterptr);
imp.activate(parameter);
let parameter: Option<glib::Variant> = from_glib_borrow(parameterptr);
imp.activate(parameter.as_ref());

or so

if let Some(first_prop) = get_first_prop(type_) {
break ActionProperty::from_id(first_prop, id);
}
type_ = type_.parent()?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually correct to look at all the parents, can you add a comment why?
Shouldn't this just look at the just the type of T?

Maybe a test with multiple levels of GAction inheritance would be useful to make sure this works and continues working

.get_name
.expect("no parent \"get_name\" implementation");
let ret = func(self.obj().unsafe_cast_ref::<Action>().to_glib_none().0);
from_glib_none(ret)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is rather suboptimal as it copies strings all the time whenever the name is retrieved. This should be possible to make a &glib::GStr or not?

Co-authored-by: Sebastian Dröge <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE REQUEST] Allow to implement gio::Action
3 participants