gtk4/
dialog.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    cell::{Cell, RefCell},
5    future::Future,
6    pin::Pin,
7    ptr,
8    rc::Rc,
9};
10
11use glib::translate::*;
12
13use crate::{ffi, prelude::*, Dialog, DialogFlags, ResponseType, Widget, Window};
14
15impl Dialog {
16    /// Creates a new [`Dialog`][crate::Dialog] with the given title and transient parent.
17    ///
18    /// The @flags argument can be used to make the dialog modal, have it
19    /// destroyed along with its transient parent, or make it use a headerbar.
20    ///
21    /// Button text/response ID pairs should be listed in pairs, with a [`None`]
22    /// pointer ending the list. Button text can be arbitrary text. A response
23    /// ID can be any positive number, or one of the values in the
24    /// [`ResponseType`][crate::ResponseType] enumeration. If the user clicks one of these
25    /// buttons, [`Dialog`][crate::Dialog] will emit the [`response`][struct@crate::Dialog#response] signal
26    /// with the corresponding response ID.
27    ///
28    /// If a [`Dialog`][crate::Dialog] receives a delete event, it will emit ::response with a
29    /// response ID of [`ResponseType::DeleteEvent`][crate::ResponseType::DeleteEvent].
30    ///
31    /// However, destroying a dialog does not emit the ::response signal;
32    /// so be careful relying on ::response when using the
33    /// [`DialogFlags::DESTROY_WITH_PARENT`][crate::DialogFlags::DESTROY_WITH_PARENT] flag.
34    ///
35    /// Here’s a simple example:
36    /// **⚠️ The following code is in c ⚠️**
37    ///
38    /// ```c
39    /// GtkWindow *main_app_window; // Window the dialog should show up on
40    /// GtkWidget *dialog;
41    /// GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
42    /// dialog = gtk_dialog_new_with_buttons ("My dialog",
43    ///                                       main_app_window,
44    ///                                       flags,
45    ///                                       _("_OK"),
46    ///                                       GTK_RESPONSE_ACCEPT,
47    ///                                       _("_Cancel"),
48    ///                                       GTK_RESPONSE_REJECT,
49    ///                                       NULL);
50    /// ```
51    ///
52    /// # Deprecated since 4.10
53    ///
54    /// Use [`Window`][crate::Window] instead
55    /// ## `title`
56    /// Title of the dialog
57    /// ## `parent`
58    /// Transient parent of the dialog
59    /// ## `flags`
60    /// from [`DialogFlags`][crate::DialogFlags]
61    /// ## `first_button_text`
62    /// text to go in first button
63    ///
64    /// # Returns
65    ///
66    /// a new [`Dialog`][crate::Dialog]
67    #[doc(alias = "gtk_dialog_new_with_buttons")]
68    #[doc(alias = "new_with_buttons")]
69    #[cfg_attr(feature = "v4_10", deprecated = "Since 4.10")]
70    #[allow(deprecated)]
71    pub fn with_buttons<T: IsA<Window>>(
72        title: impl IntoOptionalGStr,
73        parent: Option<&T>,
74        flags: DialogFlags,
75        buttons: &[(&str, ResponseType)],
76    ) -> Self {
77        assert_initialized_main_thread!();
78        let ret: Self = unsafe {
79            title.run_with_gstr(|title| {
80                Widget::from_glib_none(ffi::gtk_dialog_new_with_buttons(
81                    title.to_glib_none().0,
82                    parent.map(|p| p.as_ref()).to_glib_none().0,
83                    flags.into_glib(),
84                    ptr::null_mut(),
85                ))
86                .unsafe_cast()
87            })
88        };
89
90        ret.add_buttons(buttons);
91        ret
92    }
93}
94
95mod sealed {
96    pub trait Sealed {}
97    impl<T: super::IsA<super::Dialog>> Sealed for T {}
98}
99
100// rustdoc-stripper-ignore-next
101/// Trait containing manually implemented methods of [`Dialog`](crate::Dialog).
102#[cfg_attr(feature = "v4_10", deprecated = "Since 4.10")]
103#[allow(deprecated)]
104pub trait DialogExtManual: sealed::Sealed + IsA<Dialog> + 'static {
105    /// Adds multiple buttons.
106    ///
107    /// This is the same as calling [`DialogExt::add_button()`][crate::prelude::DialogExt::add_button()]
108    /// repeatedly. The variable argument list should be [`None`]-terminated
109    /// as with [`Dialog::with_buttons()`][crate::Dialog::with_buttons()]. Each button must have both
110    /// text and response ID.
111    ///
112    /// # Deprecated since 4.10
113    ///
114    /// Use [`Window`][crate::Window] instead
115    /// ## `first_button_text`
116    /// button text
117    #[doc(alias = "gtk_dialog_add_buttons")]
118    fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
119        for &(text, id) in buttons {
120            Self::add_button(self, text, id);
121        }
122    }
123
124    /// Gets the response id of a widget in the action area
125    /// of a dialog.
126    ///
127    /// # Deprecated since 4.10
128    ///
129    /// Use [`Window`][crate::Window] instead
130    /// ## `widget`
131    /// a widget in the action area of @self
132    ///
133    /// # Returns
134    ///
135    /// the response id of @widget, or [`ResponseType::None`][crate::ResponseType::None]
136    ///  if @widget doesn’t have a response id set.
137    #[doc(alias = "gtk_dialog_get_response_for_widget")]
138    #[doc(alias = "get_response_for_widget")]
139    fn response_for_widget<P: IsA<Widget>>(&self, widget: &P) -> ResponseType {
140        unsafe {
141            from_glib(ffi::gtk_dialog_get_response_for_widget(
142                AsRef::<Dialog>::as_ref(self).to_glib_none().0,
143                widget.as_ref().to_glib_none().0,
144            ))
145        }
146    }
147
148    // rustdoc-stripper-ignore-next
149    /// Shows the dialog and returns a `Future` that resolves to the
150    /// `ResponseType` on response.
151    ///
152    /// ```no_run
153    /// use gtk4::prelude::*;
154    ///
155    /// # async fn run() {
156    /// let dialog = gtk4::MessageDialog::builder()
157    ///     .buttons(gtk4::ButtonsType::OkCancel)
158    ///     .text("What is your answer?")
159    ///     .build();
160    ///
161    /// let answer = dialog.run_future().await;
162    /// dialog.close();
163    /// println!("Answer: {:?}", answer);
164    /// # }
165    /// ```
166    fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
167        Box::pin(async move {
168            let (sender, receiver) = futures_channel::oneshot::channel();
169
170            let sender = Cell::new(Some(sender));
171
172            let response_handler = self.connect_response(move |_, response_type| {
173                if let Some(m) = sender.replace(None) {
174                    let _result = m.send(response_type);
175                }
176            });
177
178            self.as_ref().present();
179
180            if let Ok(response) = receiver.await {
181                self.disconnect(response_handler);
182                response
183            } else {
184                ResponseType::None
185            }
186        })
187    }
188
189    // rustdoc-stripper-ignore-next
190    /// Shows the dialog and calls the callback when a response has been
191    /// received.
192    ///
193    /// **Important**: this function isn't blocking.
194    ///
195    /// ```no_run
196    /// use gtk4::prelude::*;
197    ///
198    /// let dialog = gtk4::MessageDialog::builder()
199    ///     .buttons(gtk4::ButtonsType::OkCancel)
200    ///     .text("What is your answer?")
201    ///     .build();
202    ///
203    /// dialog.run_async(|obj, answer| {
204    ///     obj.close();
205    ///     println!("Answer: {:?}", answer);
206    /// });
207    /// ```
208    fn run_async<F: FnOnce(&Self, ResponseType) + 'static>(&self, f: F) {
209        let response_handler = Rc::new(RefCell::new(None));
210        let response_handler_clone = response_handler.clone();
211        let f = RefCell::new(Some(f));
212        *response_handler.borrow_mut() = Some(self.connect_response(move |s, response_type| {
213            if let Some(handler) = response_handler_clone.borrow_mut().take() {
214                s.disconnect(handler);
215            }
216            (*f.borrow_mut()).take().expect("cannot get callback")(s, response_type);
217        }));
218        self.as_ref().present();
219    }
220}
221
222impl<O: IsA<Dialog>> DialogExtManual for O {}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227    use crate as gtk4;
228
229    #[test]
230    async fn dialog_future() {
231        let dialog = Dialog::new();
232        glib::idle_add_local_once(glib::clone!(
233            #[strong]
234            dialog,
235            move || {
236                dialog.response(ResponseType::Ok);
237            }
238        ));
239        let response = dialog.run_future().await;
240        assert_eq!(response, ResponseType::Ok);
241    }
242}