2

I'm trying to get a file dialog popup in a Rust GTK application. The window subclasses gtk::ApplicationWindow, but when I try to use it to open the dialog it gets the error

error[E0277]: the trait bound `options::imp::Options: gtk4::prelude::IsA<gtk4::Window>` is not satisfied
   --> src/options/mod.rs:126:17
    |
124 |             let file_chooser = FileChooserDialog::new(
    |                                ---------------------- required by a bound introduced by this call
125 |                 Some("Open File"),
126 |                 Some(self),
    |                 ^^^^^^^^^^ the trait `gtk4::prelude::IsA<gtk4::Window>` is not implemented for `options::imp::Options`
    |
    = help: the following other types implement trait `gtk4::prelude::IsA<T>`:
              <ATContext as gtk4::prelude::IsA<ATContext>>
              <ATContext as gtk4::prelude::IsA<gtk4::glib::Object>>
              <AboutDialog as gtk4::prelude::IsA<AboutDialog>>
              <AboutDialog as gtk4::prelude::IsA<Accessible>>
              <AboutDialog as gtk4::prelude::IsA<Buildable>>
              <AboutDialog as gtk4::prelude::IsA<ConstraintTarget>>
              <AboutDialog as gtk4::prelude::IsA<Native>>
              <AboutDialog as gtk4::prelude::IsA<Root>>
            and 1758 others

Options is a window:

glib::wrapper! {
    pub struct Options(ObjectSubclass<imp::Options>)
    @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, @implements gio::ActionMap, gio::ActionGroup;
}

but I'm not sure how to convince glib. How is this usually dealt with? Is adding IsA trait to Option doable? I've played around with it but cannot find anything that compiles. One solution might be to change Options to a box and then include it as a child of a Window, but I think there must be a way of doing this and I'd like to learn it.

There is sample code that opens a FileChooser, but it uses a Window, not a subclass. I tried making a new window and using that - it does pop up the dialog, (and a small blank window, in the background), but can't get focus - focus remains with the original window, even if I try grabbing it for the new window or the dialog. Currently I changed to FileChooserNative, which works (it does not take a widget), but I've run into similar problems earlier and would like to understand better what is happening.

-- Edit -- add sample code The sample code demonstrates the first issue, not accepting subclass as a Window. For some reason it does not have the second problem, the nonresponsive dialog. I'm not sure why, the code seems identical

use gtk::prelude::*;
use gtk::Application;

mod simple {
    use gtk::{gio, glib};
    
    glib::wrapper! {
        pub struct Simple(ObjectSubclass<imp::Simple>)
            @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget, @implements gio::ActionMap, gio::ActionGroup;
    }
    
    impl Simple {
        pub fn new<P: glib::IsA<gtk::Application>>(app: &P) -> Self {
            glib::Object::builder().property("application", app).build()
        }
    }
    
    pub mod imp {
        use gtk::{glib, CompositeTemplate};
        use gtk::glib::clone;
        use gtk::prelude::*;
        use gtk::subclass::prelude::*;
        use gtk::{FileChooserAction, FileChooserDialog, ResponseType, };
        // use gtk::FileChooserNative;
        impl WidgetImpl for Simple {}
        impl WindowImpl for Simple {}
        impl ApplicationWindowImpl for Simple {}

        #[derive(Debug, Default, CompositeTemplate)]
        #[template(file = "simple.ui")]
        pub struct Simple {
            #[template_child]
            pub open_button: TemplateChild<gtk::Button>,
        }
        
        #[glib::object_subclass]
        impl ObjectSubclass for Simple {
            const NAME: &'static str = "Simple";
            type Type = super::Simple;
            type ParentType = gtk::ApplicationWindow;

            fn class_init(klass: &mut Self::Class) {
                klass.bind_template();
            }
            fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
                obj.init_template();
            }
        }

        impl ObjectImpl for Simple {
            fn constructed(&self) {
                self.parent_constructed();
                let simple = self;
                self.open_button.connect_clicked(clone!(@weak simple => move |_b| { simple.save(); }));
            }
        }

        impl Simple {
            fn save(&self) {
// Using this line has window a Simple, which is an ApplicationWindow. The compile fails with the above error.                
//                let window = self;
// Using this line compiles and runs, but with an extra window. In the program control does not go to the dialog
// until the window with the button closes, here it seems to work. I'm not sure why, the code here is identical
                let window = gtk::ApplicationWindow::new(&self.obj().application().unwrap());
                let file_chooser = FileChooserDialog::new(
                    Some("Open File"),
                    Some(&window),
                    FileChooserAction::Save,
                    &[("Open", ResponseType::Ok), ("Cancel", ResponseType::Cancel)],
                );
                file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| {
                    if response == ResponseType::Ok {
                        println!("{:?}", d.file().unwrap());
                    } else {
                        println!("{:?}", response);
                    }                        
                    d.close();
                });
                file_chooser.show();
                // window.show();
            }
        }
    }
}

    
fn main() {
    let app = gtk::Application::new( Some("abc.def"), Default::default(), );
    app.connect_activate(build_ui);
    app.run();
}

fn build_ui(app: &Application) {
    let window = simple::Simple::new(app);
    window.show();
}

and the template:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="Simple" parent="GtkApplicationWindow">
    <property name="title" >Simple test</property>
    <property name="default-width">400</property>
    <property name="default-height">880</property>
    <child>
      <object class="GtkBox" id="v_box">
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkButton" id="open_button">
            <property name="label">Save</property>
          </object>
        </child>
      </object>
    </child>
  </template>>
</interface>
3
  • Haven't worked with GTK yet, but noticed that error talks about gtk4::Window, while macros contains @extends gtk::Window. Could you create a reproducible example, including exact dependencies versions? Commented Mar 3, 2023 at 5:42
  • 1
    @Cerberus that's most likely because gtk reccommends to install gtk4 with cargo add gtk4 --rename gtk just to confuse everybody. Commented Mar 3, 2023 at 8:35
  • @Cerberus, cafce25 is right, gtk4 is used as gtk. As a beginner I just followed the instructions on how to set things up, and that's what the instructions said. Commented Mar 3, 2023 at 16:49

1 Answer 1

0

Following worked for me. Probably should create a fn called as_gtk_window() to retrieve the gtk4 Window in an idiomatic manner. Yes, I followed the gtk4-rs tutorial and as a result gtk4 is mapped to gtk.

let gtk_window = (&*window as &dyn Any).downcast_ref::<gtk::Window>();

dialog.open(gtk_window, gio::Cancellable::NONE, move |file| {
                    if let Ok(file) = file {  .....
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.