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 {}