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
95// rustdoc-stripper-ignore-next
96/// Trait containing manually implemented methods of [`Dialog`](crate::Dialog).
97#[cfg_attr(feature = "v4_10", deprecated = "Since 4.10")]
98#[allow(deprecated)]
99pub trait DialogExtManual: IsA<Dialog> + 'static {
100    /// Adds multiple buttons.
101    ///
102    /// This is the same as calling [`DialogExt::add_button()`][crate::prelude::DialogExt::add_button()]
103    /// repeatedly. The variable argument list should be [`None`]-terminated
104    /// as with [`Dialog::with_buttons()`][crate::Dialog::with_buttons()]. Each button must have both
105    /// text and response ID.
106    ///
107    /// # Deprecated since 4.10
108    ///
109    /// Use [`Window`][crate::Window] instead
110    /// ## `first_button_text`
111    /// button text
112    #[doc(alias = "gtk_dialog_add_buttons")]
113    fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
114        for &(text, id) in buttons {
115            Self::add_button(self, text, id);
116        }
117    }
118
119    /// Gets the response id of a widget in the action area
120    /// of a dialog.
121    ///
122    /// # Deprecated since 4.10
123    ///
124    /// Use [`Window`][crate::Window] instead
125    /// ## `widget`
126    /// a widget in the action area of @self
127    ///
128    /// # Returns
129    ///
130    /// the response id of @widget, or [`ResponseType::None`][crate::ResponseType::None]
131    ///  if @widget doesn’t have a response id set.
132    #[doc(alias = "gtk_dialog_get_response_for_widget")]
133    #[doc(alias = "get_response_for_widget")]
134    fn response_for_widget<P: IsA<Widget>>(&self, widget: &P) -> ResponseType {
135        unsafe {
136            from_glib(ffi::gtk_dialog_get_response_for_widget(
137                AsRef::<Dialog>::as_ref(self).to_glib_none().0,
138                widget.as_ref().to_glib_none().0,
139            ))
140        }
141    }
142
143    // rustdoc-stripper-ignore-next
144    /// Shows the dialog and returns a `Future` that resolves to the
145    /// `ResponseType` on response.
146    ///
147    /// ```no_run
148    /// use gtk4::prelude::*;
149    ///
150    /// # async fn run() {
151    /// let dialog = gtk4::MessageDialog::builder()
152    ///     .buttons(gtk4::ButtonsType::OkCancel)
153    ///     .text("What is your answer?")
154    ///     .build();
155    ///
156    /// let answer = dialog.run_future().await;
157    /// dialog.close();
158    /// println!("Answer: {:?}", answer);
159    /// # }
160    /// ```
161    fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
162        Box::pin(async move {
163            let (sender, receiver) = futures_channel::oneshot::channel();
164
165            let sender = Cell::new(Some(sender));
166
167            let response_handler = self.connect_response(move |_, response_type| {
168                if let Some(m) = sender.replace(None) {
169                    let _result = m.send(response_type);
170                }
171            });
172
173            self.as_ref().present();
174
175            if let Ok(response) = receiver.await {
176                self.disconnect(response_handler);
177                response
178            } else {
179                ResponseType::None
180            }
181        })
182    }
183
184    // rustdoc-stripper-ignore-next
185    /// Shows the dialog and calls the callback when a response has been
186    /// received.
187    ///
188    /// **Important**: this function isn't blocking.
189    ///
190    /// ```no_run
191    /// use gtk4::prelude::*;
192    ///
193    /// let dialog = gtk4::MessageDialog::builder()
194    ///     .buttons(gtk4::ButtonsType::OkCancel)
195    ///     .text("What is your answer?")
196    ///     .build();
197    ///
198    /// dialog.run_async(|obj, answer| {
199    ///     obj.close();
200    ///     println!("Answer: {:?}", answer);
201    /// });
202    /// ```
203    fn run_async<F: FnOnce(&Self, ResponseType) + 'static>(&self, f: F) {
204        let response_handler = Rc::new(RefCell::new(None));
205        let response_handler_clone = response_handler.clone();
206        let f = RefCell::new(Some(f));
207        *response_handler.borrow_mut() = Some(self.connect_response(move |s, response_type| {
208            if let Some(handler) = response_handler_clone.borrow_mut().take() {
209                s.disconnect(handler);
210            }
211            (*f.borrow_mut()).take().expect("cannot get callback")(s, response_type);
212        }));
213        self.as_ref().present();
214    }
215}
216
217impl<O: IsA<Dialog>> DialogExtManual for O {}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use crate as gtk4;
223
224    #[test]
225    async fn dialog_future() {
226        let dialog = Dialog::new();
227        glib::idle_add_local_once(glib::clone!(
228            #[strong]
229            dialog,
230            move || {
231                dialog.response(ResponseType::Ok);
232            }
233        ));
234        let response = dialog.run_future().await;
235        assert_eq!(response, ResponseType::Ok);
236    }
237}