0

In the following gtk4 code, I'm trying to remove an EditableLabel from display by deleting the cooresponding StringObject . This is supposed to happen after editing is complete as specified by the connect_editing_notify callback.

If I run the program and exit editing mode using the return key then it works as expected: the label disappears. However, if I exit editing mode by selecting a different label then I get a repeating console print saying that an assertion has failed:

(viewdb:13773): Gtk-CRITICAL **: 17:35:26.754: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

(viewdb:13773): Gtk-CRITICAL **: 17:35:26.754: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

(viewdb:13773): Gtk-CRITICAL **: 17:35:26.754: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

How can I cleanly delete the EditableLabel after editing ?

use gtk::glib::clone;
use gtk::prelude::*;

use gtk::{
    Application, ApplicationWindow, EditableLabel, ListView, SignalListItemFactory,
    SingleSelection, StringList, StringObject, Widget,
};

fn build_ui(app: &Application) {
    let model: StringList = vec!["One", "Two", "Three", "Four"].into_iter().collect();

    let selection_model = SingleSelection::new(Some(&model));

    let factory = SignalListItemFactory::new();
    factory.connect_setup(move |_, listitem| {
        let label = EditableLabel::builder().build();

        listitem.set_child(Some(&label));

        listitem
            .property_expression("item")
            .chain_property::<StringObject>("string")
            .bind(&label, "text", Widget::NONE);

        label.connect_editing_notify(
            clone!( @strong model, @strong listitem => move |lbl: &EditableLabel| {
                if !lbl.is_editing() {
                    let position = listitem.position();
                    model.remove(position); // remove model entry cooresponding to edit.
                }
            }),
        );
    });

    let view = ListView::new(Some(&selection_model), Some(&factory));

    let window = ApplicationWindow::builder()
        .application(app)
        .child(&view)
        .build();

    window.present();
}

fn main() {
    let app = Application::builder().build();
    app.connect_activate(build_ui);
    app.run();
}
Dave Compton
  • 1,421
  • 1
  • 11
  • 18
  • 1
    You could try with `gio::source::idle_add_local_once(|| model.remove(position));`, probably with a call to `clone!`, but you get the idea. – rodrigo May 25 '22 at 10:35
  • Thanks! That works. The actual working line turned out to be ``gtk::glib::source::idle_add_local_once(clone!(@strong model => move || model.remove(position)));`` If you write this up as an answer I'll mark it as correct. – Dave Compton May 25 '22 at 12:48

1 Answers1

1

Removing the item from its parent when it is still processing some events sometimes causes issues.

The easy fix is to do the removing in a deferred function. In C that would be done with g_idle_add() that is a hassle to use correctly. But in Rust it is super-easy:

gtk::glib::source::idle_add_local_once(
    clone!(@strong model => 
        move || model.remove(position)
    )
);

But note that since idle functions are run some unspecified time in the future, it is possible, although very unlikely, that the value of position (a plain index in the list) when the callback is run will refer to a different item.

I didn't test it but I think it is wiser to keep the listitem itself into the callback:

gtk::glib::source::idle_add_local_once(
    clone!(@strong model, @weak listitem => 
        move || {
            let position = listitem.position();
            if position != INVALID_LIST_POSITION {
                model.remove(position)
            }
        }
    )
);

A weak clone will ensure that it will only be called if the item still exists.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • I tested it and it works. Thanks again! Does the weak clone mean that the closure will not be called at all if the item no longer exists or does it mean that the `.position()` method will somehow fail gracefully and exit the closure (or maybe something else altogether)? – Dave Compton May 25 '22 at 15:10
  • According to the `clone!` [docs](https://docs.rs/glib/latest/glib/macro.clone.html), if you weakly clone an object and when the callback is to be called, that weak reference is expired, then the callback will not be called at all. There is a feature `@weak-allow-none` to get the weak reference as an `Option` but I don't think you need that here. – rodrigo May 25 '22 at 15:28