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>
gtk4::Window, while macros contains@extends gtk::Window. Could you create a reproducible example, including exact dependencies versions?gtk4withcargo add gtk4 --rename gtkjust to confuse everybody.