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::DialogFlags;
4use crate::ResponseType;
5use crate::Widget;
6use crate::Window;
7use crate::prelude::*;
8use crate::{Dialog, ffi};
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
87pub trait DialogExtManual: IsA<Dialog> + IsA<Widget> + 'static {
88    /// Adds more buttons, same as calling [`DialogExt::add_button()`][crate::prelude::DialogExt::add_button()]
89    /// repeatedly. The variable argument list should be [`None`]-terminated
90    /// as with `gtk_dialog_new_with_buttons()`. Each button must have both
91    /// text and response ID.
92    /// ## `first_button_text`
93    /// button text
94    #[doc(alias = "gtk_dialog_add_buttons")]
95    fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
96        for &(text, id) in buttons {
97            //FIXME: self.add_button don't work on 1.8
98            Self::add_button(self, text, id);
99        }
100    }
101
102    // rustdoc-stripper-ignore-next
103    /// Shows the dialog and returns a `Future` that resolves to the
104    /// `ResponseType` on response.
105    ///
106    /// ```no_run
107    /// use gtk::prelude::*;
108    ///
109    /// # async fn run() {
110    /// let dialog = gtk::MessageDialog::builder()
111    ///    .buttons(gtk::ButtonsType::OkCancel)
112    ///    .text("What is your answer?")
113    ///    .build();
114    ///
115    /// let answer = dialog.run_future().await;
116    /// dialog.close();
117    /// println!("Answer: {:?}", answer);
118    /// # }
119    /// ```
120    fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
121        Box::pin(async move {
122            let (sender, receiver) = futures_channel::oneshot::channel();
123
124            let sender = Cell::new(Some(sender));
125
126            let response_handler = self.connect_response(move |_, response_type| {
127                if let Some(m) = sender.replace(None) {
128                    let _result = m.send(response_type);
129                }
130            });
131
132            self.show();
133
134            if let Ok(response) = receiver.await {
135                if response != ResponseType::DeleteEvent {
136                    self.disconnect(response_handler);
137                }
138                response
139            } else {
140                ResponseType::None
141            }
142        })
143    }
144}
145
146impl<O: IsA<Dialog> + IsA<Widget>> DialogExtManual for O {}