Skip to main content

gtk/
dialog.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::prelude::*;
4use crate::Dialog;
5use crate::DialogFlags;
6use crate::ResponseType;
7use crate::Widget;
8use crate::Window;
9use glib::translate::*;
10use std::cell::Cell;
11use std::future::Future;
12use std::pin::Pin;
13use std::ptr;
14
15impl Dialog {
16    /// Creates a new [`Dialog`][crate::Dialog] with title `title` (or [`None`] for the default
17    /// title; see [`GtkWindowExt::set_title()`][crate::prelude::GtkWindowExt::set_title()]) and transient parent `parent` (or
18    /// [`None`] for none; see [`GtkWindowExt::set_transient_for()`][crate::prelude::GtkWindowExt::set_transient_for()]). The `flags`
19    /// argument can be used to make the dialog modal ([`DialogFlags::MODAL`][crate::DialogFlags::MODAL])
20    /// and/or to have it destroyed along with its transient parent
21    /// ([`DialogFlags::DESTROY_WITH_PARENT`][crate::DialogFlags::DESTROY_WITH_PARENT]). After `flags`, button
22    /// text/response ID pairs should be listed, with a [`None`] pointer ending
23    /// the list. Button text can be arbitrary text. A response ID can be
24    /// any positive number, or one of the values in the [`ResponseType`][crate::ResponseType]
25    /// enumeration. If the user clicks one of these dialog buttons,
26    /// [`Dialog`][crate::Dialog] will emit the [`response`][struct@crate::Dialog#response] signal with the corresponding
27    /// response ID. If a [`Dialog`][crate::Dialog] receives the [`delete-event`][struct@crate::Widget#delete-event] signal,
28    /// it will emit ::response with a response ID of [`ResponseType::DeleteEvent`][crate::ResponseType::DeleteEvent].
29    /// However, destroying a dialog does not emit the ::response signal;
30    /// so be careful relying on ::response when using the
31    /// [`DialogFlags::DESTROY_WITH_PARENT`][crate::DialogFlags::DESTROY_WITH_PARENT] flag. Buttons are from left to right,
32    /// so the first button in the list will be the leftmost button in the dialog.
33    ///
34    /// Here’s a simple example:
35    ///
36    ///
37    /// **⚠️ The following code is in C ⚠️**
38    ///
39    /// ```C
40    ///  GtkWidget *main_app_window; // Window the dialog should show up on
41    ///  GtkWidget *dialog;
42    ///  GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
43    ///  dialog = gtk_dialog_new_with_buttons ("My dialog",
44    ///                                        main_app_window,
45    ///                                        flags,
46    ///                                        _("_OK"),
47    ///                                        GTK_RESPONSE_ACCEPT,
48    ///                                        _("_Cancel"),
49    ///                                        GTK_RESPONSE_REJECT,
50    ///                                        NULL);
51    /// ```
52    /// ## `title`
53    /// Title of the dialog, or [`None`]
54    /// ## `parent`
55    /// Transient parent of the dialog, or [`None`]
56    /// ## `flags`
57    /// from [`DialogFlags`][crate::DialogFlags]
58    /// ## `first_button_text`
59    /// text to go in first button, or [`None`]
60    ///
61    /// # Returns
62    ///
63    /// a new [`Dialog`][crate::Dialog]
64    #[doc(alias = "gtk_dialog_new_with_buttons")]
65    pub fn with_buttons<T: IsA<Window>>(
66        title: Option<&str>,
67        parent: Option<&T>,
68        flags: DialogFlags,
69        buttons: &[(&str, ResponseType)],
70    ) -> Dialog {
71        assert_initialized_main_thread!();
72        let ret: Dialog = unsafe {
73            Widget::from_glib_none(ffi::gtk_dialog_new_with_buttons(
74                title.to_glib_none().0,
75                parent.map(|p| p.as_ref()).to_glib_none().0,
76                flags.into_glib(),
77                ptr::null_mut(),
78            ))
79            .unsafe_cast()
80        };
81
82        ret.add_buttons(buttons);
83        ret
84    }
85}
86
87mod sealed {
88    pub trait Sealed {}
89    impl<T: glib::IsA<crate::Dialog> + glib::IsA<crate::Widget>> Sealed for T {}
90}
91
92pub trait DialogExtManual: IsA<Dialog> + IsA<Widget> + sealed::Sealed + 'static {
93    /// Adds more buttons, same as calling [`DialogExt::add_button()`][crate::prelude::DialogExt::add_button()]
94    /// repeatedly. The variable argument list should be [`None`]-terminated
95    /// as with `gtk_dialog_new_with_buttons()`. Each button must have both
96    /// text and response ID.
97    /// ## `first_button_text`
98    /// button text
99    #[doc(alias = "gtk_dialog_add_buttons")]
100    fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
101        for &(text, id) in buttons {
102            //FIXME: self.add_button don't work on 1.8
103            Self::add_button(self, text, id);
104        }
105    }
106
107    // rustdoc-stripper-ignore-next
108    /// Shows the dialog and returns a `Future` that resolves to the
109    /// `ResponseType` on response.
110    ///
111    /// ```no_run
112    /// use gtk::prelude::*;
113    ///
114    /// # async fn run() {
115    /// let dialog = gtk::MessageDialog::builder()
116    ///    .buttons(gtk::ButtonsType::OkCancel)
117    ///    .text("What is your answer?")
118    ///    .build();
119    ///
120    /// let answer = dialog.run_future().await;
121    /// dialog.close();
122    /// println!("Answer: {:?}", answer);
123    /// # }
124    /// ```
125    fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
126        Box::pin(async move {
127            let (sender, receiver) = futures_channel::oneshot::channel();
128
129            let sender = Cell::new(Some(sender));
130
131            let response_handler = self.connect_response(move |_, response_type| {
132                if let Some(m) = sender.replace(None) {
133                    let _result = m.send(response_type);
134                }
135            });
136
137            self.show();
138
139            if let Ok(response) = receiver.await {
140                if response != ResponseType::DeleteEvent {
141                    self.disconnect(response_handler);
142                }
143                response
144            } else {
145                ResponseType::None
146            }
147        })
148    }
149}
150
151impl<O: IsA<Dialog> + IsA<Widget>> DialogExtManual for O {}